Python 类与模块属性

13 投票
4 回答
16680 浏览
提问于 2025-04-15 13:28

我想听听大家对Python中类属性的讨论。比如,类属性的好用场景是什么?大多数情况下,我想不出一个类属性比模块级属性更合适的例子。如果真是这样,那为什么还要有类属性呢?

我对类属性的问题是,它们太容易被错误地覆盖了,这样一来,你的“全局”值就变成了一个局部实例属性。

欢迎大家分享你们会如何处理以下情况:

  1. 类和/或子类使用的常量值。这可能包括那些永远不会改变的“魔法数字”字典键或列表索引,但可能需要一次性初始化。
  2. 默认的类属性,在少数情况下会为类的特定实例进行更新。
  3. 用于表示类的内部状态的全局数据结构,这个状态在所有实例之间共享。
  4. 一个类初始化了一些默认属性,这些属性不受构造函数参数的影响。

相关帖子:
类属性和实例属性的区别

4 个回答

2

类属性通常用来让子类可以改变默认设置。举个例子,BaseHTTPRequestHandler这个类有两个类常量,分别是sys_version和server_version,其中server_version的默认值是 "BaseHTTP/" + __version__。而SimpleHTTPRequestHandler这个子类则把server_version改成了 "SimpleHTTP/" + __version__

4

类属性有什么好的使用场景呢?

情况 0. 类方法其实就是类属性。这不仅仅是技术上的相似之处——你可以在运行时通过给它们赋值来访问和修改类方法。

情况 1. 一个模块可以很方便地定义多个类。把关于 class A 的所有内容放在 A... 里,把关于 class B 的所有内容放在 B... 里是很合理的。例如,

# module xxx
class X:
    MAX_THREADS = 100
    ...

# main program
from xxx import X

if nthreads < X.MAX_THREADS: ...

情况 2. 这个类有很多默认属性,可以在实例中修改。在这里,能让属性保持为“全局默认值”是一种特性,而不是缺陷。

class NiceDiff:
    """Formats time difference given in seconds into a form '15 minutes ago'."""

    magic = .249
    pattern = 'in {0}', 'right now', '{0} ago'

    divisions = 1

    # there are more default attributes

人们创建 NiceDiff 的实例来使用现有的或稍微修改过的格式,但如果要把它本地化成另一种语言,就可以通过子类化这个类来以根本不同的方式实现一些功能并且重新定义常量:

class Разница(NiceDiff): # NiceDiff localized to Russian
    '''Из разницы во времени, типа -300, делает конкретно '5 минут назад'.'''

    pattern = 'через {0}', 'прям щас', '{0} назад'

你的使用场景

  • 常量——是的,我把它们放在类里。说 self.CONSTANT = ... 有点奇怪,所以我觉得这样做没有太大风险。
  • 默认属性——有点混合,像上面说的可以放在类里,但也可以放在 __init__ 里,这要看具体情况。
  • 全局数据结构——如果只被这个类使用,就放在类里,但也可以放在模块里,无论哪种情况都必须有非常清晰的文档说明。
7

#4: 我从来不用类属性来初始化默认的实例属性(也就是你通常放在__init__里的那些)。比如:

class Obj(object):
    def __init__(self):
        self.users = 0

而且绝对不要:

class Obj(object):
    users = 0

为什么呢?因为这样不一致:当你赋值给其他对象时,它不会按你想要的那样工作:

class Obj(object):
    users = []

这会导致用户列表在所有对象之间共享,而这在这种情况下是我们不想要的。把这些分成类属性和在__init__里的赋值,取决于它们的类型,这样会让人困惑,所以我总是把它们都放在__init__里,这样我觉得更清晰。


至于其他的,我一般把类特定的值放在类里面。这并不是因为全局变量是“邪恶”的——在某些语言里它们确实比较麻烦,因为它们的作用域是模块,除非模块本身太大——但如果外部代码想要访问它们,把所有相关的值放在一个地方会更方便。例如,在module.py里:

class Obj(object):
    class Exception(Exception): pass
    ...

然后:

from module import Obj

try:
    o = Obj()
    o.go()
except o.Exception:
    print "error"

除了允许子类改变这个值(这并不总是我们想要的),这也意味着我不需要费劲地导入异常名称和其他使用Obj所需的东西。“from module import Obj, ObjException, ...”很快就会让人觉得烦。

撰写回答