python、__slots__和“属性是只读的”

26 投票
5 回答
30732 浏览
提问于 2025-04-15 11:23

我想在Python中创建一个对象,这个对象有一些属性,我希望能保护自己,避免不小心用错属性名。我的代码是这样的:

class MyClass( object ) :
    m = None # my attribute
    __slots__ = ( "m" ) # ensure that object has no _m etc

a = MyClass() # create one
a.m = "?"  # here is a PROBLEM

但是运行这段简单的代码后,我遇到了一个很奇怪的错误:

Traceback (most recent call last):
  File "test.py", line 8, in <module>
    a.m = "?"
AttributeError: 'test' object attribute 'm' is read-only

有没有哪位聪明的程序员能花点时间告诉我关于“只读”错误的事情呢?

5 个回答

7

__slots__ 是用来管理实例变量的,而你提到的那个是类变量。你应该这样做:

class MyClass( object ) :
  __slots__ = ( "m", )
  def __init__(self):
    self.m = None

a = MyClass()
a.m = "?"       # No error
11

你完全误用了 __slots__。它会阻止为实例创建 __dict__。这样做只有在你遇到很多小对象导致内存问题时才有意义,因为去掉 __dict__ 可以减少内存占用。这是一种极端的优化,在99.9%的情况下都不需要。

如果你需要你所描述的那种安全性,那么Python真的不是合适的语言。最好使用一些严格的语言,比如Java(而不是试图在Python中写Java的代码)。

如果你自己都搞不清楚为什么类属性在你的代码中造成了这些问题,那么也许你应该再考虑一下引入这样的语言技巧。可能更明智的做法是先更熟悉这门语言。

为了完整起见,这里有一个关于 slots文档链接

49

当你使用 __slots__ 来声明实例变量时,Python 会创建一个和你声明的变量同名的描述符对象,作为类变量。在你的例子中,这个描述符被你在下一行定义的类变量 m 覆盖了:

  m = None # my attribute

你需要做的是:不要定义一个叫 m 的类变量,而是在 __init__ 方法中初始化实例变量 m

class MyClass(object):
  __slots__ = ("m",)
  def __init__(self):
    self.m = None

a = MyClass()
a.m = "?"

顺便提一下,单元素的元组在元素后面需要加个逗号。你的代码中这两种写法都可以,因为 __slots__ 可以接受一个字符串或者一个字符串的可迭代对象/序列。一般来说,要定义一个包含元素 1 的元组,应该用 (1,) 或者 1,,而不是 (1)

撰写回答