Update: as chepner points out in the comments, creating the class in the function is a bad idea, it performs ten to eighty times slower than other solutions. See the performance comparison in my self-answer, which also shows how to do the typing.
I do this:
def get_an_x():
class X:
foo = 1
bar = 'Hello'
baz = None
return X
get_an_x
returns a class X
, which serves as a plain structure.
What is the type of this class X
? Doing def get_an_x() -> type: ...
(that what type(X)
returns) will not work, then PyCharms's type checker does not detect the attributes of X
. Annotating the attributes with ClassVar[...]
also does not help.
I have seen this answer, but doing def get_an_x() -> type[X]: ...
does not work, because I define the class inside the function, and it is not seen from outside.
Creating a class in a function and returning it, is a bad idea. It is very slow. There are other ways to do this:
### INITIALISATION
## CLASS INSTANCE
# Structural type info, fastest (except simple tuples).
class MyInstance:
__slots__ = ['foo', 'bar']
def __init__(self, foo: int, bar: str):
self.foo = foo
self.bar = bar
## DATA CLASS
# https://docs.python.org/3/library/dataclasses.html
# Structural type info, fast.
from dataclasses import dataclass
@dataclass
class MyDataclass:
foo: int
bar: str
## SIMPLE NAMESPACE
# https://docs.python.org/3/library/types.html#types.SimpleNamespace
# No structural type info, fast.
# The structure can change, it is useful when working with JSON, see e.g.
# https://medium.com/@shashank_iyer/
# simplify-json-access-with-simplenamespace-e91f5a09345b
from types import SimpleNamespace
## NAMED TUPLE
# https://docs.python.org/3/library/typing.html#typing.NamedTuple
# Structural type info, slightly slower than above solutions.
# It is a tuple under the hood.
from typing import NamedTuple
MyNamedTuple = NamedTuple('MyNamedTuple', foo=int, bar=str)
## CLASS WITH PROTOCOL (not recommened)
# https://docs.python.org/3/library/typing.html#typing.Protocol
# Structural type info, 10 to 80 times slower than above solutions!
from typing import Protocol
class MyClass(Protocol):
foo: int
bar: str
def make_my_class(fooparam: int, barparam: str) -> MyClass:
class my_class:
foo = fooparam
bar = barparam
return my_class
### USAGE
## PERFORMANCE
from timeit import timeit
def performance(it, does, times=1_000_000):
timing = timeit(does, number=times)
print(f"{it:<12} {timing:6.3f}")
performance('instance', lambda: MyInstance(1, 'Hello'))
performance('dataclass', lambda: MyDataclass(1, 'Hello'))
performance('namespace', lambda: SimpleNamespace(foo=1, bar='Hello'))
performance('namedtuple', lambda: MyNamedTuple(foo=1, bar='Hello'))
performance('class', lambda: make_my_class(fooparam=1, barparam='Hello'))
# For comparision, the performance of simple datatypes
performance('tuple', lambda: (1, 'Hello'))
performance('list', lambda: [1, 'Hello'])
performance('dict', lambda: {'foo': 1, 'bar': 'Hello'})
performance('set', lambda: {1, 'Hello'})
## RESULTS
# instance 0.478
# dataclass 0.568
# namespace 0.483
# namedtuple 0.796
# class 14.904 # varies between 10 and 25 seconds
# tuple 0.344
# list 0.591
# dict 0.666
# set 0.707
Protocol, suggested by abel1502, seems to do what I want:
from typing import Protocol
class X(Protocol):
foo: str
bar: int
baz: None
def get_an_x() -> X:
class x:
foo = 'the answer'
bar = 42
baz = None
return x
Now PyCharms type checker detects wrong types and non-existing or unknown attributes:
# These are ok
x: X = get_an_x()
foo: str = x.foo
bar: int = x.bar
baz: None = x.baz
# These are type errors
unknown = x.unknownattribute
wrong_type: int = x.foo
wrong_x: str = get_an_x()
def get_a_wrong_x() -> X:
class x:
foox = 'the answer'
bar = '42'
return x