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

133 投票
6 回答
219646 浏览
提问于 2025-04-15 21:52

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

class Foo:
    num = 1

还是像这样呢?

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

这个问题中,我发现这两种写法,

bar = Foo()
bar.num += 1

都是合理的操作。

我明白第一种方法会给我一个类变量,而第二种方法则不会。不过,如果我不需要类变量,只是想给我的实例变量设置一个默认值,这两种方法哪个更好呢?或者说其中一种更符合“Python风格”吗?

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

6 个回答

7

使用类成员来设置默认值是个不错的办法,只要你小心不要用可变的值,比如列表或字典,这样做可能会出问题。如果你用的实例属性是指向一个类的引用,只要默认值是None,这种方法也是有效的。

我见过这个技巧在repoze中被成功使用,repoze是一个基于Zope的框架。这样做的好处不仅仅是当你的类保存到数据库时,只需要保存那些非默认的属性,而且当你需要在结构中添加一个新字段时,所有现有的对象都会看到这个新字段及其默认值,而不需要实际更改存储的数据。

我发现这个方法在更一般的编码中也很有效,但这更多是个人风格的问题。用你觉得最舒服的方式就好。

72

这两个代码片段做的事情不一样,所以这不是个人喜好的问题,而是看在你的具体情况中哪个行为是正确的。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

实际回答

如果我不需要类变量,只想为我的实例变量设置一个默认值,这两种方法一样好吗?还是其中一种更“符合Python风格”?

例子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
180

在扩展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是不能被改变的。我认为这样做主要是为了哈希的原因。

撰写回答