如何导出对象的某些属性到字典中

huangapple go评论89阅读模式
英文:

How to export to dict some properties of an object

问题

我有一个Python类,其中包含几个属性。我想要实现一个方法,该方法将一些属性作为字典返回。我想要使用装饰器标记这些属性。以下是一个示例:

class Foo:
    @export_to_dict  # 我想要将此属性添加到字典中
    @property
    def bar1(self):
        return 1

    @property # 我不想将此属性添加到字典中
    def bar2(self):
        return {"smth": 2}

    @export_to_dict # 我想要将此属性添加到字典中
    @property
    def bar3(self):
        return "a"

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

    def to_dict(self):
        return ... # 期望的结果:{"bar1": 1, "bar3": "a"}

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

def export_to_dict(func):
    setattr(func, '_export_to_dict', True)
    return func

然后,在调用to_dict方法时,搜索具有_export_to_dict属性的属性。

是否有另一种完成任务的方法?

英文:

I have a python class which has several properties. I want to implement a method which will return some properties as a dict. I want to mark the properties with a decorator. Here's an example:

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"}

One way to implement it is to set an additional attribute to the properties with the export_to_dict decorator, like this:

def export_to_dict(func):
    setattr(func, '_export_to_dict', True)
    return func

and to search for properties with the _export_to_dict attribute when to_dict is called.

Is there another way to accomplish the task?

答案1

得分: 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)

        # 我们不再需要装饰器对象,恢复属性。
        setattr(owner, name, self.property)

class Foo:
    @export_to_dict  # 我想要将此属性添加到字典中
    @property
    def bar1(self):
        return 1

    @property # 我不想将此属性添加到字典中
    def bar2(self):
        return {"smth": 2}

    @export_to_dict # 我想要将此属性添加到字典中
    @property
    def bar3(self):
        return "a"

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

    # 不再需要 to_dict 方法!

print(Foo().to_dict())
{'bar1': 1, 'bar3': 'a'}

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


带有设置器的属性

如果需要支持属性设置器,情况会有点复杂,但仍然可行。仅传递 property.setter 是不够的,因为设置器替换了获取器,而 __set_name__ 不会被调用(它们具有相同的名称)。

这可以通过分离注释过程并为 property.setter 创建一个包装类来解决。

class export_to_dict:
    # 用于创建属性的设置器。
    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)
        # 我们不再需要装饰器对象,恢复属性。
        setattr(owner, name, self.property)

class Foo:
    @export_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}
英文:

Marking each property forces to_dict to scan all methods/attributes on each invocation, which is slow and inelegant. Here's a self-contained alternative that keeps your example usage identical.

Keep a list of exported properties in a class attribute

By making export_to_dict a class, we can use __set_name__ (Python 3.6+) to get a reference of the Foo class and add a new attribute to it. Now to_dict knows exactly which properties to extract, and because the list is per-class, you can annotate different classes without conflict.

And while we're at it, we can make export_to_dict automatically generate the to_dict function to any class that has exported properties.

The decorator also restores the original property after the class is created, so the properties work as normal without any performance impact.

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'}

If you don't want to your Foo class to have an extra attribute, you can store the mapping in a static dict export_to_dict.properties_by_class = {class: [properties]}.


Properties with setters

If you need to support property setters, the situation is a bit more complicated but still doable. Passing property.setter through is not sufficient, because the setter replaces the getter and __set_name__ is not called (they have the same name, after all).

This can be fixed by splitting the annotation process and creating a wrapper class for 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}

答案2

得分: 1

以下是您提供的内容的翻译:

免责声明:只有在EDIT 3EDIT 2中实现了完整的读/写协议。特别是,EDIT 2没有“副作用”[请参见我对_BoppreH_答案的评论]。

[EDIT 3] 自定义属性

如果需要完整的读/写/...协议,那么请制作您的自定义属性类。只需按照文档操作。

然后,使用自定义属性来跟踪您需要的那些方法,并对那些您不介意的方法使用默认的property

这里只是一个示例,请注意to_dict是一个属性而不是一个方法,只有在显式触发自定义属性后才可以访问它。

