【问题标题】:python, __slots__, and "attribute is read-only"python、__slots__ 和“属性是只读的”
【发布时间】:2010-10-23 16:03:50
【问题描述】:

我想在 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

有没有聪明的程序员可以抽出一点时间来告诉我“只读”错误?

【问题讨论】:

  • 这似乎是一个不必要的用例。你为什么做这个?你为什么不使用属性?
  • 我想证明自己不要用错字写一个属性名称,例如,如果我写 object.my_prperty = 1 而不是 object.my_property = 1(错过了 'o'),python 将不会显示任何错误,但我的程序中会出现逻辑错误。所以我想将一个属性限制为我的预定义集,所以只有它们可以被访问。我如何用属性解决这个问题?
  • 通常在 python 中,我们接受作业中拼写错误的可能性,并编写好的测试来捕捉肤浅和更多语义错误。我谦虚地建议,如果你想写 python,你应该考虑用 python 的方式来做,而不是和语言打架。我要补充一点,在过去的 9 年中,我的 python 代码中的拼写错误使我可能花费了 20 分钟。
  • 您也可以查看 Python IDE。我通常使用 IDLE,其中(在 shell 中,而不是在 .py 文件中)你可以这样做:a=MyClass(),然后键入 a。它会告诉你所有的属性。我还使用了 PyDev,它是 Eclipse 的 Python 插件,它甚至可以在 .py 文件中执行此操作。那里还有很多其他人。谷歌或在这个网站上搜索“Python IDE”,我相信你会找到你喜欢的。
  • 错误应该是AttributeError: 'MyClass' object attribute 'm' is read-only

标签: python exception descriptor readonly-attribute


【解决方案1】:

__slots__ 适用于实例变量,而您所拥有的是类变量。你应该这样做:

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

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

【讨论】:

    【解决方案2】:

    当您使用__slots__ 声明实例变量时,Python 会创建一个descriptor object 作为具有相同名称的类变量。在您的情况下,此描述符被您在以下行定义的类变量 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)。

    【讨论】:

    • 你知道为什么如果我写了:slots = [ "m" ] m = 5 Python 会用变量覆盖描述符对象并且不会使用描述符对象的 set()方法来设置值吗?
    • 因为 m 在这个上下文中是一个类变量,而不是一个实例变量。为了利用描述符的 set() 方法,您应该分配给对象的 m,而不是类。如果要初始化对象的实例变量,请在 init 方法中进行,就像我在代码 sn-p 中所做的那样。
    • 文档中的密钥(顺便说一句移动了here)是__slots__ are implemented at the class level by creating descriptors for each variable name. As a result, class attributes cannot be used to set default values for instance variables defined by __slots__; otherwise, the class attribute would overwrite the descriptor assignment。那么会发生什么(无论定义 m 和 __slots__ 的顺序如何?)当尝试分配时,set 方法不再映射到任何东西 - 也不是 get 但它回退到 MyClass.m
    【解决方案3】:

    考虑一下。

    class SuperSafe( object ):
        allowed= ( "this", "that" )
        def __init__( self ):
            self.this= None
            self.that= None
        def __setattr__( self, attr, value ):
            if attr not in self.allowed:
                raise Exception( "No such attribute: %s" % (attr,) )
            super( SuperSafe, self ).__setattr__( attr, value )
    

    更好的方法是使用单元测试来进行这种检查。这是相当大的运行时开销。

    【讨论】:

    • 但与 slots 相比,这是更多的代码行吗?为什么要手动检查语言可以自动执行的操作。
    • 为了避免“非常奇怪的错误”消息并避免花费大量时间调试 slots 的内部奥秘。我更喜欢明显而简单的解决方案,其中不需要秘密知识即可调试。
    • 无法添加新的实例变量是 slots 的副作用,而不是目的。 (这是一种性能优化)。
    • @Ravi — 我认为这是问题的核心。
    【解决方案4】:

    你完全在滥用__slots__。它阻止为实例创建__dict__。这仅在您遇到许多小对象的内存问题时才有意义,因为摆脱 __dict__ 可以减少占用空间。这是一项核心优化,在 99.9% 的情况下都不需要。

    如果您需要您所描述的那种安全性,那么 Python 确实是错误的语言。最好使用像 Java 这样严格的东西(而不是尝试用 Python 编写 Java)。

    如果您自己无法弄清楚为什么类属性会在您的代码中导致这些问题,那么也许您应该三思而后行地引入这样的语言技巧。首先熟悉该语言可能会更明智。

    为了完整起见,这里是documentation link for slots。

    【讨论】:

    • +1 表示完整性。正如到处提到的那样,slots 不是一个坏主意,但需要小心使用。它不仅仅是一项安全检查,而且会增加实现的复杂性,需要注意您如何使用对象。
    【解决方案5】:
    class MyClass( object ) :
        m = None # my attribute
    

    这里的m是类属性,而不是实例属性。您需要在__init__ 中自行将其与您的实例连接。

    【讨论】:

      猜你喜欢
      • 2011-08-11
      • 2021-05-07
      • 2011-11-08
      • 2014-10-20
      • 1970-01-01
      • 2018-02-16
      • 2013-03-05
      • 1970-01-01
      相关资源
      最近更新 更多