我应该给我的班级成员默认值如下:
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.我个人认为第二种方法更优雅,但我想知道“标准”方式是什么。
答案 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]
>>>
请注意,a
和b
都有共享属性。这通常是不受欢迎的。
当类型是可变的时,这是为实例变量定义默认值的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解释了差异,但这里有一些例子:
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
如果我不需要类变量,但只需要为我的实例变量设置默认值,这两种方法同样好吗?或者其中一个比另一个更'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)