创建具有类型注释的Python类(通过编程方式)。

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

Create python class with type annotations programatically

问题

我希望能够像以下这样通过编程方式创建一个Python类

class Foo(BaseModel):
    bar: str = "baz"

以下几乎可以正常工作:

Foo = type("Foo", (BaseModel,), {"bar": "baz"})

但不包括注释,Foo.__annotations__ 在第一个示例中设置,但在第二个示例中没有设置。

有没有办法实现这一点?

我的动机是创建一个类装饰器,用于创建一个经过修改类型注释的已装饰类的克隆。注释必须在类创建时设置(而不是事后),以便BaseModel的元类能够看到它们。


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

I want to be able to create a python class like the following programmatically:

```python
class Foo(BaseModel):
    bar: str = &quot;baz&quot;

The following almost works:

Foo = type(&quot;Foo&quot;, (BaseModel,), {&quot;bar&quot;:&quot;baz})

But doesn't include the annotation, Foo.__annotations__ is set in first example but not the second.

Is there any way to achieve this?

My motivation is to create a class decorator that creates a clone of the decorated class with modified type annotations. The annotations have to be set during class creation (not after the fact) to that the metaclass of BaseModel will see them.

答案1

得分: 4

@Scarlet的评论让我意识到以下解决方案回答了我的问题。

```python
Foo = type(
    "Foo", 
    (BaseModel,), 
    {
        "bar": "baz", 
        "__annotations__": {"bar": str}
    }
)

尽管这解决了我动态调整pydantic类字段类型的激励问题,但需要重写元类的行为,如下所示:

from pydantic.main import ModelMetaclass

class OverrideModelMetaclass(ModelMetaclass):
    def __new__(cls, name, bases, attrs):
        attrs["__annotations__"] = {
            attribute_name: cls.__transform_annotation(annotation)
            for attribute_name, annotation in attrs["__annotations__"].items()
        }
        return super().__new__(cls, name, bases, attrs)

    @classmethod
    def __transform_annotation(cls, annotation):
        # 此方法映射属性注释
        return Override[annotation]

class Foo(BaseModel, metaclass=OverrideModelMetaclass):
    bar: str

上述代码等效于:

class Foo(BaseModel):
    bar: Override[str]

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

The comment from @Scarlet made me realise the following solution answers my question.

```python
Foo = type(
    &quot;Foo&quot;, 
    (BaseModel,), 
    {
        &quot;bar&quot;: &quot;baz, 
        &quot;__annotations__&quot;: {&quot;bar&quot;: str}
    }
)

Although it turns solving my motivating problem with dynamically tweaking field types on pydantic classes required overriding the behaviour of the metaclass like so:

from pydantic.main import ModelMetaclass

class OverrideModelMetaclass(ModelMetaclass):
    def __new__(cls, name, bases, attrs):
        attrs[&quot;__annotations__&quot;] = {
            attribute_name: cls.__transform_annotation(annotation)
            for attribute_name, annotation in attrs[&quot;__annotations__&quot;].items()
        }
        return super().__new__(cls, name, bases, attrs)

    @classmethod
    def __transform_annotation(cls, annotation):
        # this method maps over the attribute annotations
        return Override[annotation]


class Foo(BaseModel, metaclass=OverrideModelMetaclass):
    bar: str

The above is equivalent to:

class Foo(BaseModel):
    bar: Override[str]

huangapple
  • 本文由 发表于 2023年2月18日 21:41:38
  • 转载请务必保留本文链接:https://go.coder-hub.com/75493729.html
匿名

发表评论

匿名网友

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

确定