在 post_init 中创建的数据类的属性的访问类型提示

Posted

技术标签:

【中文标题】在 post_init 中创建的数据类的属性的访问类型提示【英文标题】:Access Type Hints for attributes of a dataclass created in post_init 【发布时间】:2021-09-01 08:45:50 【问题描述】:

Python:3.7+

我有一个数据类及其子类,如下所示:

from abc import ABC
from dataclasses import dataclass
from typing import Dict, List, Optional

from dbconn import DBConnector


@dataclass
class User:
  uid: int
  name: str


@dataclass
class Model(ABC):
  database: DBConnector
  user: User

  def func(self, *args, **kwargs):
    pass


@dataclass
class Command(Model):
  message: Optional[str] = "Hello"

  def __post_init__(self):
    self.user_id: str = str(self.user.uid)
    self.message = f"self.user.name: self.message"

我可以使用typing.get_type_hints(Command) 获得databaseusermessage 的类型提示。 如何获得user_id 的类型提示?

一种解决方法是将 user.uid 和 user.name 作为单独的参数传递给 Command,但当 User 对象具有许多有用的属性时,这并不实用。

我认为它首先不起作用的原因是因为 init 在运行时被调用,这就是类型检查不考虑这些属性的原因。一种可能的解决方案是解析类的 ast,但我不确定这是否推荐和足够通用。如果是,将不胜感激一个工作示例。

【问题讨论】:

为什么不像在User 中那样在__post_init__ 之外“声明”它? @DeepSpace,我觉得这不会是可扩展的。此外,我正在尝试使用这些类型提示创建一些东西 - 一种 ORM。我认为一直为外部模型声明属性并不理想。 【参考方案1】:

通过使用inspect.get_source 和正则表达式匹配Type Hinted 属性想出了一个hacky 解决方案。还必须将数据类转换为最终模型的普通类。

from abc import ABC
from dataclasses import dataclass
import inspect
import re
from typing import Dict, List, Optional

from dbconn import DBConnector


@dataclass
class User:
    uid: int
    name: str


@dataclass
class Model(ABC):
    database: DBConnector
    user: User

    def func(self, *args, **kwargs):
        pass

    def get_type_hints(self):
        source = inspect.getsource(self.__class__)
        # Only need type hinted attributes
        patt = r"self\.(?P<name>.+):\s(?P<type>.+)\s="
        attrs = re.findall(patt, source)
        for attr in attrs:
            yield attr + (getattr(self, attr[0]), )


class Command(Model):
    message: Optional[str] = "Hello"

    def __init__(
        self, database: DBConnector,
        user: User,
        message: Optional[str] = "Hello"
    ):
        super().__init__(database, user)
        self.user_id: str = str(self.user.uid)
        self.message: Optional[str] = f"self.user.name: self.message"


cmd = Command(DBConnector(), User(123, 'Stack Overflow'))
for attr in cmd.get_type_hints():
    print(attr)

# Output
('user_id', 'str', '123')
('message', 'str', 'Stack Overflow: Hello')

如果有人能提出更强大的解决方案,我肯定对此很感兴趣。现在,我会将此标记为我的答案,以防有人偶然发现此问题并且可以使用 hacky 解决方案。

【讨论】:

我无法真正改进这一点,但值得注意的是,在全局命名空间中或作为类变量编译一次正则表达式(例如 regex = re.compile(patt))可能会更有效,然后在您的Model.get_type_hints 方法中调用regex.findall(source)re.findall(patt, source) 每次调用 Model.get_type_hints 时都必须编译模式。

以上是关于在 post_init 中创建的数据类的属性的访问类型提示的主要内容,如果未能解决你的问题,请参考以下文章

Kivy - 在其他屏幕中创建的访问实例

通过其他类构造函数在主方法中创建对象时访问对象属性

Springboot默认数据库连接池及常用属性

通过 phpmyadmin 访问在 mysql 中创建的数据库

iOS Swift 如何访问在完成处理程序闭包中创建的数据——在闭包之外

Xcode 4 Core Data:如何使用在数据模型编辑器中创建的获取属性