【问题标题】:How to decorate a class and use descriptors to access properties?如何装饰一个类并使用描述符来访问属性?
【发布时间】:2017-10-05 06:58:59
【问题描述】:

我正在尝试掌握(开始;))以了解如何在 Python 3 上正确使用装饰器和描述符。我想出了一个想法,我正在尝试弄清楚如何对其进行编码。

我希望能够创建一个装饰有特定“功能”B或“类”B的类A,让我能够在将 A 上的属性声明为特定类型的组件并在 A __init__ 魔术函数上分配值之后,创建 A 的实例。例如:

componentized 是某些“函数 B”或“类 B”,它允许我声明类 Vector。我将 xy 声明为 component(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

但主要目标是我想为每个标记的 component(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 类中以某种方式订阅浮点类型的 xy,所以当我执行 #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

要像描述符一样使用组件,但我无法设法解决该类型的问题。我试图做一个数组来保存 valtype,但后来不知道如何从装饰器 __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

(代码 cmets 是不言自明的)

通过这个设计我可以实现我想要的(上面解释过) 谢谢大家的帮助。

【问题讨论】:

  • 将对象分配给实例属性或更改它们是任何实例默认支持的普通任务。即使是练习,最好做一些更具体的事情。此外,如果您想管理实例创建(更改其属性、分配新属性等),您最好查看metaclasses。

标签: python python-3.x python-decorators magic-methods python-descriptors


【解决方案1】:

我认为有一个最简单的方法来实现这种行为:重载 __getattr____setattr__ 函数。

获取vector.xy:

class Vector:
    ...

    def __getattr__(self, item):
         return tuple(object.__getattribute__(self, char) for char in item)

__getattr__ 函数仅在访问属性的“正常”方式失败时调用,如Python documentation 中所述。 因此,当 python 找不到 vector.xy 时,会调用 __getattr__ 方法,我们返回每个值的元组(即 x 和 y)。
我们使用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 将分别分配,这显然不是你想要的!

【讨论】:

  • 谢谢@Diego,好吧,你提到的缺点对我来说并不重要,我只会使用一个 char 命名属性。这是一个很好的方法。但是,你将如何从装饰器函数或类中做到这一点?我的意思是,我想要装饰器类或函数componentized(我不知道它应该是一个类还是一个函数)将这个功能提供给类 Vector(或者我给它的任何类),并使用 component(float) 之类的东西来 "flag" 使用您提到的覆盖解决方案,并且仅使用我想要表现的属性(和给定的类型)。提前致谢
  • 也许你可以让你的 Vector 类继承一个“抽象”类,并覆盖我已经完成的 __getattr____setattr__ad。然后,您可以添加一些功能,例如“特殊”属性列表以更好地适应 setattr...如果您认为这是个好主意,我可以编辑我的答案以制作这样的课程。
  • 我不认为装饰器是这里的最佳选择
  • 我用您的建议编辑了我的问题,并尝试做其他事情,但无法完成!。 PS:@Diego,我不希望 Vector 或任何其他我希望此行为从抽象类继承的类。我要装饰一下!
  • 我不明白为什么你绝对希望装饰器来完成这项工作......以这种方式实现这种行为真的很难关于你必须记住的问题:实际上你的组件值将是相同的对于您的类的所有实例...
【解决方案2】:

FWIW,我通过 abusing __slots__ 做了类似的事情。我创建了一个抽象基类,它读取子类的插槽,然后将其用于酸洗(使用__getstate____setstate__)。你可以用 get-set-attr 做类似的事情,但你仍然需要处理类的实际 attr 与你想用作 get/set 属性的那些。


上一个答案:

为什么不直接使用@property decorator?请参阅文档中的第三个示例。您可以通过首先将 attr 名称更改为不同的名称和 private(例如 _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

如果您希望每个 attr 自动发生这种情况,那么您将需要使用元类,正如@kasramvd 评论的那样。如果您没有很多不同的类来执行此操作或很多属性,则可能不值得努力。

【讨论】:

  • 我想到了这个,但我不想为每个组合定义,因为我事先不知道它们!我只有xy和/或zrgb等。
  • 是的,组合的解决方法是@Diego 在他们的回答中提出的,__setattr__
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-07-10
  • 2019-10-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-11-26
  • 2019-08-01
相关资源
最近更新 更多