4. 类属性和实例属性
这边至少有两个小陷阱。第一,新手习惯于把属性放在类里面(而不是实例中),他们当看到这些属性会在实例之间共享的时候会很惊讶:
>>> class Foo:
... bar = []
... def __init__(self, x):
... self.bar.append(x)
...
>>> f = Foo(42)
>>> g = Foo(100)
>>> f.bar, g.bar
([42, 100], [42, 100])
这不是语言的缺陷,而是非常好的特性,在很多情形下非常有用。造成误解的原因是你使用的是类的属性而不是实例的属性,这两个是有区别的,Python中创建实例的属性和其他语言有区别,在C++或Object Pascal中,需要在class body中定义属性。
另一个小陷阱:self.foo可能指的是实例属性foo,或者在没有实例属性的情况下,指类属性foo。也就是说:在属性名称相同的情况下,实例属性会覆盖类属性。如下:
>>> class Foo:
... a = 42
... def __init__(self):
... self.a = 43
...
>>> f = Foo()
>>> f.a
43
和
>>> class Foo:
... a = 42
...
>>> f = Foo()
>>> f.a
42
第一个例子中,f.a指实例属性,值为43。它覆盖了值为42的类属性。第二个例子中,没有实例属性,所以f.a值类属性。
下面的代码将两者结合起来:
class foo:
bar = []
def __init__(self, x):
print(id(self.bar))
self.bar = self.bar + [x]
print(id(self.bar))
f = foo(1)
print(f.bar)
g = foo(2)
print(g.bar)
prints:
33254536
33257672
[1]
33254536
33251528
[2]
self.bar = self.bar + [x]中,两个self.bar并不相同。第二个指的是类属性bar,而表达式的结果却赋给了实例属性。从属性的id上我们可以清楚的看到。
解决方法:这之间的区别可能会让人迷惑,但是却很好理解。当你想要在各个类实例之间公用某些东西时,使用类属性。为了避免混淆,你可以使用self.__class__.name来代替self.name,即使没有同名属性。当某属性对于某个实例是独一无二时,使用实例属性,并用self.name表示。
下面是一个更令人困惑的例子:
>>> class Foo:
... bar = []
... def __init__(self, x):
... self.bar += [x]
...
>>> f = Foo(42)
>>> g = Foo(100)
>>> f.bar
[42, 100]
>>> g.bar
[42, 100]
读者自己去体会为什么会这样?(参看上一篇的陷阱#3)
5. 可变的默认参数
这个陷阱是上一篇#2的变种:
>>> def popo(x=[]):
... x.append(666)
... print x
...
>>> popo([1, 2, 3])
[1, 2, 3, 666]
>>> x = [1, 2]
>>> popo(x)
[1, 2, 666]
>>> x
[1, 2, 666]
这个是预期的,但是下面的:
>>> popo()
[666]
>>> popo()
[666, 666]
>>> popo()
[666, 666, 666]
可能你觉得不管函数执行多少次,输出都应该是[666],因为popo()执行时默认参数为x = []。错!默认参数只绑定“一次”,是当函数被创建的时候,而不是函数被调用的时候。所以,如果是一个可变对象,每一次调用都会改变它(默认参数时)。
解决方法:这个行为很少会有用处,你只要稍作注意即可。
6. UnboundLocalError
>>> def p():
... x = x + 2
...
>>> p()
Traceback (most recent call last):
File "<input>", line 1, in ?
File "<input>", line 2, in p
UnboundLocalError: local variable 'x' referenced before
assignment
在函数p中,x = x + 2不能被解析,因为x还没有值,再来看看如下代码:
>>> x = 2
>>> def q():
... print x
... x = 3
... print x
...
>>> q()
Traceback (most recent call last):
File "<input>", line 1, in ?
File "<input>", line 2, in q
UnboundLocalError: local variable 'x' referenced before
assignment
换句话说:一个变量可以使global的或者local的,但不能既是global又是local。