简体   繁体   中英

How to type python class factory with linter?

I have this code

from abc import ABC
from typing import Type


class AbstractFoo(ABC):
    """Abstract foo."""
    foo_attr: int


def foo_factory(foo_attr_val: int) -> Type[AbstractFoo]:

    class Foo(AbstractFoo):
        foo_attr = foo_attr_val

    return Foo


FooA = foo_factory(5)


class FooB(FooA):
    """Public concrete foo."""

print(FooB.foo_attr)

This executes just fine, but then I run mypy with mypy foo.py , I get an error that looks like

foo.py:21: error: Variable "foo.FooA" is not valid as a type
foo.py:21: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases
foo.py:21: error: Invalid base class "FooA"
Found 2 errors in 1 file (checked 1 source file)

I don't understand what is wrong here and why this type is invalid. What's the way to fix this?

I'm currently on Python 3.9 with mypy version 0.971.

Your problem is that mypy does not support dynamic base classes (doesn't matter if they are well-defined). See this mypy issue for more workarounds and details.

If you're happy enough to have a factory producing class without major modifications, you can just pretend that return type is that class ( try me online! ):

from abc import ABC
from typing import Type


class AbstractFoo(ABC):
    """Abstract foo."""
    foo_attr: int


def foo_factory(foo_attr_val: int) -> Type[AbstractFoo]:

    class Foo(AbstractFoo):
        foo_attr = foo_attr_val

    return Foo


if TYPE_CHECKING:
    FooA = AbstractFoo
else:
    FooA = foo_factory(5)


class FooB(FooA):
    """Public concrete foo."""

print(FooB.foo_attr)

TYPE_CHECKING is a special constant that is False at runtime and True for type checkers. Here you make type checker think that your FooA is just a type alias (and on py3.10+ or with typing_extensions you can even make it explicit with TypeAlias type annotation).

Based on the comments, it appears as if this is a mypy issue. I did find that with Python 3.10 on MAC, the code still required some changes to run correctly:

class AbstractFoo:
    foo_attr: int

def foo_factory(foo_attr: int) -> type[AbstractFoo]:

    class Foo(AbstractFoo):
        AbstractFoo.foo_attr=foo_attr

    return Foo


FooA = foo_factory(5)

class FooB(FooA):
  pass

I made two changes:

  1. Type -> type
  2. foo_attr --> AbstractFoo.foo_attr

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