如果只有一个实例,我应该使用实例属性还是类属性?

137
我有Python类,运行时只需要一个实例,因此每个类只需要属性一次而不是每个实例都有。如果有多个实例(这不会发生),则所有实例应具有相同的配置。我想知道以下哪种选项更好或更符合"惯用的"Python。
类变量:
class MyController(Controller):

  path = "something/"
  children = [AController, BController]

  def action(self, request):
    pass

实例变量:

class MyController(Controller):

  def __init__(self):
    self.path = "something/"
    self.children = [AController, BController]

  def action(self, request):
    pass
4个回答

177

如果您只有一个实例,那么最好将所有变量设置为每个实例,因为它们将被访问得更快(由于从类到实例的“继承”,少了一级“查找”),并且没有任何缺点可以抵消这个小优势。


9
从未听说过Borg模式?一开始就只有一个实例是错误的方式。 - Devin Jeanpierre
486
@Devin,是的,我听说过Borg模式,因为我是介绍它的人(在2001年,参见http://code.activestate.com/recipes/66531-singleton-we-dont-need-no-stinkin-singleton-the-bo/ ;-)。但是,在简单情况下,只需有一个单一实例而没有强制措施也没问题。 - Alex Martelli
2
@user1767754,你可以使用python -mtimeit轻松自己制作它们。但是,我刚在Python3.4中完成了这项工作,我注意到访问int类变量实际上比在我的旧工作站上访问相同的实例变量要快大约5到11纳秒——不确定是什么代码路径导致了这种情况。 - Alex Martelli

49

进一步呼应MikeAlex的建议,并添加我自己的观点...

使用实例属性是典型的...更习惯用的Python方式。类属性没有被广泛使用,因为它们的用例是特定的。对于静态和类方法与“普通”方法相比也是如此。它们是特殊的构造,解决了特定的用例,否则就是由一个想要展示他们知道Python编程中一些鲜为人知的角落的异常程序员创建的代码。

Alex在他的回复中提到,由于少了一层查找,访问会(稍微)更快...让我进一步澄清那些还不知道这是如何工作的人。这非常类似于变量访问--搜索顺序如下:

  1. 本地变量
  2. 非本地变量
  3. 全局变量
  4. 内置变量

对于属性访问,顺序是:

  1. 实例
  2. 基于MRO(方法分辨率顺序)确定的基类

这两种技术都是以“从内部向外部”的方式工作的,这意味着首先检查最局部的对象,然后按顺序检查外部层。

在上面的例子中,假设您正在查找"path"属性。当它遇到类似"self.path"的引用时,Python首先查找实例属性是否匹配。当失败时,它检查从对象实例化的类。最后,它将搜索基类。正如Alex所述,如果您的属性在实例中找到,则无需查找其他位置,因此可以节省一点时间。
但是,如果您坚持使用类属性,则需要进行额外的查找。或者,您的另一种选择是通过类而不是实例引用对象,例如MyController.path而不是self.path。这是一个直接查找,可以避开延迟查找,但是像alex在下面提到的那样,它是一个全局变量,所以您失去了您认为会节省的那一点(除非您创建对[全局]类名的本地引用)。
底线是大多数情况下应使用实例属性。但是,在某些情况下,类属性是正确的工具。同时使用代码将需要最多的勤奋,因为使用self只会使您获得实例属性对象,并且遮蔽同名的类属性访问。在这种情况下,您必须通过类名访问属性才能引用它。

@wescpy,但是MyController在全局中查找,因此总成本比self.path更高,其中path是实例变量(因为self是方法的本地变量==超快速查找)。 - Alex Martelli
啊,很好。发现得不错。我猜唯一的解决方法就是创建一个本地引用……但现在来看,这并不值得。 - wescpy

28

当不确定时,您可能需要一个实例属性。

类属性最好保留给特殊情况下合适的情况。唯一非常常见的用例是方法。使用类属性作为只读常量以供实例使用并不罕见(尽管唯一的好处是如果您还希望从类外访问),但是您应该谨慎存储其中的任何状态,这通常不是您想要的。即使您只有一个实例,您也应该像处理其他实例一样编写类,这通常意味着使用实例属性。


1
类变量是一种只读常量。如果Python允许我定义常量,我会将其写成常量。 - deamon
1
@deamon,我更倾向于将我的常量完全放在类定义之外,并使用全大写字母进行命名。将它们放在类内也可以。将它们作为实例属性不会有任何影响,但可能有点奇怪。我认为这不是社区过于支持某个选项的问题。 - Mike Graham
@MikeGraham,值得一提的是,Google的Python风格指南建议避免使用全局变量,而是使用类变量。当然也有例外情况。 - Dennis
这里有一个新的链接,指向Google的Python风格指南。现在只是简单地写着:“避免使用全局变量”,并且它们的定义是,全局变量也包括声明为类属性的变量。然而,对于这种问题,Python自己的风格指南(PEP-8)应该是首选。然后你自己的思维应该是最好的工具(当然你也可以从Google等地方获取灵感)。 - colidyre

8

同样的问题在 Python中访问类变量的性能 - 这里的代码改编自@Edward Loper

本地变量是最快访问的,与模块变量几乎一样,其次是类变量,然后是实例变量。

你可以从以下4个范围中访问变量:

  1. 实例变量 (self.varname)
  2. 类变量 (Classname.varname)
  3. 模块变量 (VARNAME)
  4. 本地变量 (varname)

测试如下:

import timeit

setup='''
XGLOBAL= 5
class A:
    xclass = 5
    def __init__(self):
        self.xinstance = 5
    def f1(self):
        xlocal = 5
        x = self.xinstance
    def f2(self):
        xlocal = 5
        x = A.xclass
    def f3(self):
        xlocal = 5
        x = XGLOBAL
    def f4(self):
        xlocal = 5
        x = xlocal
a = A()
'''
print('access via instance variable: %.3f' % timeit.timeit('a.f1()', setup=setup, number=300000000) )
print('access via class variable: %.3f' % timeit.timeit('a.f2()', setup=setup, number=300000000) )
print('access via module variable: %.3f' % timeit.timeit('a.f3()', setup=setup, number=300000000) )
print('access via local variable: %.3f' % timeit.timeit('a.f4()', setup=setup, number=300000000) )

结果如下:
access via instance variable: 93.456
access via class variable: 82.169
access via module variable: 72.634
access via local variable: 72.199

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接