3.1 属性的基本操作
默认情况下,创建任何类内部的属性都将支持以下4种操作。
- 创建新属性。
- 为已有属性赋值。
- 获取属性的值。
- 删除属性。
我们可以使用如下简单的代码来对这些操作进行测试,创建一个简单的泛型类并将其实例化。
>>> class Generic:
… pass
…
>>> g= Generic()
以上代码允许我们创建、获取、赋值和删除属性。我们可以容易地创建和获取一个属性,以下是一些例子。
>>> g.attribute= "value"
>>> g.attribute
'value'
>>> g.unset
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Generic' object has no attribute 'unset'
>>> del g.attribute
>>> g.attribute
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Generic' object has no attribute 'attribute'
我们可以添加、修改和删除属性。如果试图获取一个未赋值的属性或者删除一个不存在的属性则会抛出异常。
另一种更好的办法是从types.SimpleNamepace类创建实例。此时不需要额外定义一个新类,就能实现同样的功能。我们可以像如下代码这样创建SimpleNamespace类的对象。
>>> import types
>>> n = types.SimpleNamespace()
在如下代码中,可以看到使用SimpleNamespace类能够完成同样的任务。
>>> n.attribute= "value"
>>> n.attribute
'value'
>>> del n.attribute
>>> n.attribute
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'namespace' object has no attribute 'attribute'
我们可以为这个对象添加属性,试图获取任何一个未定义的属性将会引发异常。比起我们之前看到的,使用创建object类实例的实现方式,使用SimpleNamesp ace类的做法会略有不同。object类的实例不允许创建新属性,因为它缺少Python内部用来存储属性值的dict结构。
特性和init()方法
大多数情况,我们使用类的init()方法来初始化花色特性。理想情况下,可以在init()方法中提供所有属性的默认值。
而在init()方法中没必要为所有的属性赋值。基于这样的考虑,一个特性的存在与否就构成了对象状态的一部分。
可选特性更好地完善了类定义,它使得特性在类的定义中发挥了很大的作用。特性通常可以根据类层次结构进行选择性的添加(或删除)。
因此,可选特性隐藏了一种非正式的子类关系。当使用可选特性时,要考虑到对多态性的影响。
在21点游戏中,需要考虑这样的规则:只允许发牌一次。即如果已经发牌了,就不能再次发牌。我们可以考虑用以下几种方式来实现。
- 基于Hand.split方法提出一个子类,并将其命名为SplitHand,在这里省略该类的具体实现。
- 也可以为Hand对象创建一个Status属性,其值可以从Hand.split()方法返回,函数类型为布尔型,但是我们也可以考虑把它实现为可选属性。
以下是Hand.split()函数的一种实现方式,通过可选属性来检测并阻止多次发牌的操作。
def split( self, deck ):
assert self.cards[0].rank == self.cards[1].rank
try:
self.split_count
raise CannotResplit
except AttributeError:
h0 = Hand( self.dealer_card, self.cards[0], deck.pop() )
h1 = Hand( self.dealer_card, self.cards[1], deck.pop() )
h0.split_count= h1.split_count= 1
return h0, h1
事实上,split()方法的逻辑仅仅是检查是否已存在split_count属性。如果属性存在,则判断为多次分牌操作并抛出异常;如果split_count属性不存在,说明这是第1次发牌,就是允许的。
使用可选属性的一个好处是使得init()方法看起来相对整洁了一些,不好的地方在于它隐藏了一些对象的状态。对于try语句的这种使用方式(检测对象属性是否存在,存在则抛出异常),很容易造成困惑而且应该避免使用。
