python 数据类中字段元数据的用途是什么

arm*_*men 13 python metadata python-dataclasses

我一直在阅读python 数据类和其他网页的文档。字段元数据是只读的,文档说:

数据类根本不使用它,而是作为第三方扩展机制提供

我对第三方扩展将如何使用“只读映射中的值”感到困惑。这看起来像是以前会出现在软件文档中的内容?

我的目的是装饰我的字段,以便我知道是否计算了特定值(字段的一部分),我不认为这是可以通过元数据实现的,对吧?

Sar*_*ack 11

有些图书馆确实利用了元数据。例如,marshmallow-dataclass,一个非常流行的数据类验证库,允许您通过使用您自己定义的数据类中的元数据挂钩来安装自定义验证器方法以及其他一些东西。我想其他想要扩展数据类可用性的库也可以利用它。

是的,您认为您不想/不能使用元数据来跟踪字段是否已计算,这对我来说听起来是正确的。我认为您可以采取一些技巧来完成您想要的任务,但听起来您想要一个比数据类更有状态的类,并且它们的字段“应该”具有元数据功能。

由于映射代理是只读的,因此在实例化数据类时首次创建映射代理后,您没有太多能力对其进行更新。解决元数据字段只读限制的最灵活方法是让元数据字段本身指向数据类实例本身范围之外的内容。在这种情况下,只读包装器围绕您所指向的事物,而不是事物中的内容。或者,您也可以在运行时自己更新该类的整个元数据字段(这是允许的,因为元数据的内容是只读的,而不是元数据字段本身)。但是,如果您以更匿名的方式创建它,则无法在创建后更改元数据字段的内容。

例如:

from dataclasses import dataclass, field, fields


external_metadata_dict = {'is_initialized': False}
other_external_metadata_dict = {'foo': 1}

@dataclass
class UsesExternalDict:
    variable: int = field(metadata=external_metadata_dict)


example1 = UsesExternalDict(0)
print(f'example1 initial metadata: {fields(example1)[0].metadata}')
example2 = UsesExternalDict(0)
print(f'example2 initial metadata: {fields(example2)[0].metadata}')

# update the thing example1 and example2 both point to, even though their metadata is in a read-only wrapper
external_metadata_dict['is_initialized'] = True
print(f'example1 updated metadata: {fields(example1)[0].metadata}')
print(f'example2 updated metadata: {fields(example2)[0].metadata}')

# directly modifying the 'metadata' field also allowed
example3 = UsesExternalDict(0)
fields(example3)[0].metadata = other_external_metadata_dict
Run Code Online (Sandbox Code Playgroud)

给出

example1 initial metadata: {'is_initialized': False}
example2 initial metadata: {'is_initialized': False}
example1 updated metadata: {'is_initialized': True}
example2 updated metadata: {'is_initialized': True}
example3 initial metadata: {'is_initialized': True}
example3 updated metadata: {'foo': 1}
Run Code Online (Sandbox Code Playgroud)

但在大多数编程情况下,您最有可能想要这样做:

from dataclasses import dataclass, field, fields


@dataclass
class UsesInternalDict:
    variable: int = field(metadata={'is_initialized': False})

example = UsesInternalDict(0)
print(f'example initial metadata: {fields(example)[0].metadata}')

fields(example)[0].metadata['is_initialized'] = True
print(f'example updated metadata: {fields(example)[0].metadata}')
Run Code Online (Sandbox Code Playgroud)

只是给出

example initial metadata: {'is_initialized': False}
Traceback (most recent call last):
  File "/home/sinback/scrap.py", line 11, in <module>
    fields(example)[0].metadata['is_initialized'] = True
TypeError: 'mappingproxy' object does not support item assignment
Run Code Online (Sandbox Code Playgroud)

因为您正在尝试更新数据类的实际只读部分。

没有什么可以阻止您编写数据类的方法,该方法在运行时手动更新其一个字段的元数据部分,采用我在第一个示例中演示的方式之一,例如:

@dataclass
class UsesExternalDict:
    variable: int = field(metadata=external_metadata_dict)

    def messwith(self):
        self.__dataclass_fields__['variable'].metadata = other_external_metadata_dict
Run Code Online (Sandbox Code Playgroud)

但是......它在风格上确实不符合数据类应该如何工作。即使抛开风格问题,它也是一个非常脆弱和粗糙的解决方案 - 它利用了 Python 的一切都是参考语言的特性,这导致程序员总是编写错误。如果您与其他人一起工作,这将完全导致混乱。

您可以探索的另一个选择是让您跟踪自身的字段成为其自己的类,该类自行跟踪该元数据。对我来说,这对你来说是最好的选择,但这取决于你。