有没有一种方法可以使用@property装饰器来避免Python子类中的样板属性getter?

yzuktlbb  于 12个月前  发布在  Python
关注(0)|答案(3)|浏览(84)

我试图创建一些超类的子类,这些超类具有属性(由property装饰器描述)和一个properties()方法,该方法将属性及其值作为dict返回。我希望子类能够利用继承的properties()方法,并且需要将最少的样板代码复制并粘贴到每个子类定义中。我使用Python 3.8,如果它在这里有区别的话。
这里的重点是我想要重构的 * 样板 * 代码。
我从一个简单的类BaseClass开始,定义如下:

class BaseClass(object):
    def __init__(self, A, B):
        self._A = A
        self._B = B
    
    @property
    def A(self):
        return self._A
    
    @A.setter
    def A(self, new_value):
        # ... Validate input ...
        self._A = new_value

    @property
    def B(self):
        return self._B

    @B.setter
    def B(self, new_value):
        # ... Validate input ...
        self._B = new_value

    def properties(self):
        class_items = self.__class__.__dict__.items()
        return dict((k, getattr(self, k)) for k, v in class_items if isinstance(v, property))

我应该注意到,我在阅读this other stackoverflow answer之后,在最后定义了properties()方法。
如上定义的BaseClass的示例用法:

>>> base_instance = BaseClass('foo','bar')
>>> base_instance.properties()
{'A': 'foo', 'B': 'bar'}

从那里,我想创建一些继承自BaseClass的子类,并让properties()方法以相同的方式工作。考虑以下情况:

class SubClass(BaseClass):
    def __init__(self, A, B):
        super().__init__(A, B)

但这并不像我预期的那样:

>>> sub_instance = SubClass('spam',10)
>>> sub_instance.A  # works as expected
'spam'
>>> sub_instance.B  # works as expected
10
>>> sub_instance.properties()  # expected {'A': 'spam', 'B': 10}
{}

我知道下面的可选子类定义产生了我所期望的行为

class SubClass(BaseClass):
    def __init__(self, A, B):
        super().__init__(A, B)

    @BaseClass.A.getter    #
    def A(self):           #  This is all boilerplate
        return self._A     #  that I need to copy and
                           #  paste in every subclass
    @BaseClass.B.getter    #  of BaseClass I define...
    def B(self):           #
        return self._B     #
>>> sub_instance = SubClass('spam',10)
>>> sub_instance.properties()
{'A': 'spam', 'B': 10}

有没有一种更简洁的方法来定义超类BaseClass中的属性(使用property装饰器),以便properties()方法“正常工作”,而不需要样板代码行?

8yoxcaq7

8yoxcaq71#

你可以尝试使用inspect.getmro来获取所有的基类:

from inspect import getmro

class BaseClass(object):
    def __init__(self, A, B):
        self._A = A
        self._B = B

    @property
    def A(self):
        return self._A

    @A.setter
    def A(self, new_value):
        self._A = new_value

    @property
    def B(self):
        return self._B

    @B.setter
    def B(self, new_value):
        self._B = new_value

    def properties(self):
        out = []
        for klass in getmro(self.__class__):
            class_items = klass.__dict__.items()
            out.extend(
                dict(
                    (k, getattr(self, k))
                    for k, v in class_items
                    if isinstance(v, property)
                )
            )
        return out

class SubClass(BaseClass):
    def __init__(self, A, B):
        super().__init__(A, B)

    @property
    def C(self):
        return 42

sub_instance = SubClass("spam", 10)
print(sub_instance.properties())

图纸:

['C', 'A', 'B']
lkaoscv7

lkaoscv72#

您应该修复定义properties的方式,以遍历整个MRO:

def properties(self):
    klasses = reversed(type(self).mro())
    return {
        k: getattr(self, k) 
        for klass in klasses 
        for k, v in klass.items() 
        if isinstance(v, property)
    }
irtuqstp

irtuqstp3#

为了在一定程度上减少属性的模板代码,您可以创建函数,自动实现对指定内部变量的存储和检索。

def autoProperty(varName):
    g = dict()
    exec(f"def getProp(self): return self.{varName}",g)
    exec(f"def setProp(self,value): self.{varName} = value",g)
    return property(g["getProp"],g["setProp"])

使用这个函数,你可以用一行来定义读/写属性,当你需要对赋值进行特殊处理时,你可以把它用作装饰器(后缀为.setter):

class BaseClass:

    A = autoProperty("_A")
    
    @autoProperty("_B").setter
    def B(self,value):
        print("changing B to",value)
        self._B = value
        
    def __init__(self,A,B):
        self._A = A
        self._B = B

    def properties(self):
        return { name:getattr(self,name) for name in dir(self.__class__)
                 if isinstance(getattr(self.__class__,name),property) }

这将在properties()方法中生成属性列表。

base_instance = BaseClass('foo','bar')
print(base_instance.properties())

{'A': 'foo', 'B': 'bar'}
  • 请注意,我使用了dir()而不是__class__.__dict__,因为子类在它们的__dict__中不会有超类的属性名称 *

定义子类时,可以使用相同的方法添加更多属性:

class SubClass(BaseClass):

    C = autoProperty("_C")
    
    def __init__(self,A,B):
        super().__init__(A,B)
        self.C = A + "-" + B

properties()方法现在访问子类和超类的累积属性:

sub_instance = SubClass('spam','10')
print(sub_instance.properties())

{'A': 'spam', 'B': '10', 'C': 'spam-10'}

相关问题