#python #properties #python-descriptors
Вопрос:
У меня есть класс со многими параметрами, которые хранятся в словаре, как показано ниже (на практике это немного сложнее, но это дает хорошую идею). Я могу «автоматически генерировать» методы получения и установки, используя partialmethod
:
for a_name in ['x', 'y', 'z']:
locals()["get_" a_name] = partialmethod(_get_arg,
arg_name=a_name)
locals()["set_" a_name] = partialmethod(_set_arg,
arg_name=a_name)
В идеале я хотел бы использовать @property
, но только если я смогу «автогенерировать» @property
, @setter
и @deleter
. Возможно ли это? Второй фрагмент показывает свойства, добавленные «вручную»; Я рассматриваю возможность использования эквивалента partialmethod
, чтобы избежать повторения и недостижимого кода.
class C:
def __init__(self):
self.kwargs = {'x' : 0, 'y' : 3, 'z' : True}
def __get_arg(self, arg_name):
assert arg_name in self.kwargs
return self.kwargs[arg_name]
def __set_arg(self, arg_name, value):
assert arg_name in self.kwargs
self.kwargs[arg_name] = value
def __del_arg(self, arg_name):
assert arg_name in self.kwargs
del self.kwargs[arg_name]
@property
def x(self):
return self.__get_arg('x')
@x.setter
def x(self, value):
self.__set_arg('x', value)
@x.deleter
def x(self):
self.__del_arg('x')
@property
def y(self):
return self.__get_arg('y')
@y.setter
def y(self, value):
self.__set_arg('y', value)
@y.deleter
def y(self):
self.__del_arg('y')
@property
def z(self):
return self.__get_arg('z')
@x.setter
def z(self, value):
self.__set_arg('z', value)
@x.deleter
def z(self):
self.__del_arg('z')
c = C()
c.x = 'foo' # setter called
foo = c.x # getter called
del c.x # deleter called
Ответ №1:
Вы можете написать свой собственный тип дескриптора и использовать __set_name__
метод, который будет вызван для него (механизмом создания класса), чтобы выяснить, на какое имя он был сохранен в классе:
class MyProp:
def __set_name__(self, owner, name):
self.name = name
def __get__(self, instance, owner=None):
if instance is None:
return self
return instance._get_arg(self.name)
def __set__(self, instance, value):
instance._set_arg(self.name, value)
def __delete__(self, instance):
instance._del_arg(self.name)
Вы бы использовали его таким образом:
class C:
# define __init__, _get_arg, etc.
x = MyProp()
y = MyProp()
z = MyProp()
Обратите внимание, что, поскольку это код из другого класса, вызывающий _X_arg
методы, вы, вероятно, не захотите искажать имена, поэтому я изменил __
префиксы только на один _
.
Комментарии:
1. Спасибо! будет ли myProp внутренним классом C? В
x = MyProp()
, было бы это больше похожеself.my_properties['x'] = C.MyProp(self, 'x')
на и т. Д.?2. Но мне все равно нужно создавать x = myProp(), y = myProp(), z = myProp() один за другим, я никак не могу создать их в цикле и по-прежнему использовать геттеры и сеттеры так, как я хочу, верно?
3. Я полагаю , вы могли бы определить
MyProp
внутриC
, но в этом нет необходимости. И вы могли бы использовать цикл, чтобы установить их, я полагаю, но я думаю, что это было бы намного уродливее и сложнее для понимания, чем то, что я показал. Вы можете рассмотреть возможность использования декоратора класса для добавления дескрипторов, хотя вам нужно будет самостоятельно задать имя, так__set_name__
как оно вызывается только во время создания класса. Метакласс также может работать (и будет вызываться__set_name__
автоматически), но тогда менее очевидно, как вы указываете список нужных вам имен свойств.4. @Bickknight Могу я спросить, в чем преимущество по
__ set_name__
сравнению__init__
с тем же содержимым? Не нужно передаватьname
в качестве параметра? Извините за задержку с ответом, пришлось срочно заняться чем-то другим.5. Да, тот факт, что это происходит автоматически, является главным преимуществом использования
__set_name__
. Если бы вы написали__init__
метод, который принимаетname
в качестве аргумента, вам нужно было бы написатьx = MyProp('x')
, что немного избыточно. Это могло бы лучше работать с альтернативными подходами к созданию атрибутов, но в своем ответе я хотел выделить подход с дескрипторами.