有效Python中的描述符

时间:2018-03-27 15:34:25

标签: python python-descriptors

我正在阅读“有效的Python”一书中的第31项。我不明白为什么在第97页的示例中,为什么math_gradewriting_gradescience_gradeExam类的类(静态)变量,而不是常规的,实例变量。如果它们是实例变量,则Grade类不需要在其全局簿记字典中使用实例作为键。在我看来,就像作者提出一个明显的设计错误只是为了说明如何使用描述符,即在Grade类中的全球簿记,这似乎是一个坏主意。

我的另一个问题是更高层次的问题:这不是一个令人困惑,不清楚的做事方式吗?将多个对象的全局状态保存在单个注册表中,例如Grade。对我来说,似乎不是一个可重复使用,干净的设计。

以下是对没有这本书的人的代码参考:

https://github.com/SigmaQuan/Better-Python-59-Ways/blob/master/item_31_use_descriptors.py

具体地

 class Grade(object):
     def __get__(*args, **kwargs):
          super().__getattribute__(*args, **kwargs)

     def __set__(*args, **kwargs):
          super().__setattr__(args, kwargs)


 class Exam(object):
     math_grade = Grade()
     writing_grade = Grade()
     science_grade = Grade()

1 个答案:

答案 0 :(得分:0)

我认为每个人都可以使用这个主题的一个很好的参考,实际上是这个Descriptors How To

中的官方文档

我设置了一个示例,但请注意,有很多关于描述符的内容,除非编写需要动态实例化和验证不同字段的框架或某个库(如ORM),否则不应该使用它。例如。

对于通常的验证需求,请将自己限制为属性装饰器。

class PositionX: # from 0 to 1000
    def __init__(self, x):
        self.x = x

print('***Start***')
print()
print('Original PositionX class')
pos1 = PositionX(50)
print(pos1.x)
pos1.x = 100
print(pos1.x)
pos1.x = -10
print(pos1.x)
print()


# let's validate x with a property descriptor, using @property

class PositionX: # from 0 to 1000
    def __init__(self, position):
        self.x = position

    @property
    def x(self):
        return self._x

    @x.setter
    def x(self, value):
        if 0 <= value <= 1000:
            self._x = value
        else:
            raise ValueError

print('PositionX attribute x validated with @property')
pos2 = PositionX(50)
print(pos2.x)
pos2.x = 100
print(pos2.x)
try:
    pos2.x = -10
except ValueError:
    print("Can't set x to -10")
print()

# Let's try instead to use __set__ and __get__ in the original class
# This is wrong and won't work. This makes the class PositionX a descriptor,
# while we wanted to protect x attribute of PositionX with the descriptor.

class PositionX: # from 0 to 1000
    def __init__(self, x):
        self.x = x

    def __get__(self, instance):
        print('Running __get__')
        return self._x

    def __set__(self, instance, value):
        print('Running __set__')
        if 0 <= value <= 1000:
            self._x = value
        else:
            raise ValueError

print("Using __set__ and __get__ in the original class. Doesn't work.")
print("__get__ and __set__ don't even run because x is found in the pos3 instance and there is no descriptor object by the same name in the class.")

pos3 = PositionX(50)
print(pos3.x)
pos3.x = 100
print(pos3.x)
try:
    pos3.x = -10
except ValueError:
    print("Can't set x to -10")
print(pos3.x)
print()

# Let's define __set__ and __get__ to validate properties like x
# (with the range 0 to 1000). This actually makes the class Range0to1000
# a data descriptor. The instance dictionary of the managed class PositionX
# is always overrided by the descriptor.

# This works because now on x attribute reads and writes of a PositionX
# instance the __get__ or __set__ descriptor methods are always run.

# When run they get or set the PositionX instance __dict__ to bypass the
# trigger of descriptor __get__ or __set__ (again)

class Range0to1000:
    def __init__(self, name): # the property name, 'x', 'y', whatever
        self.name = name
        self.value = None

    def __get__(self, instance, managed_class):
        print('Running __get__')
        return instance.__dict__[self.name]
        # same as getattr(instance, self.name) but doesn't trigger
        # another call to __get__ leading to recursion error

    def __set__(self, instance, value):
        print('Running __set__')
        if 0 <= value <= 1000:
            instance.__dict__[self.name] = value
            # same as setattr(instance, self.name, self.value) but doesn't
            # trigger another call to __set__ leading to recursion error
        else:
            raise ValueError

class PositionX: # holds a x attribute from 0 to 1000

    x = Range0to1000('x') # no easy way to avoid passing the name string 'x'
                          # but now you can add many other properties
                          # sharing the same validation code
#   y = Range0to1000('y')
#   ...

    def __init__(self, x):
        self.x = x

print("Using a descriptor class to validate x.")

pos4 = PositionX(50)
print(pos4.x)
pos4.x = 100
print(pos4.x)
try:
    pos4.x = -10
except ValueError:
    print("Can't set x to -10")
print(pos4.x)
print()
print('***End***')