Как украсить класс и использовать дескрипторы для доступа к свойствам?
Я пытаюсь освоить (начать;)), чтобы понять, как правильно работать с декораторами и дескрипторами на Python 3. Мне пришла в голову мысль, что я пытаюсь понять, как его кодировать.
Я хочу иметь возможность создать класс A, украшенный определенной "функцией" B или "классом" B, который позволяет мне создавать экземпляр A после деления свойств на A как компонента определенного типа и присвоения значений на A __init__
магическая функция. Например:
Componenttized это некая "функция B" или "класс B", который позволяет мне объявить класс Vector. Я объявляю x и y компонентом (float) следующим образом:
@componentized
class Vector:
x = component(float)
y = component(float)
def __init__ (self, x, y):
self.x = x
self.y = y
Я имею в виду, чтобы быть в состоянии это:
v = Vector(1,2)
v.x #returns 1
Но главная цель состоит в том, чтобы я хотел сделать это для каждого помеченного компонента (float):
v.xy #returns a tuple (1,2)
v.xy = (3,4) #assigns to x the value 3 and y the value 4
Моя идея - создать декоратор @componentized, который переопределяет __getattr__
а также __setattr__
магические методы. Вроде этого:
def componentized(cls):
class Wrapper(object):
def __init__(self, *args):
self.wrapped = cls(*args)
def __getattr__(self, name):
print("Getting :", name)
if(len(name) == 1):
return getattr(self.wrapped, name)
t = []
for x in name:
t.append(getattr(self.wrapped, x))
return tuple(t)
@componentized
class Vector(object):
def __init__(self, x, y):
self.x = x
self.y = y
И это отчасти сработало, но я не думаю, что понял, что произошло. Причина, когда я пытался сделать назначение и переопределить __setattr__
магический метод, который вызывается, даже когда я создаю экземпляр класса. Два раза в следующем примере:
vector = Vector(1,2)
vector.x = 1
Как бы я мог достичь такого поведения?
Заранее спасибо! Если вам нужна дополнительная информация, не стесняйтесь спрашивать!
РЕДАКТИРОВАТЬ:
После ответа @Diego мне удается это сделать:
def componentized(cls):
class wrappedClass(object):
def __init__(self, *args, **kwargs):
t = cls(*args,**kwargs)
self.wrappedInstance = t
def __getattr__(self, item):
if(len(item) == 1):
return self.wrappedInstance.__getattribute__(item)
else:
return tuple(self.wrappedInstance.__getattribute__(char) for char in item)
def __setattr__(self, attributeName, value):
if isinstance(value, tuple):
for char, val in zip(attributeName, value):
self.wrappedInstance.__setattr__(char, val)
elif isinstance(value, int): #EMPHASIS HERE
for char in attributeName:
self.wrappedInstance.__setattr__(char, value)
else:
object.__setattr__(self, attributeName, value)
return wrappedClass
И имея класс Vector вот так:
@componentized
class Vector:
def __init__ (self, x, y):
self.x = x
self.y = y
Это ведет себя как я хотел, но я до сих пор не знаю, как достичь:
x = component(float)
y = component(float)
внутри класса Vector, чтобы как-то подписаться на x и y типа float, поэтому, когда я делаю #EMPHASIS LINE(в строке, я жестко закодировал определенный тип) в коде, я могу проверить, присваивает ли кто-то значение значению x и / или y для экземпляра Vector имеет тип, который я определил с помощью:
x = component(float)
Итак, я попробовал это (класс компонента (дескриптора)):
class component(object):
def __init__(self, t, initval=None):
self.val = initval
self.type = t
def __get__(self, obj, objtype):
return self.val
def __set__(self, obj, val):
self.val = val
Использовать компонент как дескриптор, но мне не удалось сделать обходной путь для обработки типа. Я пытался создать массив для хранения val и type, но потом не знал, как получить его из метода __setattr__ декоратора.
Можете ли вы указать мне правильное направление?
PS: Надеюсь, вы, ребята, понимаете, что я пытаюсь сделать, и помогите мне. заранее спасибо
Решение
Хорошо, используя ответ @Diego (который я буду принимать) и некоторые обходные пути для достижения моих личных потребностей, мне удалось это:
Декоратор (компонентный)
def componentized(cls):
class wrappedClass(object):
def __init__(self, *args):
self.wrappedInstance = cls(*args)
def __getattr__(self, name):
#Checking if we only request for a single char named value
#and return the value using getattr() for the wrappedInstance instance
#If not, then we return a tuple getting every wrappedInstance attribute
if(len(name) == 1):
return getattr(self.wrappedInstance, name)
else:
return tuple(getattr(self.wrappedInstance, char) for char in name)
def __setattr__(self, attributeName, value):
try:
#We check if there is not an instance created on the wrappedClass __dict__
#Meaning we are initializing the class
if len(self.__dict__) == 0:
self.__dict__[attributeName] = value
elif isinstance(value, tuple): # We get a Tuple assign
self.__checkMultipleAssign(attributeName)
for char, val in zip(attributeName, value):
setattr(self.wrappedInstance, char, val)
else:
#We get a value assign to every component
self.__checkMultipleAssign(attributeName)
for char in attributeName:
setattr(self.wrappedInstance, char, value)
except Exception as e:
print(e)
def __checkMultipleAssign(self, attributeName):
#With this we avoid assigning multiple values to the same property like this
# instance.xx = (2,3) => Exception
for i in range(0,len(attributeName)):
for j in range(i+1,len(attributeName)):
if attributeName[i] == attributeName[j]:
raise Exception("Multiple component assignment not allowed")
return wrappedClass
компонент (класс дескриптора)
class component(object):
def __init__(self, t):
self.type = t #We store the type
self.value = None #We set an initial value to None
def __get__(self, obj, objtype):
return self.value #Return the value
def __set__(self, obj, value):
try:
#We check whether the type of the component is diferent to the assigned value type and raise an exeption
if self.type != type(value):
raise Exception("Type \"{}\" do not match \"{}\".\n\t--Assignation never happened".format(type(value), self.type))
except Exception as e:
print(e)
else:
#If the type match we set the value
self.value = value
(Комментарии к коду говорят сами за себя)
С этим дизайном я могу добиться того, чего хотел (объяснено выше). Спасибо вам всем за помощь.
2 ответа
Я думаю, что есть простейший способ добиться такого поведения: перегрузка __getattr__
а также __setattr__
функции.
Получаем vector.xy:
class Vector:
...
def __getattr__(self, item):
return tuple(object.__getattribute__(self, char) for char in item)
__getattr__
Функция вызывается только в случае сбоя "нормального" способа доступа к атрибуту, как указано в документации по Python. Итак, когда питон не находит vector.xy
, __getattr__
Метод вызывается, и мы возвращаем кортеж каждого значения (т. е. х и у).
Мы используем object.__getattribute__
чтобы избежать бесконечного повторения.
Установка vector.abc:
def __setattr__(self, key, value):
if isinstance(value, tuple) and len(key) == len(value):
for char, val in zip(key, value):
object.__setattr__(self, char, val)
else:
object.__setattr__(self, key, value)
__setattr__
метод всегда вызывается в отличие __getattr__
поэтому мы устанавливаем каждое значение отдельно только тогда, когда элемент, который мы хотим установить, имеет ту же длину, что и кортеж значения.
>>> vector = Vector(4, 2)
>>> vector.x
4
>>> vector.xy
(4, 2)
>>> vector.xyz = 1, 2, 3
>>> vector.xyxyxyzzz
(1, 2, 1, 2, 1, 2, 3, 3, 3)
Единственным недостатком является то, что если вы действительно хотите назначить кортеж как (предположим, у вас есть атрибут с именем size
):
vector.size = (1, 2, 3, 4)
Тогда s, i, z и e будут назначены отдельно, и это, очевидно, не то, что вы хотите!
FWIW, я сделал что-то подобное, злоупотребляя __slots__
, Я создал абстрактный базовый класс, который считывал слоты подкласса, а затем использовал его для травления (с __getstate__
а также __setstate__
). Вы можете сделать что-то похожее с get-set-attr, но вам все равно придется поэкспериментировать с фактическим attr класса против тех, которые вы хотите использовать в качестве свойств get / set.
Предыдущий ответ:
Почему бы просто не использовать @property
декоратор? Смотрите третий пример в документации. Вы бы применили его, сначала изменив имена attr на что-то другое и частное (например, _x
), а затем использовать фактическое имя x
как собственность.
class Vector(object):
def __init__(self, x, y):
self._x = x
self._y = y
@property
def x(self):
return self._x
@x.setter
def x(self, value):
self._x = value
@property
def xy(self):
return (self._x, self._y) # returns a tuple
@xy.setter
def xy(self, value):
self._x, self._y = value # splits out `value` to _x and _y
И если вы хотите, чтобы это происходило с каждым атрибутом автоматически, вам нужно будет использовать метакласс, как прокомментировал @kasramvd. Если у вас не так много разных классов, где вы хотите использовать это или много свойств, возможно, это не стоит затраченных усилий.