在Python的类中将实例变量声明为None是一种好习惯吗?


68

考虑以下类别:

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

我的同事倾向于这样定义:

class Person:
    name = None
    age = None

    def __init__(self, name, age):
        self.name = name
        self.age = age

这样做的主要原因是他们选择的编辑器显示了自动补全的属性。

就我个人而言,我不喜欢后者,因为一个类将那些属性设置为毫无意义None

哪种方法更好,原因何在?


62
永远不要让您的IDE规定您编写什么代码?
马丁·彼得

14
顺便说一句:使用适当的python IDE(例如PyCharm),在__init__已经设置的属性中提供自动补全等功能。此外,using None可以防止IDE推断出更好的属性类型,因此最好使用明智的默认值(当可能)。
Bakuriu 2014年

如果仅用于自动补全,则可以使用类型提示,其他文档字符串也将是加号。
dashesy

3
“永远不要让您的IDE决定您编写什么代码”是一个有争议的问题。从Python 3.6开始,有一个在线注释和一个typing模块,如果您喜欢这种话,便可以向IDE和linter提供提示...
cz

1
这些在类级别的分配对其余代码没有影响。它们对没有影响self。即使在实例中未显示self.name或未self.age分配__init__它们self,它们也只会在类中显示Person
jolvi

Answers:


70

我将后一种不好的做法称为“这没有按照您的想法做”。

您的同事的位置可以改写为:“我将创建一堆类静态准全局变量,这些变量从未被访问过,但是它们确实占用了各个类的命名空间表(__dict__)中的空间,这只是为了使我的IDE能够做到东西。”


4
然后有点像docstrings ;-)当然,Python -OO对于一些需要它的人来说,有一个便捷的模式可以剥离它们。
史蒂夫·杰索普

15
迄今为止,占用的空间是此约定令人讨厌的最不重要的原因。每个名称(每个进程)一打字节。我觉得只是在浪费时间阅读有关该问题的答案,这就是节省空间成本的重要性。

8
@delnan我同意条目的内存大小是没有意义的,我更认为是在逻辑/心理空间中占用的空间,进行自省式调试,因此我想这需要更多的读取和排序。我-LL
StarWeaver

3
实际上,如果您对空间如此偏执,您会注意到这可以节省空间并浪费时间。未初始化的成员会使用类值,因此没有一堆变量名映射到None(每个实例一个)。因此,该类的成本为几个字节,而每个实例节省的成本为几个字节。但是每次失败的变量名搜索都会花费一点时间。
乔恩·杰伊·奥伯马克2014年

2
如果您担心8个字节,则不会使用Python。
cz

26

1.使您的代码易于理解

读取代码比编写代码要多得多。使您的代码维护人员的工作更加轻松(明年也可能要自己做)。

我不知道任何硬性规则,但我更喜欢将任何未来的实例状态明确声明为完全不使用。与崩溃已经AttributeError很糟糕了。不清楚实例属性的生命周期会更糟。恢复可能导致导致属性分配的呼叫序列所需的精神体操量很容易变得不平凡,从而导致错误。

因此,我通常不仅在构造函数中定义所有内容,而且还努力将可变属性的数量保持在最少。

2.不要混用类级别和实例级别的成员

您在class声明内定义的所有内容均属于该类,并且由该类的所有实例共享。例如,当您在类内定义函数时,该函数将成为所有实例都相同的方法。同样适用于数据成员。这与您通常在中定义的实例属性完全不同__init__

类级数据成员作为常量最有用:

class Missile(object):
  MAX_SPEED = 100  # all missiles accelerate up to this speed
  ACCELERATION = 5  # rate of acceleration per game frame

  def move(self):
    self.speed += self.ACCELERATION
    if self.speed > self.MAX_SPEED:
      self.speed = self.MAX_SPEED
    # ...

2
是的,但是将类级成员和实例级成员混合在一起几乎就是def所做的。它创建了我们认为是对象属性的函数,但实际上是类的成员。财产及其类似的东西也是如此。当工作确实由类来介导时,给人一种幻想属于对象的想法,这是一种永不过时的方法。如果对于Python本身来说还可以的话,那怎么可能会那么糟糕。
乔恩·杰伊·奥伯马克2014年

好吧,Python中的方法解析顺序(也适用于数据成员)不是很简单。在实例级别找不到的内容将在类级别然后在基类之间搜索,等等。您确实可以通过分配同名实例级别成员来隐藏类级别成员(数据或方法)。但是实例级方法绑定到实例的方法self,不需要self传递,而类级方法是未绑定的,并且是普通函数,如在def时间上看到的那样,并接受实例作为第一个参数。所以这些是不同的事情。
9000

