【问题标题】:While using property type for creating descriptor I am getting recursion error使用属性类型创建描述符时出现递归错误
【发布时间】:2019-02-14 12:40:20
【问题描述】:
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def fget(self):
        print("Getting ")
        return self.age

    def fset(self, value):
        print("Setting ")
        self.age = value

    def fdel(self):
        print("Deleting")
        del self.age

    age = property(fget, fset, fdel, "I'm the property.")

if __name__ == "__main__":

    p = Person("ankit", 29)

我正在尝试使用property 构造函数来创建描述符。我不想使用property 的装饰器形式。我收到错误提示

RecursionError: maximum recursion depth exceeded while calling a Python object

【问题讨论】:

    标签: python python-3.x properties


    【解决方案1】:

    object.__setattr__() 找到绑定描述符(使用__set__ 方法的描述符)时,它会调用它,而不是在实例的__dict__ 中设置名称:值对——这显然是xD 的预期结果。

    所以在你的情况下,当你实例化 Person:

    Person("ankit", 29) 
    

    Python 调用初始化器:

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

    调用Person.age.__set__(self, age),调用fset(self, value)

    def fset(self, value):
        print("Setting ")
        self.age = value
    

    调用Person.age.__set__(self, age),调用fset(self, value)

    def fset(self, value):
        print("Setting ")
        self.age = value
    

    调用Person.age.__set__(self, age),调用fset(self, value)

    def fset(self, value):
        print("Setting ")
        self.age = value
    

    调用Person.age.__set__(self, age),调用fset(self, value)

    def fset(self, value):
        print("Setting ")
        self.age = value
    

    调用Person.age.__set__(self, age),调用fset(self, value)

    def fset(self, value):
        print("Setting ")
        self.age = value
    

    等等等等等等……

    您不能对property 和基础实例属性使用相同的名称,因为property 将完全隐藏实例属性。

    通常,使用与property 同名的实现属性,只是前缀为一个前导下划线(Python 的“受保护属性”约定):

    class Person:
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
        def fget(self):
            print("Getting ")
            return self._age
    
        def fset(self, value):
            print("Setting ")
            self._age = value
    
        def fdel(self):
            print("Deleting")
            del self._age
    
        age = property(fget, fset, fdel, "I'm the property.")
    

    【讨论】:

    • 我喜欢随着新答案的发布,答案如何不断深入到坚韧不拔的细节中。非常+1。我希望接下来会看到一个进入 CPython 实现细节的内容:)
    • @MadPhysicist 我不打算进一步挖掘,抱歉 ;-)
    • 这是一个比我的答案更详细的答案.. +1 和最佳实践...
    【解决方案2】:

    方法中的代码在文件被导入时被转换为代码对象,但直到你真正调用该方法时才被执行。当您运行__init__ 时,age 已经是您的属性的名称。因此,self.age = age 行尝试调用fset,这是它应该的。但随后fset 尝试执行self.age = value,它只是再次调用fset,等等。

    问题是您的实例中没有名为 age 的可见属性,只有类中的描述符。这个问题有两种常见的解决方案。

    1. 看到最频繁的一个是将名为_age 的属性添加到self。虽然 Python 没有隐私,但开头的下划线礼貌地要求用户不要乱用它,除非他们想要意想不到的结果。

      在这种情况下,您的课程将如下所示:

      class Person:
          def __init__(self, name, age):
              self.name = name
              self._age = age
      
          def fget(self):
              print("Getting ")
              return self._age
      
          def fset(self, value):
              print("Setting ")
              self._age = value
      
          def fdel(self):
              print("Deleting")
              del self._age
      
          age = property(fget, fset, fdel, "I'm the property.")
      
    2. 我个人最喜欢做同样事情的方法是实际使用属性描述符隐藏实例属性这一事实。请记住,仅当您使用 . 表示法(或直接调用 __getattr__)时才会发生阴影。你仍然可以分配给self.__dict__ 就好了。这样就可以以不易看到或无法访问的方式保存特定于实例的数据,而无需向对象的命名空间添加任何名称:

      在这种情况下,您的课程将如下所示:

      class Person:
          def __init__(self, name, age):
              self.name = name
              self.__dict__['age'] = age
      
          def fget(self):
              print("Getting ")
              return self.__dict__['age']
      
          def fset(self, value):
              print("Setting ")
              self.__dict__['age'] = value
      
          def fdel(self):
              print("Deleting")
              del self.__dict__['age']
      
          age = property(fget, fset, fdel, "I'm the property.")
      

    【讨论】:

    • 我个人不会考虑使用属性解析的这个“特性”来隐藏属性值作为“最佳实践”,恰恰相反——实际上,如果我偶然发现这样的代码,我会立即着手将其重构为规范的_age 实现属性方案。维护代码已经很困难了,不必浪费时间在“聪明”的技巧上,这些技巧没有任何价值,只会使代码不明显(我自己也犯了很多这样的“聪明技巧”——经验教训:不要试图变得聪明)。 (注意:@mad-physicist 请不要冒犯 ;-))
    • @brunodesthuilliers。肯定没有拍。
    【解决方案3】:

    您需要更改property var 的名称...

    错误:

    RecursionError: 调用 Python 对象时超出最大递归深度

    这是因为你调用了property = age,所以递归发生在fset

    这是一个例子:

    class Person:
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
        def fget(self):
            print("Getting ")
            return self.age
    
        def fset(self, value):
            print("Setting ")
            self.age = value
    
        def fdel(self):
            print("Deleting")
            del self.age
    
        my_age = property(fget, fset, fdel, "I'm the property.")
    
    if __name__ == "__main__":
    
        p = Person("ankit", 29)
        print(p.my_age)
    

    这将打印:

    得到

    29

    【讨论】:

      猜你喜欢
      • 2020-01-27
      • 2020-06-07
      • 1970-01-01
      • 1970-01-01
      • 2020-06-30
      • 1970-01-01
      • 1970-01-01
      • 2022-11-13
      • 2020-06-16
      相关资源
      最近更新 更多