class PropertyTracker:

    PROPERTY_TRACKER_ID = 'to_dict'  # 它将成为目标类的属性(而不是方法!)

    def __init__(self, fget=None, fset=None, fdel=None, doc=None):
        self.fget = fget
        self.fset = fset
        self.fdel = fdel
        if doc is None and fget is not None:
            doc = fget.__doc__
        self.__doc__ = doc

    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        if self.fget is None:
            raise AttributeError("unreadable attribute")

        if not hasattr(obj, self.PROPERTY_TRACKER_ID):
            setattr(obj, self.PROPERTY_TRACKER_ID, {})

        value = self.fget(obj)
        getattr(obj, self.PROPERTY_TRACKER_ID).update({self.fget.__name__: value})
        return value

    def __set__(self, obj, value):
        if self.fset is None:
            raise AttributeError("can't set attribute")
        self.fset(obj, value)

        p_name = self.fget.__name__
        getattr(obj, self.PROPERTY_TRACKER_ID).update({p_name: value})

    def getter(self, fget):
        return type(self)(fget, self.fset, self.fdel, self.__doc__)

    def setter(self, fset):
        return type(self)(self.fget, fset, self.fdel, self.__doc__)


class Foo:

    @PropertyTracker  # 我想要将这个属性添加到字典中
    def bar1(self):
        return 1

    @property  # 我不想将这个属性添加到字典中
    def bar2(self):
        return {"smth": 2}

    @PropertyTracker  # 我想要将这个属性添加到字典中
    def bar3(self):
        return self._bar3

    @bar3.setter
    def bar3(self, v):
        self._bar3 = v

    @property
    def bar4(self):  # 我不想将这个属性添加到字典中
        return [2, 3, 4]


f = Foo()
# print(f.to_dict) # <- 将引发错误,"to_dict"尚不存在!
f.bar1  # <- 调用使用自定义属性装饰的方法后,"to_dict"将变为可用
f.bar2
print(f.to_dict)
# {'bar1': 1}
f.bar3 = 3
print(f.to_dict)
# {'bar1': 1, 'bar3': 3}
f.bar3 = 333
print(f.to_dict)
# {'bar1': 1, 'bar3': 333}

[EDIT 2] 类装饰器 + 组合方法装饰器装饰类属性,Python >= 3.8。

支持读/写协议,没有任何副作用。

初始化时间线

  1. 装饰器的__init__
  2. 装饰器的方法record
  3. 装饰器的__call__
  4. 更新dict
  5. 实例的设置器和获取器
class PropertyReader:
    # 添加“to_dict”方法和“_recorded_props”到目标类

    def __init__(self):
        self._recorded_props = {}

    def record(self, prop) -> property:
        self._recorded_props[prop.fget.__name__] = None  # 默认值
        return prop

    def __call__(self, target_cls):
        # 动态添加“to_dict”方法和“recorded_props”属性
        setattr(target_cls, self.to_dict.__name__, self.to_dict)
        setattr(target_cls, '_recorded_props', self._recorded_props)

        # 将获取器/设置器装饰应用于目标类
        for prop_name in self._recorded_props:
            self.decorated_descriptors(target_cls, prop_name)

        return target_cls

    @staticmethod
    def to_dict(target_cls) -> dict:
        # 不显示设置为None的属性
        return {k: v for k, v in target_cls._recorded_props.items() if v is not None}
        return target_cls._recorded_props  # 所有键值对,包括值为None的键值对

    @staticmethod
    def setter_wrapper(prop):
        # 应用于的设置器包装器
        def __setter_wrapper(target_cls, v):
            prop.fset(target_cls, v)
            target_cls._recorded_props.update({prop.fget.__name__: v})
        return __setter_wrapper

    @classmethod
    def decorated_descriptors(cls, target_cls, prop_name) -> None:
        # 向装饰的类添加新的获取器/设置器
        prop = getattr(target_cls, prop_name)
        if prop.fset:
            setattr(target_cls, prop_name, prop.setter(cls.setter_wrapper(prop)))
        else:
            target_cls._recorded_props.update({prop_name: prop.fget(target_cls)})


@(prop:=PropertyReader())  # 鲸鱼装饰
class Foo:

    @prop.record
    @property
    def bar1(self):
        return 1

    @property
    def bar2(self):
        return {"smth": 2}

    @prop.record
    @property
    def bar3(self):
        return self._bar3

    @bar3.setter
    def bar3(self, v):
        self._bar3 = v


f = Foo()
print(f.to_dict())
# {'bar1': 1}
f.bar1
f.bar3 = 333
print(f.to_dict())
# {'bar1': 1,

<details>
<summary>英文:</summary>

