Дескрипторы в эффективном Python
Я читаю Пункт 31 в книге "Эффективный Питон". Я не понимаю, почему в примере на странице 97, почему math_grade
, writing_grade
а также science_grade
классовые (статические) переменные Exam
класс, а не обычные переменные экземпляра. Если они были переменными экземпляра, то 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 ответ
Я думаю, что хорошая ссылка в теме, доступной каждому, на самом деле является официальной документацией в этой статье.
Я настроил пример, но учтите, что в дескрипторах есть гораздо больше, и вы не должны использовать это, если не пишете каркас или некоторую библиотеку (например, 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***')