简体   繁体   中英

Annotating specific dataclass subclass in a signature

from dataclasses import dataclass


@dataclass
class BaseProduct:
    ...


@dataclass
class ProductA(BaseProduct):
    a_specific_id: int


@dataclass
class ProductSubmissionCommand:
    product_id: str
    product: BaseProduct


class AProductRequestSubmitter:
    def __call__(self, job_id: int, cmd: ProductSubmissionCommand):
        ...
        cmd.product.a_specific_id

mypy error is: error: "BaseProduct" has no attribute "a_specific_id"

How to annotate AProductRequestSubmitter.__call__ properly? This class is specific for AProduct , there are other submitters for different product types.

Is it possible to use Generic types or Literal values somehow? Or maybe cast or assert is an only way to go?

There are multiple ways to achieve what you are trying to, I will show the ones that pop out the top of my head right now.

Ensure correct type at runtime

There may be multiple children having BaseProduct as their parent and there is no way for the type checker to know for sure, you will never pass a ProductSubmissionCommand instance that has product attribute of a type that is a child of BaseProduct that does not have a_specific_id attribute. In order to make sure this can never happen, you could start your function call with a type assertion, such as:

class AProductRequestSubmitter:
    def __call__(self, job_id: int, cmd: ProductSubmissionCommand):
        if not isinstance(cmd.product, ProductA):
            raise TypeError(f"Only 'ProductA' instances are allowed, not '{cmd.product.__class__.__name__}'")
        ...
        cmd.product.a_specific_id

Pycharm (or any other static type checker) should now be quiet, because the program will never reach the part of the code, where a_specific_id is accessed unless the correct type is in use.

Subclass ProductSubmissionCommand

If you really plan on using AProductRequestSubmitter for instances, where ProductSubmissionCommand has product of type ProductA , you may subclass your ProductSubmissionCommand and typehint its product attribute as a ProductA instance.

@dataclass
class ProductASubmissionCommand(ProductSubmissionCommand):
    product: ProductA


class AProductRequestSubmitter:
    def __call__(self, job_id: int, cmd: ProductASubmissionCommand):
        ...
        cmd.product.a_specific_id

Again, all clever typecheckers should now understand, that cmd will always have product.a_specific_id attribute.

Use Protocol

Using:

class ProductASubmissionCommand(Protocol):
    product: ProductA

class AProductRequestSubmitter:
    def __call__(self, job_id: int, cmd: ProductASubmissionCommand):
        ...
        cmd.product.a_specific_id

You are basically saying "accept anything, that has an attribute named product of a type ProductA ". This is a little hacky and makes you create a protocol subclass only for the sake of typehinting this single method call, but, again, should work with all static type checkers.

You have 2 possible ways.

  1. Ignore static typint by using an explicit getattr call

     class AProductRequestSubmitter: def __call__(self, job_id: int, cmd: ProductSubmissionCommand): ... getattr(cmd.product, 'a_specific_id')
  2. Use Generic to specialize ProductSubmissionCommand on a static typing point of view:

     T = TypeVar("T", bound=BaseProduct) @dataclass class ProductSubmissionCommand(Generic[T]): product_id: str product: T class AProductRequestSubmitter: def __call__(self, job_id: int, cmd: ProductSubmissionCommand[ProductA]): pass

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