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


90

我应该给班级成员这样的默认值吗?

class Foo:
    num = 1

还是这样?

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

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

bar = Foo()
bar.num += 1

是一个定义明确的操作。

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

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

Answers:


132

扩展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无法更改。我相信,原因主要是散列。


2
最近,我遇到了一种实现可变属性默认值的简单得多的方法。只需self.attr = attr or []__init__方法中编写即可。(我认为)它可以达到相同的结果,并且仍然清晰易读。
条例草案

4
抱歉!我在上面的评论中对该建议进行了更多研究,显然它并不完全相同,因此不建议使用
条例草案

为什么TestC类的括号是空的?那是Python 2的遗产吗?
克里斯·格

55

这两个代码段执行不同的操作,所以这不是品味问题,而是您所处上下文中正确行为的问题。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

实际答案

如果我不需要类变量,而只需要为实例变量设置默认值,那么这两种方法是否一样好?还是其中一个比另一个更“ pythonic”?

为此,展览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

1
是此示例中使用的init方法与用于初始化@badp的underscore___init__underscore不同
Eliethesaiyan 2015年

第一个例子不应该是def __init__(self, num = None)
PyWalker2797 '19

6

只要您谨慎使用不可变值,使用类成员来提供默认值就可以很好地工作。如果您尝试使用列表或字典来进行操作,那将是非常致命的。只要实例属性是对类的引用,只要默认值是None,它也可以使用。

我已经看到该技术在repoze中非常成功地使用了,repoze是在Zope之上运行的框架。这样做的好处不仅在于,当您的类持久化到数据库时,仅需要保存非默认属性,而且还需要在架构中添加新字段时,所有现有对象都可以看到具有默认值的新字段不需要实际更改存储数据的值。

我发现它在更通用的编码中也很好用,但这是一种风格。使用最快乐的东西。


3

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

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

除了只有第一个[]被克隆在附近;x.ly.l都是邪恶的名字和链接到同一个参考对象的不同立即值。(由语言律师组成)
Phlip,2015年

1

您也可以将类变量声明为None,这将阻止传播。当您需要一个定义明确的类并希望防止AttributeError时,这很有用。例如:

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


0

有了dataclasses(Python 3.7中添加的一项功能),现在还有另一种(非常方便的)方式可以在类实例上设置默认值。装饰器dataclass将在您的类上自动生成一些方法,例如构造函数。如上面链接的文档所述,“在这些生成的方法中使用的成员变量是使用PEP 526类型注释定义的”。

考虑到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)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.