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

时间:2010-04-21 08:11:41

标签: python class oop

我应该给我的班级成员默认值如下:

class Foo:
    num = 1

还是喜欢这个?

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

this question我发现在这两种情况下,

bar = Foo()
bar.num += 1

是一个明确定义的操作。

我知道第一种方法会给我一个类变量,而第二种方法则不会。但是,如果我不需要类变量,但只需要为我的实例变量设置默认值,这两种方法同样好吗?或者其中一个比另一个更'pythonic'?

我注意到的一件事是在Django教程中,they use the second method to declare Models.我个人认为第二种方法更优雅,但我想知道“标准”方式是什么。

6 个答案:

答案 0 :(得分:110)

扩展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都有共享属性。这通常是不受欢迎的。

当类型是可变的时,这是为实例变量定义默认值的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
[]

我的第一段代码工作原因是因为,对于不可变类型,Python会在您需要时创建它的新实例。如果您需要添加1比1,Python会为您创建一个新的2,因为旧的1无法更改。我相信原因主要是哈希。

答案 1 :(得分:47)

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

图表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绑定到栏。传播变化!

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

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

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

这样你就可以:

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

答案 2 :(得分:4)

只要您只注意使用不可变值,使用类成员给出默认值就可以很好地工作。如果你试图用一个非常致命的列表或词典来做。它也适用于instance属性是对类的引用,只要默认值为None。

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

我发现它在更通用的编码中也很有效,但它是一种风格的东西。使用你最开心的任何事情。

答案 3 :(得分:3)

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

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

答案 4 :(得分:1)

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

true

此外,如果您需要默认值:

>>> 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 >>> 中默认值的信息。

答案 5 :(得分:0)

使用dataclasses(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)