让蟒蛇__set__工作

时间:2014-11-20 00:05:06

标签: python python-2.7 descriptor magic-methods

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

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

我在这里有什么误解?

2 个答案:

答案 0 :(得分:5)

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

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

答案 1 :(得分:0)

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

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)

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