四时宝库

程序员的知识宝库

python散装笔记——38: Classes - 类(3)

7: 多重继承

Python 使用 C3 线性化算法来确定类属性(包括方法)的解析顺序。这就是所谓的方法解析顺序 (MRO)。

下面是一个简单的例子:

class Foo(object):
  foo = 'attr foo of Foo'

class Bar(object):
  foo = 'attr foo of Bar' # we won't see this.
  bar = 'attr bar of Bar'
  
class FooBar(Foo, Bar):
  foobar = 'attr foobar of FooBar'

现在,如果我们实例化 FooBar,如果我们查找 foo 属性,我们会发现 Foo 的属性被首先找到

fb = FooBar()

>>> fb.foo
'attr foo of Foo'

这是 FooBar 的 MRO:

FooBar.mro()
[, , , ]

可以简单地说,Python 的 MRO 算法是:

深度优先(例如,先是 FooBar,然后是 Foo),除非共享的父(对象)被子(Bar)阻塞,并且不允许循环关系。

例如,Bar 不能从 FooBar 继承,而 FooBarBar 继承。

有关 Python 中的综合示例,请参阅维基百科条目。

super 可以获取父类的特征。

class Foo(object):
  def foo_method(self):
    print "foo Method"

class Bar(object):
  def bar_method(self):
    print "bar Method"
    
class FooBar(Foo, Bar):
  def foo_method(self):
    super(FooBar, self).foo_method()

当每个类都有自己的 __init__ 方法时,如果我们尝试多重继承,那么只有先继承的类的 __init__ 方法才会被调用。

下面的例子中,只有 Foo 类的初始方法被调用,Bar 类的初始方法没有被调用

class Foo(object):
  def __init__(self):
    print("foo init")

class Bar(object):
  def __init__(self):
    print("bar init")
    
class FooBar(Foo, Bar):
  def __init__(self):
    print("foobar init")
    super(FooBar, self).__init__()
    
a = FooBar()

输出:

foobar init
foo init

但这并不意味着 Bar 类不能继承。最终 FooBar 类的实例也是 Bar 类和 Foo 类的实例。

print(isinstance(a,FooBar))
print(isinstance(a,Foo))
print(isinstance(a,Bar))

输出:

True
True
True

8: 属性

Python 类支持属性,这些属性看起来就像普通的对象变量,但可以附加自定义行为和文档。

class MyClass(object):
  def __init__(self):
    self._my_string = ""
    
    @property
    def string(self):
      """A profoundly important string."""
      return self._my_string

    @string.setter
    def string(self, new_value):
      assert isinstance(new_value, str), \
      "Give me a string, not a %r!" % type(new_value)
      self._my_string = new_value

    @string.deleter
    def x(self):
      self._my_string = None

MyClass 的对象看起来会有一个属性 .string,但它的行为现在受到严格控制:

mc = MyClass()
mc.string = "String!"
print(mc.string)
del mc.string

除了上述有用的语法外,属性语法还允许为这些属性添加验证或其他增强功能。这在公共应用程序接口(Public API)中尤其有用--在公共应用程序接口中,应为用户提供一定程度的帮助。

属性的另一个常见用途是使类能够显示 “虚拟属性”--实际上并不存储的属性,只有在请求时才会计算。

class Character(object):
  def __init__(self, name, max_hp):
    self._name = name
    self._hp = max_hp
    self._max_hp = max_hp


  # Make hp read only by not providing a set method
  @property
  def hp(self):
    return self._hp

  # Make name read only by not providing a set method
  @property
  def name(self):
    return self.name
    
  def take_damage(self, damage):
    self._hp -= damage
    self._hp = 0 if self.hp <0 else self.hp

  @property
  def is_alive(self):
    return self.hp != 0

  @property
  def is_wounded(self):
    return self.hp < self._max_hp if self.hp > 0 else False

  @property
  def is_dead(self):
    return not self.is_alive
      
bilbo = Character('Bilbo Baggins', 100)
bilbo.hp
# out : 100
bilbo.hp = 200
# out : AttributeError: can't set attribute
# hp attribute is read only.

bilbo.is_alive
# out : True
bilbo.is_wounded
# out : False
bilbo.is_dead
# out : False

bilbo.take_damage( 50 )

bilbo.hp
# out : 50

bilbo.is_alive
# out : True
bilbo.is_wounded
# out : True
bilbo.is_dead
# out : False

bilbo.take_damage( 50 )
bilbo.hp
# out : 0

bilbo.is_alive
# out : False
bilbo.is_wounded
# out : False
bilbo.is_dead
# out : True

9: 实例变量的默认值

如果变量包含不可变类型的值(如字符串),则可以像下面这样分配默认值

class Rectangle(object):
  def __init__(self, width, height, color='blue'):
    self.width = width
    self.height = height
    self.color = color
    
  def area(self):
    return self.width * self.height

# Create some instances of the class
default_rectangle = Rectangle(2, 3)
print(default_rectangle.color) # blue

red_rectangle = Rectangle(2, 3, 'red')
print(red_rectangle.color) # red

在构造函数中初始化可变对象(如列表)时需要小心谨慎。请看下面的示例:

class Rectangle2D(object):
  def __init__(self, width, height, pos=[0,0], color='blue'):
    self.width = width
    self.height = height
    self.pos = pos
    self.color = color
    
r1 = Rectangle2D(5,3)
r2 = Rectangle2D(7,8)
r1.pos[0] = 4
r1.pos # [4, 0]
r2.pos # [4, 0] r2 的位置也发生了变化

造成这种行为的原因是,在 Python 中,缺省参数是在函数执行时绑定的,而不是在函数声明时绑定的。要获得一个不在实例间共享的缺省实例变量,应该使用这样的结构:

class Rectangle2D(object):
  def __init__(self, width, height, pos=None, color='blue'):
    self.width = width
    self.height = height
    self.pos = pos or [0, 0] # 默认值是 [0, 0]
    self.color = color
    
r1 = Rectangle2D(5,3)
r2 = Rectangle2D(7,8)
r1.pos[0] = 4
r1.pos # [4, 0]
r2.pos # [0, 0] r2 的位置没有改变

另请参阅可变默认参数Common Gotchas  The Hitchhiker's Guide to Python和 “最小惊奇 ”以及可变默认参数。

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言
    友情链接