简体   繁体   中英

Access Type Hints for attributes of a dataclass created in post_init

Python: 3.7+

I have a dataclass and a subclass of it as following:

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}"

I could get the type hint for database , user and message using typing.get_type_hints(Command) . How can I get the type hints for user_id ?

One workaround would to be pass in the user.uid and user.name as separate params to Command but that's not pragmatic when User object has many useful attributes.

I believe the reason why it doesn't work in the first place is because init gets called at runtime and that's why type checking doesn't take those attrs into account. One possible solution would be to parse the ast of the class but I'm not sure if that's recommended and generic enough. If yes, would appreciate a working example.

Figured out a hacky solution by using inspect.get_source and regex matching Type Hinted attributes. Also had to convert dataclass into a normal class for the end Model.

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

If someone can come up with a more robust solution, I'm definitely interested in it. For now, I'll mark this as my answer, in case someone stumbles upon this and is ok with a hacky solution.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM