关于类:我应该如何在Python中声明实例变量的默认值?

How should I declare default values for instance variables in Python?

我应该给我的类成员这样的默认值吗:

1
2
class Foo:
    num = 1

或者像这样?

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

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

1
2
bar = Foo()
bar.num += 1

是一个定义明确的操作。

我知道第一个方法会给我一个类变量,而第二个方法不会。但是,如果我不需要类变量,但只需要为实例变量设置一个默认值,那么这两种方法是否都一样好?或者其中一个比另一个更"Python"?

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


扩展bp的答案,我想向您展示他所说的不变类型的含义。

首先,这没关系:

1
2
3
4
5
6
7
8
9
10
11
>>> class TestB():
...     def __init__(self, attr=1):
...         self.attr = attr
...    
>>> a = TestB()
>>> b = TestB()
>>> a.attr = 2
>>> a.attr
2
>>> b.attr
1

但是,这只适用于不可变(不可变)类型。如果默认值是可变的(意味着它可以被替换),则会发生以下情况:

1
2
3
4
5
6
7
8
9
10
11
12
>>> class Test():
...     def __init__(self, attr=[]):
...         self.attr = attr
...    
>>> a = Test()
>>> b = Test()
>>> a.attr.append(1)
>>> a.attr
[1]
>>> b.attr
[1]
>>>

注意,ab都有一个共享属性。这通常是不需要的。

当类型可变时,这是定义实例变量默认值的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> 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不能更改。我相信原因主要是散列。


这两个片段做了不同的事情,所以这不是一个品味的问题,而是一个在你的上下文中什么是正确的行为的问题。python文档解释了不同之处,但以下是一些示例:

展示A

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

这会将num绑定到foo实例。对此字段的更改不会传播到其他实例。

因此:

1
2
3
4
5
>>> foo1 = Foo()
>>> foo2 = Foo()
>>> foo1.num = 2
>>> foo2.num
1

展品B

1
2
class Bar:
  num = 1

这会将num绑定到bar类。更改已传播!

1
2
3
4
5
6
7
8
9
10
11
12
>>> 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

实际答案

If I do not require a class variable, but only need to set a default value for my instance variables, are both methods equally good? Or one of them more 'pythonic' than the other?

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

附件A中的代码是可以的。

但是,如果要为构造函数中的实例变量提供默认值,我会这样做:

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

……甚至:

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

…甚至:(首选,但如果且仅当您处理不可变类型时!)

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

您可以这样做:

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


使用类成员提供默认值非常有效,只要您只小心使用不可变的值。如果你试着用一张单子或口述来做,那将是非常致命的。它也适用于实例属性是对类的引用的情况,只要默认值为"无"。

我已经看到这个技术在repoze中非常成功地使用,repoze是一个运行在zope之上的框架。这里的优点不仅是当类被持久化到数据库时,只需要保存非默认属性,而且当您需要向架构中添加一个新字段时,所有现有对象都会看到新字段及其默认值,而不需要实际更改存储的数据。

我发现它在更一般的编码中也能很好地工作,但它是一种风格。用你最喜欢的东西。


使用类成员作为实例变量的默认值不是一个好主意,而且这是我第一次看到提到这个主意。它在您的示例中有效,但在许多情况下可能会失败。例如,如果值是可变的,在未修改的实例上对其进行修改将更改默认值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> class c:
...     l = []
...
>>> x = c()
>>> y = c()
>>> x.l
[]
>>> y.l
[]
>>> x.l.append(10)
>>> y.l
[10]
>>> c.l
[10]


还可以将类变量声明为"无",这将阻止传播。当您需要一个定义良好的类并希望防止attributeErrors时,这非常有用。例如:

1
2
3
4
5
6
7
8
9
10
11
>>> class TestClass(object):
...     t = None
...
>>> test = TestClass()
>>> test.t
>>> test2 = TestClass()
>>> test.t = 'test'
>>> test.t
'test'
>>> test2.t
>>>

如果需要默认值:

1
2
3
4
5
6
7
8
9
10
11
12
>>> class TestClassDefaults(object):
...    t = None
...    def __init__(self, t=None):
...       self.t = t
...
>>> test = TestClassDefaults()
>>> test.t
>>> test2 = TestClassDefaults([])
>>> test2.t
[]
>>> test.t
>>>

当然,仍然遵循其他答案中关于使用可变类型和不可变类型作为__init__中的默认值的信息。