Как украсить класс и использовать дескрипторы для доступа к свойствам?

Я пытаюсь освоить (начать;)), чтобы понять, как правильно работать с декораторами и дескрипторами на 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. Если у вас не так много разных классов, где вы хотите использовать это или много свойств, возможно, это не стоит затраченных усилий.

Другие вопросы по тегам