我应该如何在Python中为实例变量声明默认值?
我应该像这样给我的类成员设置默认值吗:
class Foo:
num = 1
还是像这样呢?
class Foo:
def __init__(self):
self.num = 1
在这个问题中,我发现这两种写法,
bar = Foo()
bar.num += 1
都是合理的操作。
我明白第一种方法会给我一个类变量,而第二种方法则不会。不过,如果我不需要类变量,只是想给我的实例变量设置一个默认值,这两种方法哪个更好呢?或者说其中一种更符合“Python风格”吗?
我注意到在Django的教程中,他们使用第二种方法来声明模型。我个人觉得第二种方法更优雅,但我想知道什么是“标准”的写法。
6 个回答
使用类成员来设置默认值是个不错的办法,只要你小心不要用可变的值,比如列表或字典,这样做可能会出问题。如果你用的实例属性是指向一个类的引用,只要默认值是None,这种方法也是有效的。
我见过这个技巧在repoze中被成功使用,repoze是一个基于Zope的框架。这样做的好处不仅仅是当你的类保存到数据库时,只需要保存那些非默认的属性,而且当你需要在结构中添加一个新字段时,所有现有的对象都会看到这个新字段及其默认值,而不需要实际更改存储的数据。
我发现这个方法在更一般的编码中也很有效,但这更多是个人风格的问题。用你觉得最舒服的方式就好。
这两个代码片段做的事情不一样,所以这不是个人喜好的问题,而是看在你的具体情况中哪个行为是正确的。Python的文档解释了它们之间的区别,下面是一些例子:
例子A
class Foo:
def __init__(self):
self.num = 1
这个代码把num
绑定到Foo的实例上。对这个字段的修改不会影响其他实例。
因此:
>>> foo1 = Foo()
>>> foo2 = Foo()
>>> foo1.num = 2
>>> foo2.num
1
例子B
class Bar:
num = 1
这个代码把num
绑定到Bar的类上。修改会被传播到所有实例!
>>> bar1 = Bar()
>>> bar2 = Bar()
>>> bar1.num = 2 #this creates an INSTANCE variable that HIDES the propagation
>>> bar2.num
1
>>> Bar.num = 3
>>> bar2.num
3
>>> bar1.num
2
>>> bar1.__class__.num
3
实际回答
如果我不需要类变量,只想为我的实例变量设置一个默认值,这两种方法一样好吗?还是其中一种更“符合Python风格”?
例子B中的代码在这种情况下是错误的:你为什么要把一个类属性(在创建实例时的默认值)绑定到单个实例上呢?
例子A中的代码是可以的。
如果你想在构造函数中为实例变量提供默认值,我建议这样做:
class Foo:
def __init__(self, num = None):
self.num = num if num is not None else 1
...或者甚至:
class Foo:
DEFAULT_NUM = 1
def __init__(self, num = None):
self.num = num if num is not None else DEFAULT_NUM
...或者甚至:(更推荐,但仅在处理不可变类型时!)
class Foo:
def __init__(self, num = 1):
self.num = num
这样你就可以做到:
foo1 = Foo(4)
foo2 = Foo() #use default
在扩展bp的回答时,我想给你解释一下他所说的不可变类型。
首先,这样做是可以的:
>>> class TestB():
... def __init__(self, attr=1):
... self.attr = attr
...
>>> a = TestB()
>>> b = TestB()
>>> a.attr = 2
>>> a.attr
2
>>> b.attr
1
不过,这种方式只适用于不可变(也就是不能改变的)类型。如果默认值是可变的(也就是可以被替换的),那么就会出现下面的情况:
>>> class Test():
... def __init__(self, attr=[]):
... self.attr = attr
...
>>> a = Test()
>>> b = Test()
>>> a.attr.append(1)
>>> a.attr
[1]
>>> b.attr
[1]
>>>
注意,a
和b
有一个共享的属性。这通常是我们不想要的结果。
这是在Python中为实例变量定义默认值的正确方式,特别是当类型是可变的时候:
>>> class TestC():
... def __init__(self, attr=None):
... if attr is None:
... attr = []
... self.attr = attr
...
>>> a = TestC()
>>> b = TestC()
>>> a.attr.append(1)
>>> a.attr
[1]
>>> b.attr
[]
我之前的代码片段之所以有效,是因为对于不可变类型,Python每次你需要的时候都会创建一个新的实例。如果你需要将1加1,Python会为你生成一个新的2,因为原来的1是不能被改变的。我认为这样做主要是为了哈希的原因。