如何根据属性动态地为类 __init__ 函数提供参数?

问题描述 投票:0回答:1

dataclasses
pydantic
这样的工具如何为它们创建的类创建
__init__()
函数?

我知道我可以使用这些工具,但我想学习如何使用 python 元编程来解析我的类并根据属性及其类型创建动态初始化函数。

举个例子:

class MyClass:
  x: int
  y: str | None
  z: float = 0.1

  # I don't want to manually make this
  def __init__(self, x: int, y: str | None, z: float = 0.1):
    self.x = x
    self.y = y
    self.z = z

是否可以使用元类和类型注释做类似的事情?

python oop metaprogramming
1个回答
0
投票

主要有两种方法:

  1. 装饰器
  2. 元类

dataclass
的想法是,您需要以某种方式获取所有注释并从字符串手动build该方法(我将向您展示)。在这种情况下,您已经创建了类对象。

可用的工具有:

  • typing
    模块具有
    get_type_hints
    功能。
  • 您可以使用
    __dict__
    访问类的属性。
  • 您可以使用
    exec()
    动态创建对象。 (数据类确实是这样做的
from typing import get_type_hints


def my_dataclass(cls):
    fn_definition = "def __init__(self, "

    # building the first line of __init__
    for annotation, type_ in get_type_hints(cls).items():
        type_name = type_.__name__ if hasattr(type_, "__name__") else str(type_)
        fn_definition += f"{annotation}: {type_name}"
        if annotation in cls.__dict__:
            fn_definition += f" = {cls.__dict__[annotation]}"
        else:
            fn_definition += ", "
    fn_definition += "):\n"

    # building the body of __init__
    for annotation in get_type_hints(cls):
        fn_definition += f"\tself.{annotation} = {annotation}\n"
    print(fn_definition)

    # creating the function object:
    namespace = {}
    exec(fn_definition, {}, namespace)
    __init__ = namespace["__init__"]
    cls.__init__ = __init__
    return cls


@my_dataclass
class MyClass:
    x: int
    y: str | None
    z: float = 0.1


obj = MyClass(10, 20)
print(obj.x)
print(obj.y)
print(obj.z)

输出:

def __init__(self, x: int, y: str | None, z: float = 0.1):
        self.x = x
        self.y = y
        self.z = z

10
20
0.1

当然,这是一种幼稚的方法,并且存在一些问题,但它为您提供了该想法的概述。

dataclass
pydantic
(我将在后面展示)还做了很多其他事情。

Pydantic 使用的另一种方法是使用元类。不同之处在于,使用装饰器时,首先创建类,然后提取所需的信息,但在元类中,该信息可在

namespace
__new__
参数中获得,“在创建该类之前”:(我赢了不去实现了,因为概念和之前一样)

class MyMeta(type):
    def __new__(cls, name, bases, namespace):
        type_hints = namespace["__annotations__"]
        default_values = {k: namespace[k] for k in type_hints if k in namespace}
        print(type_hints)
        print(default_values)


class MyClass(metaclass=MyMeta):
    x: int
    y: str | None
    z: float = 0.1

输出:

{'x': 'int', 'y': 'str | None', 'z': 'float'}
{'z': 0.1}

现在您可以检查实际的源代码并跟踪它们的实现。

另请注意,

inspect
模块中有许多有用的功能,可让您在运行时检查对象的许多信息。

© www.soinside.com 2019 - 2024. All rights reserved.