Disclaimer: Only in **EDIT 3** and **EDIT 2** a full read/write protocol is implemented. In particular, **EDIT 2** has no &quot;side effects&quot; [see my comments to _BoppreH_&#39;s answer].

----------

[EDIT 3] custom property

If a full read/write/... protocol is desired then make your _custom_ property class. Just follow the [doc](https://docs.python.org/3/howto/descriptor.html#properties).

Then use the _custom_ property to track those methods that you need and use the default `property` for those you don&#39;t mind.

Here just an example and notice the `to_dict` is an attribute not a method and it is accessible only once a _custom_ property is explicitly triggered.

class PropertyTracker:

PROPERTY_TRACKER_ID = &#39;to_dict&#39; # it will be an attribute (not a method!) of the target class
def __init__(self, fget=None, fset=None, fdel=None, doc=None):
self.fget = fget
self.fset = fset
self.fdel = fdel
if doc is None and fget is not None:
doc = fget.__doc__
self.__doc__ = doc
def __get__(self, obj, objtype=None):       
if obj is None:
return self
if self.fget is None:
raise AttributeError(&quot;unreadable attribute&quot;)        
if not hasattr(obj, self.PROPERTY_TRACKER_ID):
setattr(obj, self.PROPERTY_TRACKER_ID, {})
value = self.fget(obj)
getattr(obj, self.PROPERTY_TRACKER_ID).update({self.fget.__name__: value})
return value
def __set__(self, obj, value):
if self.fset is None:
raise AttributeError(&quot;can&#39;t set attribute&quot;)
self.fset(obj, value)
p_name =  self.fget.__name__
getattr(obj, self.PROPERTY_TRACKER_ID).update({p_name: value})
def getter(self, fget):
return type(self)(fget, self.fset, self.fdel, self.__doc__)
def setter(self, fset):
return type(self)(self.fget, fset, self.fdel, self.__doc__)

class Foo:

@PropertyTracker #    I want to add this property to dict
def bar1(self):
return 1
@property #         I don&#39;t want to add this propetry to dict
def bar2(self):
return {&quot;smth&quot;: 2}
@PropertyTracker #    I want to add this property to dict
def bar3(self):
return self._bar3
@bar3.setter
def bar3(self, v):
self._bar3 = v
@property
def bar4(self): #   I don&#39;t want to add this propetry to dict
return [2, 3, 4]

f = Foo()

print(f.to_dict) # <- will raise an error, "to_dict" doesn't exist yet!

f.bar1 # <- after calling a method decorated with the custom property the "to_dict" will become available
f.bar2
print(f.to_dict)
#{'bar1': 1}
f.bar3 = 3
print(f.to_dict)
#{'bar1': 1, 'bar3': 3}
f.bar3 = 333
print(f.to_dict)
#{'bar1': 1, 'bar3': 333}


----------
[EDIT 2] class decorator + combo method decorator decorating class properties, Python &gt;= 3.8.
Support read/write protocol _without_ any side-effects.
Initialisation time line
1. decorator&#39;s `__init__`
2. decorator&#39;s methods `record`s
3. decorator&#39;s `__call__`
4. update `dict`
5. instance&#39; setters and getters

class PropertyReader:
# add "to_dict" method and "_recorded_props" to the target class

def __init__(self):
self._recorded_props = {}
def record(self, prop) -&gt; property:
self._recorded_props[prop.fget.__name__] = None # default value
return prop
def __call__(self, target_cls):
# dynamically add &quot;to_dict&quot; method and &quot;recorded_props&quot; attr
setattr(target_cls, self.to_dict.__name__, self.to_dict)        
setattr(target_cls, &#39;_recorded_props&#39;, self._recorded_props)
# apply getter/setter decorations to the target class
for prop_name in self._recorded_props:
self.decorated_descriptors(target_cls, prop_name)
return target_cls
@staticmethod
def to_dict(target_cls) -&gt; dict:
# don&#39;t show the attributes set to None
return {k: v for k, v in target_cls._recorded_props.items() if v is not None}
return target_cls._recorded_props # all key-value pairs, also those with None
@staticmethod
def setter_wrapper(prop):
# setter wrapper to be applied to
def __setter_wrapper(target_cls, v):
prop.fset(target_cls, v)
target_cls._recorded_props.update({prop.fget.__name__: v})
return __setter_wrapper
@classmethod
def decorated_descriptors(cls, target_cls, prop_name) -&gt; None:
# add to the decorated class the new getter/setter
prop = getattr(target_cls, prop_name)
if prop.fset:
setattr(target_cls, prop_name, prop.setter(cls.setter_wrapper(prop)))
else:
target_cls._recorded_props.update({prop_name: prop.fget(target_cls)})

@(prop:=PropertyReader()) # walrus decoration
class Foo:

@prop.record
@property
def bar1(self):
return 1
@property
def bar2(self):
return {&quot;smth&quot;: 2}
@prop.record
@property
def bar3(self):
return self._bar3
@bar3.setter
def bar3(self, v):
self._bar3 = v

f = Foo()
print(f.to_dict())
#{'bar1': 1}
f.bar1
f.bar3 = 333
print(f.to_dict())
#{'bar1': 1, 'bar3': 333}
f.bar3 = 300
print(f.to_dict())
#{'bar1': 1, 'bar3': 300}
f.bar3 = 333
print(f.to_dict())
#{'bar1': 1, 'bar3': 333}


----------
[EDIT 1] class decorator with parameters decorating properties of a class.
The decorator dynamically adds an instance method `to_dict` to a class.

class PropertyReader:
# add "to_dict" method to a class

@staticmethod
def meta_to_dict(target_cls, prop_dict):
# contains a function that will be the method of the class 
def to_dict(self):
# method of the instance
return {k: prop(self) for k, prop in prop_dict.items()}
setattr(target_cls, to_dict.__name__, to_dict)
def __init__(self, *method_names):
self.method_names = method_names
def __call__(self, cls):
# filter attributes by property and identifier
props_dict = {} # dictionary of callable getters
for attr_name in dir(cls):
attr = getattr(cls, attr_name)
if isinstance(attr, property):
if attr_name in self.method_names:
props_dict[attr_name] = attr.fget # callable!
# bind method to the class
self.meta_to_dict(cls, props_dict)
return cls

@PropertyReader('bar1', 'bar3')
class Foo:
...

print(Foo().to_dict())


----------
[Original] Using a function to decorate a class.

def export_to_dict(*method_names):

def __wrapper(cls):
def meta_to_dict(prop_dict):
# contains a function that will be the method of the class 
def to_dict(self):
# method of the instance
return {k: prop(self) for k, prop in prop_dict.items()}
setattr(cls, to_dict.__name__, to_dict)
# filter attributes by property and identifier
props_dict = {}
for attr_name in dir(cls):
attr = getattr(cls, attr_name)
if isinstance(attr, property):
if attr_name in method_names:
props_dict[attr_name] = attr.fget # callable!
# bind method to the class
meta_to_dict(props_dict)
return cls
return __wrapper

@export_to_dict('bar1', 'bar3')
class Foo:
@property
def bar1(self):
return 1

@property # I don&#39;t want to add this propetry to dict
def bar2(self):
return {&quot;smth&quot;: 2}
@property
def bar3(self):
return &quot;a&quot;
def bar4(self):
return [2, 3, 4]

f = Foo()
print(f.to_dict())

{'bar1': 1, 'bar3': 'a'}


The decorator could be made &quot;prettier&quot; by using a class to avoid difficult to read nested functions&#39; declaration.
</details>
# 答案3
**得分**: 1
以下是翻译好的内容:
```python3
我个人会选择您最初的方法。我认为这是最干净的方法,涉及的未来维护工作最少:
import inspect
EXPORTABLE = "_exportable"
def export_to_dict(prop):
if prop.fget:
setattr(prop.fget, EXPORTABLE, True)
return prop
class Foo:
@export_to_dict
@property
def bar1(self):
return 1
@property
def bar2(self):
return {"smth": 2}
@export_to_dict
@property
def bar3(self):
return "a"
@property
def bar4(self):
return [2, 3, 4]
def to_dict(self):
statics = {inspect.getattr_static(self, prop) for prop in dir(self)}
props = {prop for prop in statics if isinstance(prop, property) and prop.fget is not None}
return {prop.fget.__name__: prop.fget(self) for prop in props if hasattr(prop.fget, EXPORTABLE)}
英文:

I'd personally go with your original approach. I think it's the cleanest and involves least amount of future maintenance:

import inspect

EXPORTABLE = &quot;_exportable&quot;

def export_to_dict(prop):
    if prop.fget:
        setattr(prop.fget, EXPORTABLE, True)
    return prop

class Foo:
    @export_to_dict
    @property
    def bar1(self):
        return 1

    @property
    def bar2(self):
        return {&quot;smth&quot;: 2}

    @export_to_dict
    @property
    def bar3(self):
        return &quot;a&quot;

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

    def to_dict(self):
        statics = {inspect.getattr_static(self, prop) for prop in dir(self)}
        props = {prop for prop in statics if isinstance(prop, property) and prop.fget is not None}
        return {prop.fget.__name__: prop.fget(self) for prop in props if hasattr(prop.fget, EXPORTABLE)}

答案4

得分: 0

如果使用 attrs 是您的选择,它可以实现一个非常简洁的解决方案:

import attrs

EXPORT_METADATA = '__export_metadata'

def my_field(default=attrs.NOTHING, validator=None, repr=True, eq=True, order=None, hash=None, init=True,
             metadata=None, converter=None, export=False):
    metadata = metadata or {}
    metadata[EXPORT_METADATA] = export
    return attrs.field(default=default, validator=validator, repr=repr, eq=eq, order=order, hash=hash, init=init,
                       metadata=metadata, converter=converter)

def export_filter(attribute, value):
    return attribute.metadata.get(EXPORT_METADATA, False)

@attrs.frozen
class Foo:
    bar1 = my_field(default=1, init=False, export=True)
    bar2 = my_field(default={"smth": 2}, init=False)
    bar3 = my_field(default="a", init=False, export=True)
    bar4 = my_field(default=[2, 3, 4], init=False)

    def to_dict(self):
        return attrs.asdict(self, filter=export_filter)

print(Foo().to_dict())

改编自 attrs 的 metadata example

英文:

If using attrs is an option for you, it enables a quite concise solution:

import attrs
EXPORT_METADATA = &#39;__export_metadata&#39;
def my_field(default=attrs.NOTHING, validator=None, repr=True, eq=True, order=None, hash=None, init=True,
metadata=None, converter=None, export=False):
metadata = metadata or {}
metadata[EXPORT_METADATA] = export
return attrs.field(default=default, validator=validator, repr=repr, eq=eq, order=order, hash=hash, init=init,
metadata=metadata, converter=converter)
def export_filter(attribute, value):
return attribute.metadata.get(EXPORT_METADATA, False)
@attrs.frozen
class Foo:
bar1 = my_field(default=1, init=False, export=True)
bar2 = my_field(default={&quot;smth&quot;: 2}, init=False)
bar3 = my_field(default=&quot;a&quot;, init=False, export=True)
bar4 = my_field(default=[2, 3, 4], init=False)
def to_dict(self):
return attrs.asdict(self, filter=export_filter)
print(Foo().to_dict())

Adapted from attrs's metadata example.

答案5

得分: 0

没有装饰器

无代码生成(每次调用都循环)

class Exportable:
    export_to_dict = ()

    def to_dict(self):
        return {prop: getattr(self, prop) for prop in self.export_to_dict}


class Foo(Exportable):
    export_to_dict = ["bar1", "bar2"]

    @property
    def bar1(self):
        return 1

    @property  # 我不想将此属性添加到字典中
    def bar2(self):
        return {"smth": 2}

    @property
    def bar3(self):
        return "a"

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


assert Foo().to_dict() == {"bar1": 1, "bar2": {"smth": 2}}

使用代码生成(没有额外循环)

用以下代码替换 Exportable

from convtools import conversion as c

class Exportable:
    export_to_dict = ()

    def to_dict(self):
        raise NotImplementedError

    def __init_subclass__(cls, **kwargs):
        cls.to_dict = c(
            {prop: c.attr(prop) for prop in cls.export_to_dict}
        ).gen_converter()

在底层,gen_converter 生成以下不包含循环的代码:

def converter(data_):
    try:
        return {"bar1": data_.bar1, "bar2": data_.bar2}
    except __exceptions_to_dump_sources:
        __convtools__code_storage.dump_sources()
        raise
英文:

Without decorators

No codegen (loop every call)

class Exportable:
    export_to_dict = ()

    def to_dict(self):
        return {prop: getattr(self, prop) for prop in self.export_to_dict}


class Foo(Exportable):
    export_to_dict = [&quot;bar1&quot;, &quot;bar2&quot;]

    @property
    def bar1(self):
        return 1

    @property  # I don&#39;t want to add this propetry to dict
    def bar2(self):
        return {&quot;smth&quot;: 2}

    @property
    def bar3(self):
        return &quot;a&quot;

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


assert Foo().to_dict() == {&quot;bar1&quot;: 1, &quot;bar2&quot;: {&quot;smth&quot;: 2}}

With codegen (no extra loop)

Replace Exportable with the following:

from convtools import conversion as c

class Exportable:
    export_to_dict = ()

    def to_dict(self):
        raise NotImplementedError

    def __init_subclass__(cls, **kwargs):
        cls.to_dict = c(
            {prop: c.attr(prop) for prop in cls.export_to_dict}
        ).gen_converter()

Under the hood, gen_converter generates the following code, which contains no loops:

def converter(data_):
    try:
        return {&quot;bar1&quot;: data_.bar1, &quot;bar2&quot;: data_.bar2}
    except __exceptions_to_dump_sources:
        __convtools__code_storage.dump_sources()
        raise

huangapple
  • 本文由 发表于 2023年6月29日 23:32:45
  • 转载请务必保留本文链接:https://go.coder-hub.com/76582545.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定