我认为这AttributeError是一个不错的信号,而不是一个错误。否则,您将吞噬“无”并获得毫无意义的结果。对于在中定义属性的情况,这尤其重要__init__,因此缺少的属性(但在类级别存在)只能由bug继承引起。
Davidmh 2014年

@Davidmh:确实,检测到的错误总是比未检测到的错误好!我想说的是,如果必须创建一个属性None,而在实例构建时此值没有意义,则说明您的体系结构存在问题,必须重新考虑该属性值或其初始值的生命周期。请注意,通过及早定义属性,甚至在编写类的其余部分之前就可以检测到此类问题,更不用说运行代码了。
9000

好玩!导弹!反正,我敢肯定它的确定做出一流水平的VAR和它们混合...只要类级别包含默认值,等等
埃里克Aronesty

18

我个人使用__ init __()方法定义成员。我从没想过要在课堂上定义它们。但是我总是这样做:我初始化__ init__方法中的所有成员,甚至那些__ init__方法中不需要的成员。

例:

class Person:
    def __init__(self, name, age):
        self._name = name
        self._age = age
        self._selected = None

   def setSelected(self, value):
        self._selected = value

我认为在一个地方定义所有成员很重要。它使代码更具可读性。无论是在__ init __()内部还是外部,都不那么重要。但是,团队必须采用或多或少相同的编码风格,这一点很重要。

哦,您可能会注意到我曾经在成员变量中添加前缀“ _”。


13
您应该在构造函数中设置所有值。如果该值是在类本身中设置的,则它将在实例之间共享,这对于None(无),但对于大多数值而言不是很好。因此,请勿对此进行任何更改。;)
Remco Haszing 2014年

4
参数的默认值以及采用任意位置和关键字参数的能力使它的痛苦比预期的要少得多。(实际上,可以将构造委托给其他方法甚至是独立函数,因此,如果您确实需要多个调度,也可以这样做)。
肖恩·维埃拉

6
@Benedict Python没有访问控制。下划线是公认的实现细节约定。参见PEP 8
2014年

3
@Doval非常有理由在属性名称前加上_:表示它是私有的!(为什么这个线程中有那么多人将Python与其他语言混淆或半混淆?)

1
嗯,所以您是所有公开互动的人的属性或功能之一...对不起,我误会了。在我看来,这种风格总是显得过分,但它有一定的追随者。
乔恩·杰伊·奥伯马克2014年

11

这是一个坏习惯。您不需要这些值,它们会使代码混乱,并且可能导致错误。

考虑:

>>> class WithNone:
...   x = None
...   y = None
...   def __init__(self, x, y):
...     self.x = x
...     self.y = y
... 
>>> class InitOnly:
...   def __init__(self, x, y):
...     self.x = x
...     self.y = y
... 
>>> wn = WithNone(1,2)
>>> wn.x
1
>>> WithNone.x #Note that it returns none, no error
>>> io = InitOnly(1,2)
>>> InitOnly.x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: class InitOnly has no attribute 'x'

我很难称其为“导致错误”。您在此处请求'x'意味着什么模棱两可,但是您很可能想要最可能的初始值。
乔恩·杰伊·奥伯马克2014年

我应该详细说明,如果使用不正确,可能会导致错误。
丹妮丝2014年

较高的风险仍在初始化对象。没有人真的很安全。它将导致的唯一错误是吞下真正la脚的异常。
乔恩·杰伊·奥伯马克2014年

1
如果错误可以避免更严重的问题,则不会导致错误是一个问题。
Erik Aronesty

0

我将使用“有点像docstrings,然后”并声明此无害,只要它始终为None,或者其他值的狭窄范围都不变。

它散发出狂热的气息,并且过度依赖静态类型的语言。而且它不适合作为代码。但是在文档中保留了次要目的。

它记录了期望的名称,因此,如果我与某人组合代码,而我们中的一个拥有“ username”,另一个拥有user_name,那么对于人类来说,有一条线索是我们已经分道扬and,并且没有使用相同的变量。

强制将完全初始化作为策略可以以更加Python的方式实现相同的目的,但是,如果中包含实际的代码,则__init__可以提供一个更清晰的位置来记录使用中的变量。

显然,这里的BIG问题是,它会诱使人们使用以外的值进行初始化None,这可能很糟糕:

class X:
    v = {}
x = X()
x.v[1] = 2

留下全局跟踪,并且不为x创建实例。

但这在整个Python中比在本实践中更像是一个怪癖,我们应该已经对此产生了偏执。

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.