如何导出对象的一些属性

Stu*_*ens 6 python properties decorator

我有一个 python 类,它有几个属性。我想实现一个方法,它将返回一些属性作为字典。我想用装饰器标记属性。这是一个例子:

class Foo:
    @export_to_dict  # I want to add this property to dict
    @property
    def bar1(self):
        return 1

    @property # I don't want to add this propetry to dict
    def bar2(self):
        return {"smth": 2}

    @export_to_dict # I want to add this property to dict
    @property
    def bar3(self):
        return "a"

    @property
    def bar4(self):
        return [2, 3, 4]

    def to_dict(self):
        return ... # expected result: {"bar1": 1, "bar3": "a"}
Run Code Online (Sandbox Code Playgroud)

实现它的一种方法是使用export_to_dict装饰器为属性设置一个附加属性,如下所示:

def export_to_dict(func):
    setattr(func, '_export_to_dict', True)
    return func
Run Code Online (Sandbox Code Playgroud)

并通过调用_export_to_dict时的属性来搜索属性。to_dict

还有其他方法可以完成任务吗?

Bop*_*reH 2

标记每个属性会强制to_dict在每次调用时扫描所有方法/属性,这是缓慢且不优雅的。这是一个独立的替代方案,可以使您的示例用法保持相同。

在类属性中保留导出属性的列表

通过创建export_to_dict一个类,我们可以使用__set_name__(Python 3.6+)来获取 Foo 类的引用并向其添加新属性。现在to_dict确切地知道要提取哪些属性,并且由于该列表是针对每个类的,因此您可以注释不同的类而不会发生冲突。

当我们这样做时,我们可以export_to_dict自动to_dict为任何具有导出属性的类生成函数。

装饰器还会在类创建后恢复原始属性,因此这些属性可以正常工作,不会对性能产生任何影响。

class export_to_dict:
    def __init__(self, property):
        self.property = property

    def __set_name__(self, owner, name):
        if not hasattr(owner, '_exported_properties'):
            owner._exported_properties = []
            assert not hasattr(owner, 'to_dict'), 'Class already has a to_dict method'
            owner.to_dict = lambda self: {prop.__name__: prop(self) for prop in owner._exported_properties}
        owner._exported_properties.append(self.property.fget)

        # We don't need the decorator object anymore, restore the property.
        setattr(owner, name, self.property)

class Foo:
    @export_to_dict  # I want to add this property to dict
    @property
    def bar1(self):
        return 1

    @property # I don't want to add this propetry to dict
    def bar2(self):
        return {"smth": 2}

    @export_to_dict # I want to add this property to dict
    @property
    def bar3(self):
        return "a"

    @property
    def bar4(self):
        return [2, 3, 4]

    # to_dict is not needed anymore here!

print(Foo().to_dict())
{'bar1': 1, 'bar3': 'a'}
Run Code Online (Sandbox Code Playgroud)

如果您不希望 Foo 类具有额外的属性,您可以将映射存储在静态 dict 中export_to_dict.properties_by_class = {class: [properties]}。


带有 setter 的属性

如果您需要支持属性设置者,情况会稍微复杂一些,但仍然可行。传递property.setter是不够的,因为 setter 替换了 getter 并且__set_name__不被调用(毕竟它们具有相同的名称)。

这可以通过拆分注释过程并为property.setter.

class export_to_dict:
    # Used to create setter for properties.
    class setter_helper:
        def __init__(self, setter, export):
            self.setter = setter
            self.export = export

        def __set_name__(self, owner, name):
            self.export.annotate_class(owner)
            setattr(owner, name, self.setter)

    def __init__(self, property):
        self.property = property

    @property
    def setter(self):
        return lambda fn: export_to_dict.setter_helper(self.property.setter(fn), self)

    def annotate_class(self, owner):
        if not hasattr(owner, '_exported_properties'):
            owner._exported_properties = []
            assert not hasattr(owner, 'to_dict'), 'Class already has a to_dict method'
            owner.to_dict = lambda self: {prop.__name__: prop(self) for prop in owner._exported_properties}
        owner._exported_properties.append(self.property.fget)
        
    def __set_name__(self, owner, name):
        self.annotate_class(owner)
        # We don't need the decorator object anymore, restore the property.
        setattr(owner, name, self.property)

class Foo:
    @export_to_dict  # I want to add this property to dict
    @property
    def writeable_property(self):
        return self._writeable_property

    @writeable_property.setter
    def writeable_property(self, value):
        self._writeable_property = value

foo = Foo()
foo.writeable_property = 5
print(foo.to_dict())
{'writeable_property': 5}
Run Code Online (Sandbox Code Playgroud)