我应该如何在Python中声明实例variables的默认值?

我应该给我的class级成员这样的默认值:

class Foo: num = 1 

或者像这样?

 class Foo: def __init__(self): self.num = 1 

在这个问题中,我发现在这两种情况下,

 bar = Foo() bar.num += 1 

是一个明确的操作。

我明白,第一种方法会给我一个类variables,而第二种方法不会。 但是,如果我不需要一个类variables,但只需要为我的实例variables设置默认值,这两种方法同样好? 或者其中一个比另一个更“pythonic”?

我注意到的一件事是,在Django教程中, 他们使用第二种方法来声明Models。 我个人认为第二种方法更优雅,但我想知道“标准”方式是什么。

扩展bp的答案,我想告诉你他是什么意思的不可变types。

首先,这是好的:

 >>> class TestB(): ... def __init__(self, attr=1): ... self.attr = attr ... >>> a = TestB() >>> b = TestB() >>> a.attr = 2 >>> a.attr 2 >>> b.attr 1 

但是,这只适用于不可变(unchangable)types。 如果默认值是可变的(意味着它可以被replace),则会发生这种情况:

 >>> 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都有一个共享属性。 这通常是不需要的。

当types是可变的时,这是为实例variables定义默认值的Pythonic方法:

 >>> 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 [] 

我的第一个代码片段工作的原因是因为,使用不可变types时,Python会创build一个新的实例。 如果你需要添加1到1,Python会为你创build一个新的2,因为旧的1不能改变。 原因主要是哈希,我相信。

这两个片段做不同的事情,所以这不是一个品味的问题,而是在你的上下文中什么是正确的行为的问题。 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绑定到Foo 。 变化传播!

 >>> 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 

实际答案

如果我不需要一个类的variables,但只需要为我的实例variables设置一个默认值,这两种方法同样好? 或者其中一个比另一个更“pythonic”?

图B中的代码显然是错误的:为什么要将类属性(实例创build时的默认值)绑定到单个实例?

展览A中的代码是可以的。

如果你想给你的构造函数的实例variables的默认值,我会做这个:

 class Foo: def __init__(num = None): self.num = num if num is not None else 1 

…甚至:

 class Foo: DEFAULT_NUM = 1 def __init__(num = None): self.num = num if num is not None else DEFAULT_NUM 

…甚至:(最好,但是当且仅当你处理不可变types!)

 class Foo: def __init__(num = 1): self.num = num 

这样你可以做到:

 foo1 = Foo(4) foo2 = Foo() #use default 

使用类成员实例variables的默认值不是一个好主意,这是我第一次看到这个想法。 它在你的例子中有效,但在很多情况下可能会失败。 例如,如果该值是可变的,则在未修改的实例上对其进行变更将会改变缺省值:

 >>> class c: ... l = [] ... >>> x = c() >>> y = c() >>> xl [] >>> yl [] >>> xlappend(10) >>> yl [10] >>> cl [10] 

使用类成员来给予默认值只要你只是谨慎地用不可变的值来做就行了。 如果你试图用一个清单或一个字典,这将是非常致命的。 只要默认值是None,它也适用于实例属性是对类的引用。

我已经看到这个技术非常成功地用于在Zope之上运行的框架。 这样做的好处不仅在于当你的类被持久化到数据库时,只需要保存非缺省属性,而且当你需要在模式中添加一个新的字段时,所有现有的对象都会看到新的字段的缺省值值而不需要实际改变存储的数据。

我发现它也适用于更一般的编码,但它是一种风格的东西。 使用任何你最快乐的事情。

您也可以将类variables声明为None,这将防止传播。 当你需要一个定义良好的类并且想要阻止AttributeErrors的时候,这是很有用的。 例如:

 >>> class TestClass(object): ... t = None ... >>> test = TestClass() >>> test.t >>> test2 = TestClass() >>> test.t = 'test' >>> test.t 'test' >>> test2.t >>> 

另外如果你需要默认值:

 >>> class TestClassDefaults(object): ... t = None ... def __init__(self, t=None): ... self.t = t ... >>> test = TestClassDefaults() >>> test.t >>> test2 = TestClassDefaults([]) >>> test2.t [] >>> test.t >>> 

当然,仍然按照关于使用可变与不可变types作为__init__的默认值的其他答案中的信息。