【问题标题】:getting pythons __set__ working让 python __set__ 工作
【发布时间】:2026-01-09 04:10:01
【问题描述】:

我只是想使用描述符模式,但似乎效果不佳。 这是一个简短的例子(没有任何实际用途,只是为了展示):

class Num(object):
  def__init__(self, val=0):
    self.val = val
  def __get__(self, instance, owner):
    return self.val
  def __set__(self, instance, val):
    self.val = val
  def __str__(self):
    return "Num(%s)" % self.val
  def __repr__(self):
    return self.__str__()

class Test(object):
  def __init__(self, num=Num()):
    self.num = num

和测试:

>>>t = Test()
>>>t.num # OK
Num(0)
>>>t.num + 3 #OK i know how to fix that, but I thought __get__.(t.num, t, Test) will be called
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'Num' and 'int'
>>> t.num = 4 # why isn't __set__(t.num, t, 4) called here?
>>> t.num
4

我的误解是什么?

【问题讨论】:

    标签: python python-2.7 descriptor magic-methods


    【解决方案1】:

    描述符仅在它们是类的属性时才起作用,而不是实例。如果您将班级更改为:

    class Test(object):
        num = Num()
    

    。 . .那么描述符就可以工作了。

    但是,由于必须在类上设置描述符,这意味着描述符只有一个实例,因此描述符将其值存储在self 上可能不是一个好主意。这些值将在类的所有实例之间共享。而是将值设置为 instance

    另外,请注意,您的 __str____repr__ 可能不会按照您的想法行事。调用t.num 将激活描述符并返回其val,因此t.num 的结果将是纯数字0,而不是Num 实例。描述符的全部意义在于透明地返回__get__ 的结果,而不会使描述符对象本身可见。

    以下是一些说明性示例:

    >>> t1 = Test()
    >>> t2 = Test()
    >>> t1.num
    0
    >>> Test.num
    0
    # Accessing the descriptor object itself
    >>> Test.__dict__['num']
    Num(0)
    >>> t1.num = 10
    >>> t1.num
    10
    # setting the value changed it everywhere
    >>> t2.num
    10
    >>> Test.num
    10
    

    使用替代版本的描述符:

    class Num(object):
      def __init__(self, val=0):
        self.val = val
    
      def __get__(self, instance, owner):
        try:
            return instance._hidden_val
        except AttributeError:
            # use self.val as default
            return self.val
    
      def __set__(self, instance, val):
        instance._hidden_val = val
    
    class Test(object):
        num = Num()
    
    >>> t1 = Test()
    >>> t2 = Test()
    >>> t1.num
    0
    >>> t1.num = 10
    >>> t1.num
    10
    # Now there is a separate value per instance
    >>> t2.num
    0
    

    【讨论】:

    • 谢谢,带有类属性的提示。我只是错过了那个细节。如果 repr(t.num) 不能按预期工作,至少 t.num + 3 会按预期工作,这是我在实际用例中想要的
    【解决方案2】:

    正如 BrenBarn 所说,描述符似乎用于类变量。您可能有兴趣查看 Python 的属性。

    class GenericItem(object):
        """Generic item descriptor"""
    
        def __init__(self, value=None, name=""):
            super().__init__()
    
            self.value = value
            self.name = name
        # end Constructor
    
        def __get__(self, obj, objtype):
    #         print(self, obj, objtype)
            return self.value
        # end __get__
    
        def __set__(self, obj, value):
    #         print(self, obj, value)
            self.value = value
        # end __set__
    
        def __str__(self):
            if self.name is None or self.name == "":
                return str(self.value)
            return self.name +"("+str(self.value)+")"
        # end __str__
    # end class Num
    
    class Test(object):
        def __init__(self, value=0):
            super().__init__()
    
            self._num = GenericItem(value, "Number")
        # end Constructor
    
        @property
        def num(self):
            """This is a number"""
            return self._num
        @num.setter
        def num(self, value):
            self._num.__set__(None, value)
        # end num property
    # end class Test
    
    if __name__ == "__main__":
        g = GenericItem(1, "Generic")
        print(g)
        g = 5
        print(g)
    
    
        t = Test()
        print(t.num)
        try:
            t.num + 3 # We didn't implement any sort of addition __add__
        except:
            pass
        t.num = 4
        print(t.num)
    

    结果:

    Generic(1)
    5
    Number(0)
    Number(4)
    

    属性有助于控制实例变量的设置方式。

    【讨论】: