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

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

How to export to dict some properties of an object

问题

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

  1. class Foo:
  2. @export_to_dict # 我想要将此属性添加到字典中
  3. @property
  4. def bar1(self):
  5. return 1
  6. @property # 我不想将此属性添加到字典中
  7. def bar2(self):
  8. return {"smth": 2}
  9. @export_to_dict # 我想要将此属性添加到字典中
  10. @property
  11. def bar3(self):
  12. return "a"
  13. @property
  14. def bar4(self):
  15. return [2, 3, 4]
  16. def to_dict(self):
  17. return ... # 期望的结果:{"bar1": 1, "bar3": "a"}

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

  1. def export_to_dict(func):
  2. setattr(func, '_export_to_dict', True)
  3. 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:

  1. class Foo:
  2. @export_to_dict # I want to add this property to dict
  3. @property
  4. def bar1(self):
  5. return 1
  6. @property # I don't want to add this propetry to dict
  7. def bar2(self):
  8. return {"smth": 2}
  9. @export_to_dict # I want to add this property to dict
  10. @property
  11. def bar3(self):
  12. return "a"
  13. @property
  14. def bar4(self):
  15. return [2, 3, 4]
  16. def to_dict(self):
  17. 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:

  1. def export_to_dict(func):
  2. setattr(func, '_export_to_dict', True)
  3. 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 函数。

装饰器还会在创建类后还原原始属性,因此属性可以正常工作,而不会影响性能。

  1. class export_to_dict:
  2. def __init__(self, property):
  3. self.property = property
  4. def __set_name__(self, owner, name):
  5. if not hasattr(owner, '_exported_properties'):
  6. owner._exported_properties = []
  7. assert not hasattr(owner, 'to_dict'), 'Class already has a to_dict method'
  8. owner.to_dict = lambda self: {prop.__name__: prop(self) for prop in owner._exported_properties}
  9. owner._exported_properties.append(self.property.fget)
  10. # 我们不再需要装饰器对象,恢复属性。
  11. setattr(owner, name, self.property)
  12. class Foo:
  13. @export_to_dict # 我想要将此属性添加到字典中
  14. @property
  15. def bar1(self):
  16. return 1
  17. @property # 我不想将此属性添加到字典中
  18. def bar2(self):
  19. return {"smth": 2}
  20. @export_to_dict # 我想要将此属性添加到字典中
  21. @property
  22. def bar3(self):
  23. return "a"
  24. @property
  25. def bar4(self):
  26. return [2, 3, 4]
  27. # 不再需要 to_dict 方法!
  28. print(Foo().to_dict())
  29. {'bar1': 1, 'bar3': 'a'}

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


带有设置器的属性

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

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

  1. class export_to_dict:
  2. # 用于创建属性的设置器。
  3. class setter_helper:
  4. def __init__(self, setter, export):
  5. self.setter = setter
  6. self.export = export
  7. def __set_name__(self, owner, name):
  8. self.export.annotate_class(owner)
  9. setattr(owner, name, self.setter)
  10. def __init__(self, property):
  11. self.property = property
  12. @property
  13. def setter(self):
  14. return lambda fn: export_to_dict.setter_helper(self.property.setter(fn), self)
  15. def annotate_class(self, owner):
  16. if not hasattr(owner, '_exported_properties'):
  17. owner._exported_properties = []
  18. assert not hasattr(owner, 'to_dict'), 'Class already has a to_dict method'
  19. owner.to_dict = lambda self: {prop.__name__: prop(self) for prop in owner._exported_properties}
  20. owner._exported_properties.append(self.property.fget)
  21. def __set_name__(self, owner, name):
  22. self.annotate_class(owner)
  23. # 我们不再需要装饰器对象,恢复属性。
  24. setattr(owner, name, self.property)
  25. class Foo:
  26. @export_to_dict # 我想要将此属性添加到字典中
  27. @property
  28. def writeable_property(self):
  29. return self._writeable_property
  30. @writeable_property.setter
  31. def writeable_property(self, value):
  32. self._writeable_property = value
  33. foo = Foo()
  34. foo.writeable_property = 5
  35. print(foo.to_dict())
  36. {'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.

  1. class export_to_dict:
  2. def __init__(self, property):
  3. self.property = property
  4. def __set_name__(self, owner, name):
  5. if not hasattr(owner, '_exported_properties'):
  6. owner._exported_properties = []
  7. assert not hasattr(owner, 'to_dict'), 'Class already has a to_dict method'
  8. owner.to_dict = lambda self: {prop.__name__: prop(self) for prop in owner._exported_properties}
  9. owner._exported_properties.append(self.property.fget)
  10. # We don't need the decorator object anymore, restore the property.
  11. setattr(owner, name, self.property)
  12. class Foo:
  13. @export_to_dict # I want to add this property to dict
  14. @property
  15. def bar1(self):
  16. return 1
  17. @property # I don't want to add this propetry to dict
  18. def bar2(self):
  19. return {"smth": 2}
  20. @export_to_dict # I want to add this property to dict
  21. @property
  22. def bar3(self):
  23. return "a"
  24. @property
  25. def bar4(self):
  26. return [2, 3, 4]
  27. # to_dict is not needed anymore here!
  28. print(Foo().to_dict())
  29. {'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.

  1. class export_to_dict:
  2. # Used to create setter for properties.
  3. class setter_helper:
  4. def __init__(self, setter, export):
  5. self.setter = setter
  6. self.export = export
  7. def __set_name__(self, owner, name):
  8. self.export.annotate_class(owner)
  9. setattr(owner, name, self.setter)
  10. def __init__(self, property):
  11. self.property = property
  12. @property
  13. def setter(self):
  14. return lambda fn: export_to_dict.setter_helper(self.property.setter(fn), self)
  15. def annotate_class(self, owner):
  16. if not hasattr(owner, '_exported_properties'):
  17. owner._exported_properties = []
  18. assert not hasattr(owner, 'to_dict'), 'Class already has a to_dict method'
  19. owner.to_dict = lambda self: {prop.__name__: prop(self) for prop in owner._exported_properties}
  20. owner._exported_properties.append(self.property.fget)
  21. def __set_name__(self, owner, name):
  22. self.annotate_class(owner)
  23. # We don't need the decorator object anymore, restore the property.
  24. setattr(owner, name, self.property)
  25. class Foo:
  26. @export_to_dict # I want to add this property to dict
  27. @property
  28. def writeable_property(self):
  29. return self._writeable_property
  30. @writeable_property.setter
  31. def writeable_property(self, value):
  32. self._writeable_property = value
  33. foo = Foo()
  34. foo.writeable_property = 5
  35. print(foo.to_dict())
  36. {'writeable_property': 5}

答案2

得分: 1

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

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

[EDIT 3] 自定义属性

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

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

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

  1. class PropertyTracker:
  2. PROPERTY_TRACKER_ID = 'to_dict' # 它将成为目标类的属性(而不是方法!)
  3. def __init__(self, fget=None, fset=None, fdel=None, doc=None):
  4. self.fget = fget
  5. self.fset = fset
  6. self.fdel = fdel
  7. if doc is None and fget is not None:
  8. doc = fget.__doc__
  9. self.__doc__ = doc
  10. def __get__(self, obj, objtype=None):
  11. if obj is None:
  12. return self
  13. if self.fget is None:
  14. raise AttributeError("unreadable attribute")
  15. if not hasattr(obj, self.PROPERTY_TRACKER_ID):
  16. setattr(obj, self.PROPERTY_TRACKER_ID, {})
  17. value = self.fget(obj)
  18. getattr(obj, self.PROPERTY_TRACKER_ID).update({self.fget.__name__: value})
  19. return value
  20. def __set__(self, obj, value):
  21. if self.fset is None:
  22. raise AttributeError("can't set attribute")
  23. self.fset(obj, value)
  24. p_name = self.fget.__name__
  25. getattr(obj, self.PROPERTY_TRACKER_ID).update({p_name: value})
  26. def getter(self, fget):
  27. return type(self)(fget, self.fset, self.fdel, self.__doc__)
  28. def setter(self, fset):
  29. return type(self)(self.fget, fset, self.fdel, self.__doc__)
  30. class Foo:
  31. @PropertyTracker # 我想要将这个属性添加到字典中
  32. def bar1(self):
  33. return 1
  34. @property # 我不想将这个属性添加到字典中
  35. def bar2(self):
  36. return {"smth": 2}
  37. @PropertyTracker # 我想要将这个属性添加到字典中
  38. def bar3(self):
  39. return self._bar3
  40. @bar3.setter
  41. def bar3(self, v):
  42. self._bar3 = v
  43. @property
  44. def bar4(self): # 我不想将这个属性添加到字典中
  45. return [2, 3, 4]
  46. f = Foo()
  47. # print(f.to_dict) # <- 将引发错误,"to_dict"尚不存在!
  48. f.bar1 # <- 调用使用自定义属性装饰的方法后,"to_dict"将变为可用
  49. f.bar2
  50. print(f.to_dict)
  51. # {'bar1': 1}
  52. f.bar3 = 3
  53. print(f.to_dict)
  54. # {'bar1': 1, 'bar3': 3}
  55. f.bar3 = 333
  56. print(f.to_dict)
  57. # {'bar1': 1, 'bar3': 333}

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

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

初始化时间线

  1. 装饰器的__init__
  2. 装饰器的方法record
  3. 装饰器的__call__
  4. 更新dict
  5. 实例的设置器和获取器
  1. class PropertyReader:
  2. # 添加“to_dict”方法和“_recorded_props”到目标类
  3. def __init__(self):
  4. self._recorded_props = {}
  5. def record(self, prop) -> property:
  6. self._recorded_props[prop.fget.__name__] = None # 默认值
  7. return prop
  8. def __call__(self, target_cls):
  9. # 动态添加“to_dict”方法和“recorded_props”属性
  10. setattr(target_cls, self.to_dict.__name__, self.to_dict)
  11. setattr(target_cls, '_recorded_props', self._recorded_props)
  12. # 将获取器/设置器装饰应用于目标类
  13. for prop_name in self._recorded_props:
  14. self.decorated_descriptors(target_cls, prop_name)
  15. return target_cls
  16. @staticmethod
  17. def to_dict(target_cls) -> dict:
  18. # 不显示设置为None的属性
  19. return {k: v for k, v in target_cls._recorded_props.items() if v is not None}
  20. return target_cls._recorded_props # 所有键值对,包括值为None的键值对
  21. @staticmethod
  22. def setter_wrapper(prop):
  23. # 应用于的设置器包装器
  24. def __setter_wrapper(target_cls, v):
  25. prop.fset(target_cls, v)
  26. target_cls._recorded_props.update({prop.fget.__name__: v})
  27. return __setter_wrapper
  28. @classmethod
  29. def decorated_descriptors(cls, target_cls, prop_name) -> None:
  30. # 向装饰的类添加新的获取器/设置器
  31. prop = getattr(target_cls, prop_name)
  32. if prop.fset:
  33. setattr(target_cls, prop_name, prop.setter(cls.setter_wrapper(prop)))
  34. else:
  35. target_cls._recorded_props.update({prop_name: prop.fget(target_cls)})
  36. @(prop:=PropertyReader()) # 鲸鱼装饰
  37. class Foo:
  38. @prop.record
  39. @property
  40. def bar1(self):
  41. return 1
  42. @property
  43. def bar2(self):
  44. return {"smth": 2}
  45. @prop.record
  46. @property
  47. def bar3(self):
  48. return self._bar3
  49. @bar3.setter
  50. def bar3(self, v):
  51. self._bar3 = v
  52. f = Foo()
  53. print(f.to_dict())
  54. # {'bar1': 1}
  55. f.bar1
  56. f.bar3 = 333
  57. print(f.to_dict())
  58. # {'bar1': 1,
  59. <details>
  60. <summary>英文:</summary>
  61. 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].
  62. ----------
  63. [EDIT 3] custom property
  64. 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).
  65. Then use the _custom_ property to track those methods that you need and use the default `property` for those you don&#39;t mind.
  66. 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:

  1. PROPERTY_TRACKER_ID = &#39;to_dict&#39; # it will be an attribute (not a method!) of the target class
  2. def __init__(self, fget=None, fset=None, fdel=None, doc=None):
  3. self.fget = fget
  4. self.fset = fset
  5. self.fdel = fdel
  6. if doc is None and fget is not None:
  7. doc = fget.__doc__
  8. self.__doc__ = doc
  9. def __get__(self, obj, objtype=None):
  10. if obj is None:
  11. return self
  12. if self.fget is None:
  13. raise AttributeError(&quot;unreadable attribute&quot;)
  14. if not hasattr(obj, self.PROPERTY_TRACKER_ID):
  15. setattr(obj, self.PROPERTY_TRACKER_ID, {})
  16. value = self.fget(obj)
  17. getattr(obj, self.PROPERTY_TRACKER_ID).update({self.fget.__name__: value})
  18. return value
  19. def __set__(self, obj, value):
  20. if self.fset is None:
  21. raise AttributeError(&quot;can&#39;t set attribute&quot;)
  22. self.fset(obj, value)
  23. p_name = self.fget.__name__
  24. getattr(obj, self.PROPERTY_TRACKER_ID).update({p_name: value})
  25. def getter(self, fget):
  26. return type(self)(fget, self.fset, self.fdel, self.__doc__)
  27. def setter(self, fset):
  28. return type(self)(self.fget, fset, self.fdel, self.__doc__)

class Foo:

  1. @PropertyTracker # I want to add this property to dict
  2. def bar1(self):
  3. return 1
  4. @property # I don&#39;t want to add this propetry to dict
  5. def bar2(self):
  6. return {&quot;smth&quot;: 2}
  7. @PropertyTracker # I want to add this property to dict
  8. def bar3(self):
  9. return self._bar3
  10. @bar3.setter
  11. def bar3(self, v):
  12. self._bar3 = v
  13. @property
  14. def bar4(self): # I don&#39;t want to add this propetry to dict
  15. 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}

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

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

  1. def __init__(self):
  2. self._recorded_props = {}
  3. def record(self, prop) -&gt; property:
  4. self._recorded_props[prop.fget.__name__] = None # default value
  5. return prop
  6. def __call__(self, target_cls):
  7. # dynamically add &quot;to_dict&quot; method and &quot;recorded_props&quot; attr
  8. setattr(target_cls, self.to_dict.__name__, self.to_dict)
  9. setattr(target_cls, &#39;_recorded_props&#39;, self._recorded_props)
  10. # apply getter/setter decorations to the target class
  11. for prop_name in self._recorded_props:
  12. self.decorated_descriptors(target_cls, prop_name)
  13. return target_cls
  14. @staticmethod
  15. def to_dict(target_cls) -&gt; dict:
  16. # don&#39;t show the attributes set to None
  17. return {k: v for k, v in target_cls._recorded_props.items() if v is not None}
  18. return target_cls._recorded_props # all key-value pairs, also those with None
  19. @staticmethod
  20. def setter_wrapper(prop):
  21. # setter wrapper to be applied to
  22. def __setter_wrapper(target_cls, v):
  23. prop.fset(target_cls, v)
  24. target_cls._recorded_props.update({prop.fget.__name__: v})
  25. return __setter_wrapper
  26. @classmethod
  27. def decorated_descriptors(cls, target_cls, prop_name) -&gt; None:
  28. # add to the decorated class the new getter/setter
  29. prop = getattr(target_cls, prop_name)
  30. if prop.fset:
  31. setattr(target_cls, prop_name, prop.setter(cls.setter_wrapper(prop)))
  32. else:
  33. target_cls._recorded_props.update({prop_name: prop.fget(target_cls)})

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

  1. @prop.record
  2. @property
  3. def bar1(self):
  4. return 1
  5. @property
  6. def bar2(self):
  7. return {&quot;smth&quot;: 2}
  8. @prop.record
  9. @property
  10. def bar3(self):
  11. return self._bar3
  12. @bar3.setter
  13. def bar3(self, v):
  14. 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}

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

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

  1. @staticmethod
  2. def meta_to_dict(target_cls, prop_dict):
  3. # contains a function that will be the method of the class
  4. def to_dict(self):
  5. # method of the instance
  6. return {k: prop(self) for k, prop in prop_dict.items()}
  7. setattr(target_cls, to_dict.__name__, to_dict)
  8. def __init__(self, *method_names):
  9. self.method_names = method_names
  10. def __call__(self, cls):
  11. # filter attributes by property and identifier
  12. props_dict = {} # dictionary of callable getters
  13. for attr_name in dir(cls):
  14. attr = getattr(cls, attr_name)
  15. if isinstance(attr, property):
  16. if attr_name in self.method_names:
  17. props_dict[attr_name] = attr.fget # callable!
  18. # bind method to the class
  19. self.meta_to_dict(cls, props_dict)
  20. return cls

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

print(Foo().to_dict())

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

def export_to_dict(*method_names):

  1. def __wrapper(cls):
  2. def meta_to_dict(prop_dict):
  3. # contains a function that will be the method of the class
  4. def to_dict(self):
  5. # method of the instance
  6. return {k: prop(self) for k, prop in prop_dict.items()}
  7. setattr(cls, to_dict.__name__, to_dict)
  8. # filter attributes by property and identifier
  9. props_dict = {}
  10. for attr_name in dir(cls):
  11. attr = getattr(cls, attr_name)
  12. if isinstance(attr, property):
  13. if attr_name in method_names:
  14. props_dict[attr_name] = attr.fget # callable!
  15. # bind method to the class
  16. meta_to_dict(props_dict)
  17. return cls
  18. return __wrapper

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

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

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

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

  1. The decorator could be made &quot;prettier&quot; by using a class to avoid difficult to read nested functions&#39; declaration.
  2. </details>
  3. # 答案3
  4. **得分**: 1
  5. 以下是翻译好的内容:
  6. ```python3
  7. 我个人会选择您最初的方法。我认为这是最干净的方法,涉及的未来维护工作最少:
  8. import inspect
  9. EXPORTABLE = "_exportable"
  10. def export_to_dict(prop):
  11. if prop.fget:
  12. setattr(prop.fget, EXPORTABLE, True)
  13. return prop
  14. class Foo:
  15. @export_to_dict
  16. @property
  17. def bar1(self):
  18. return 1
  19. @property
  20. def bar2(self):
  21. return {"smth": 2}
  22. @export_to_dict
  23. @property
  24. def bar3(self):
  25. return "a"
  26. @property
  27. def bar4(self):
  28. return [2, 3, 4]
  29. def to_dict(self):
  30. statics = {inspect.getattr_static(self, prop) for prop in dir(self)}
  31. props = {prop for prop in statics if isinstance(prop, property) and prop.fget is not None}
  32. 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:

  1. import inspect
  2. EXPORTABLE = &quot;_exportable&quot;
  3. def export_to_dict(prop):
  4. if prop.fget:
  5. setattr(prop.fget, EXPORTABLE, True)
  6. return prop
  7. class Foo:
  8. @export_to_dict
  9. @property
  10. def bar1(self):
  11. return 1
  12. @property
  13. def bar2(self):
  14. return {&quot;smth&quot;: 2}
  15. @export_to_dict
  16. @property
  17. def bar3(self):
  18. return &quot;a&quot;
  19. @property
  20. def bar4(self):
  21. return [2, 3, 4]
  22. def to_dict(self):
  23. statics = {inspect.getattr_static(self, prop) for prop in dir(self)}
  24. props = {prop for prop in statics if isinstance(prop, property) and prop.fget is not None}
  25. return {prop.fget.__name__: prop.fget(self) for prop in props if hasattr(prop.fget, EXPORTABLE)}

答案4

得分: 0

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

  1. import attrs
  2. EXPORT_METADATA = '__export_metadata'
  3. def my_field(default=attrs.NOTHING, validator=None, repr=True, eq=True, order=None, hash=None, init=True,
  4. metadata=None, converter=None, export=False):
  5. metadata = metadata or {}
  6. metadata[EXPORT_METADATA] = export
  7. return attrs.field(default=default, validator=validator, repr=repr, eq=eq, order=order, hash=hash, init=init,
  8. metadata=metadata, converter=converter)
  9. def export_filter(attribute, value):
  10. return attribute.metadata.get(EXPORT_METADATA, False)
  11. @attrs.frozen
  12. class Foo:
  13. bar1 = my_field(default=1, init=False, export=True)
  14. bar2 = my_field(default={"smth": 2}, init=False)
  15. bar3 = my_field(default="a", init=False, export=True)
  16. bar4 = my_field(default=[2, 3, 4], init=False)
  17. def to_dict(self):
  18. return attrs.asdict(self, filter=export_filter)
  19. print(Foo().to_dict())

改编自 attrs 的 metadata example

英文:

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

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

Adapted from attrs's metadata example.

答案5

得分: 0

没有装饰器

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

  1. class Exportable:
  2. export_to_dict = ()
  3. def to_dict(self):
  4. return {prop: getattr(self, prop) for prop in self.export_to_dict}
  5. class Foo(Exportable):
  6. export_to_dict = ["bar1", "bar2"]
  7. @property
  8. def bar1(self):
  9. return 1
  10. @property # 我不想将此属性添加到字典中
  11. def bar2(self):
  12. return {"smth": 2}
  13. @property
  14. def bar3(self):
  15. return "a"
  16. @property
  17. def bar4(self):
  18. return [2, 3, 4]
  19. assert Foo().to_dict() == {"bar1": 1, "bar2": {"smth": 2}}

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

用以下代码替换 Exportable

  1. from convtools import conversion as c
  2. class Exportable:
  3. export_to_dict = ()
  4. def to_dict(self):
  5. raise NotImplementedError
  6. def __init_subclass__(cls, **kwargs):
  7. cls.to_dict = c(
  8. {prop: c.attr(prop) for prop in cls.export_to_dict}
  9. ).gen_converter()

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

  1. def converter(data_):
  2. try:
  3. return {"bar1": data_.bar1, "bar2": data_.bar2}
  4. except __exceptions_to_dump_sources:
  5. __convtools__code_storage.dump_sources()
  6. raise
英文:

Without decorators

No codegen (loop every call)

  1. class Exportable:
  2. export_to_dict = ()
  3. def to_dict(self):
  4. return {prop: getattr(self, prop) for prop in self.export_to_dict}
  5. class Foo(Exportable):
  6. export_to_dict = [&quot;bar1&quot;, &quot;bar2&quot;]
  7. @property
  8. def bar1(self):
  9. return 1
  10. @property # I don&#39;t want to add this propetry to dict
  11. def bar2(self):
  12. return {&quot;smth&quot;: 2}
  13. @property
  14. def bar3(self):
  15. return &quot;a&quot;
  16. @property
  17. def bar4(self):
  18. return [2, 3, 4]
  19. 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:

  1. from convtools import conversion as c
  2. class Exportable:
  3. export_to_dict = ()
  4. def to_dict(self):
  5. raise NotImplementedError
  6. def __init_subclass__(cls, **kwargs):
  7. cls.to_dict = c(
  8. {prop: c.attr(prop) for prop in cls.export_to_dict}
  9. ).gen_converter()

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

  1. def converter(data_):
  2. try:
  3. return {&quot;bar1&quot;: data_.bar1, &quot;bar2&quot;: data_.bar2}
  4. except __exceptions_to_dump_sources:
  5. __convtools__code_storage.dump_sources()
  6. 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:

确定