如何在 Python 中声明实例变量的默认值?

我是否应该像这样给我的类成员默认值:

class Foo:
num = 1

还是这样?

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

In 这个问题 I discovered that in both cases,

bar = Foo()
bar.num += 1

is a well-defined operation.

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

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

175891 次浏览

这两个片段做不同的事情,所以这不是品味的问题,而是在你的上下文中什么是正确的行为的问题。Python 文档解释了这种差异,但这里有一些例子:

证据一

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

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

因此:

>>> foo1 = Foo()
>>> foo2 = Foo()
>>> foo1.num = 2
>>> foo2.num
1

证据二

class Bar:
num = 1

这将 num绑定到 同学们条。更改将被传播!

>>> 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 中的代码显然是错误的: 为什么要将 class 属性(实例创建时的默认值)绑定到单个实例?

证物 A 的密码没问题。

If you want to give defaults for instance variables in your constructor I would however do this:

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

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

>>> class c:
...     l = []
...
>>> x = c()
>>> y = c()
>>> x.l
[]
>>> y.l
[]
>>> x.l.append(10)
>>> y.l
[10]
>>> c.l
[10]

使用类成员提供默认值效果非常好,只要您注意只使用不可变的值。如果你试图用一个列表或者一个结论来做,那将是相当致命的。在实例属性是对类的引用的情况下,只要默认值为 Nothing,它也可以工作。

我见过这种技术在 repoze 中非常成功地使用,repoze 是一个运行在 Zope 之上的框架。这里的优势不仅在于当您的类被持久化到数据库时,只有非默认属性需要保存,而且当您需要向模式中添加一个新字段时,所有现有对象都可以看到带有默认值的新字段,而不需要实际更改存储的数据。

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

扩展了 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]
>>>

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

当类型可变时,这是为实例变量定义默认值的 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不能更改。我相信大部分原因是哈希。

You can also declare class variables as None which will prevent propagation. This is useful when you need a well defined class and want to prevent 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
>>>

当然,在 __init__中使用可变类型和不可变类型作为默认类型的其他答案中也有相关信息。

通过在 Python 3.7中添加的特性 数据类,现在有了另一种(相当方便的)方法来实现对类实例设置默认值。装饰器 dataclass将自动在类上生成一些方法,比如构造函数。正如上面链接的文档所指出的,“在这些生成的方法中使用的成员变量是使用 PEP 526 type annotations定义的”。

考虑到 OP 的例子,我们可以这样实现它:

from dataclasses import dataclass


@dataclass
class Foo:
num: int = 0


在构造此类的类型的对象时,我们可以选择覆盖该值。

print('Default val: {}'.format(Foo()))
# Default val: Foo(num=0)
print('Custom val: {}'.format(Foo(num=5)))
# Custom val: Foo(num=5)