I just wrote a simple @autowired
decorator for Python that instantiate classes based on type annotations.
To enable lazy initialization of the class, the package provides a lazy(type_annotation: (Type, str))
function so that the caller can use it like this:
@autowired
def foo(bla, *, dep: lazy(MyClass)):
...
This works very well, under the hood this lazy
function just returns a function that returns the actual type and that has a lazy_init property set to True
. Also this does not break IDEs' (e.g., PyCharm) code completion feature.
Lazy
type use instead of the lazy
function.Like this:
@autowired
def foo(bla, *, dep: Lazy[MyClass]):
...
This would behave very much like typing.Union. And while I'm able to implement the subscriptable type, IDEs' code completion feature will be rendered useless as it will present suggestions for attributes in the Lazy
class, not MyClass
.
I've been working with this code:
class LazyMetaclass(type):
def __getitem__(lazy_type, type_annotation):
return lazy_type(type_annotation)
class Lazy(metaclass=LazyMetaclass):
def __init__(self, type_annotation):
self.type_annotation = type_annotation
I tried redefining Lazy.__dict__
as a property to forward to the subscripted type's __dict__
but this seems to have no effect on the code completion feature of PyCharm.
I strongly believe that what I'm trying to achieve is possible as typing.Union works well with IDEs' code completion. I've been trying to decipher what in the source code of typing.Union makes it to behave well with code completion features but with no success so far.
Here's how you can add type hints to our function: Add a colon and a data type after each function parameter. Add an arrow ( -> ) and a data type after the function to specify the return data type.
PEP 484, which provides a specification about what a type system should look like in Python3, introduced the concept of type hints.
Type hints improve IDEs and linters. They make it much easier to statically reason about your code. Type hints help you build and maintain a cleaner architecture. The act of writing type hints forces you to think about the types in your program.
What Are Type Annotations? Type annotations — also known as type signatures — are used to indicate the datatypes of variables and input/outputs of functions and methods. In many languages, datatypes are explicitly stated. In these languages, if you don't declare your datatype — the code will not run.
For the Container[Type]
notation to work you would want to create a user-defined generic type:
from typing import TypeVar, Generic
T = TypeVar('T')
class Lazy(Generic[T]):
pass
You then use
def foo(bla, *, dep: Lazy[MyClass]):
and Lazy
is seen as a container that holds the class.
Note: this still means the IDE sees dep
as an object of type Lazy
. Lazy
is a container type here, holding an object of type MyClass
. Your IDE won't auto-complete for the MyClass
type, you can't use it that way.
The notation also doesn't create an instance of the Lazy
class; it creates a subclass instead, via the GenericMeta
metaclass. The subclass has a special attribute __args__
to let you introspect the subscription arguments:
>>> a = Lazy[str]
>>> issubclass(a, Lazy)
True
>>> a.__args__
(<class 'str'>,)
If all you wanted was to reach into the type annotations at runtime but resolve the name lazily, you could just support a string value:
def foo(bla, *, dep: 'MyClass'):
This is valid type annotation, and your decorator could resolve the name at runtime by using the typing.get_type_hints()
function (at a deferred time, not at decoration time), or by wrapping strings in your lazy()
callable at decoration time.
If lazy()
is meant to flag a type to be treated differently from other type hints, then you are trying to overload the type hint annotations with some other meaning, and type hinting simply doesn't support such use cases, and using a Lazy[...]
containing can't make it work.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With