现在的位置: 首页 > 综合 > 正文

十个Python陷阱(4-7)

2017年12月15日 ⁄ 综合 ⁄ 共 1972字 ⁄ 字号 评论关闭

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。


7. 个人觉得这不算个陷阱。。。所以就不翻译了。


抱歉!评论已关闭.