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

3

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.uiduser.name作为单独的参数传递给Command,但当User对象具有许多有用的属性时,这不是实用的。
我认为之所以一开始无法正常工作是因为在运行时调用了__init__,这就是为什么类型检查没有考虑这些属性的原因。一个可能的解决方案是解析类的AST,但我不确定这是否被推荐并且足够通用。如果是,请提供一个可用的示例。

为什么不像在“User”中那样,在__post_init__之外“声明”它呢? - DeepSpace
@DeepSpace,我觉得那样不太具有可扩展性。此外,我试图使用那些类型提示创建一种ORM。我认为在每次模型外部声明属性并不理想。 - Harshith Thota
1个回答

0

使用inspect.get_source和正则表达式匹配类型提示属性找到了一个巧妙的解决方案。还必须将dataclass转换为普通类以用于最终的模型。

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')

如果有人能提出一个更强大的解决方案,我肯定很感兴趣。目前,我会将这个标记为我的答案,以防有人偶然找到并接受一个巧妙的解决方案。

1
我无法真正改进这个,但值得注意的是,在您的Model.get_type_hints方法中编译一次正则表达式(例如regex = re.compile(patt))可能会更有效率,可以将其放在全局命名空间或类变量中,然后调用regex.findall(source)。每次调用Model.get_type_hints时,re.findall(patt, source)都需要编译模式。 - Alex Waygood

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接