user1658543
user1658543

Reputation:

Type of a returned class

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.

Upvotes: 0

Views: 148

Answers (2)

user1658543
user1658543

Reputation:

Updated Answer

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

Original Answer

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

Upvotes: 1

abel1502
abel1502

Reputation: 980

The issue is, every time you call foo, it returns a different class. If it had a base class, you could annotate it with typing.Type[Base], to at least show the base's attributes. But in your situation I'd suggest you to either let your IDE figure it out without annotations, or to annotate it as typing.Type.

One more option you have is creating a protocol, if you know the exact attribute set in advance, and using it for the annotation

Upvotes: 1

Related Questions