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。