TL;DR: Short way of doing stuff like def __init__(self, a, b): self.a = a; self.b = b
, using type annotations from PEP 484, inspired by Dart's MyConstructor(this.a, this.b)
and Ruby's/Crystal's/(Moon|Coffee)Script's constructor/initialize(@a, @b)
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | import sys
if sys.version_info[0:2] < (3, 5):
raise ValueError('Python >=3.5 is required!')
# Example usage:
# from auto_assign import *
# class Xyz:
# @auto_assign
# def __init__(self, x: AutoAssign[int]) -> None:
# pass
# xyz = Xyz(1)
# print(xyz.x) # 1
# (See the description for more details...)
from typing import Any, Callable, TypeVar, Union, cast
import inspect
class _AutoAssignMagic: pass
_T = TypeVar('_T')
AutoAssign = Union[_AutoAssignMagic, _T]
_F = TypeVar('_F', bound=Callable[..., Any])
def auto_assign(func: _F) -> _F:
def _wrap(*args, **kw):
if not args:
# Where is self? Let the function figure it out!
func(*args, **kw)
self = args[0]
sig = inspect.signature(func)
auto_args = set()
for param in sig.parameters.values():
# XXX: Hacky way of checking for AutoAssign.
if hasattr(param.annotation, '__origin__') and \
param.annotation.__origin__ is AutoAssign:
auto_args.add(param.name)
try:
bound = sig.bind(*args, **kw)
except TypeError:
# Just let it propagate down to the function itself.
pass
else:
bound.apply_defaults()
for auto_arg in auto_args:
setattr(self, auto_arg, bound.arguments[auto_arg])
func(*args, **kw)
return cast(_F, _wrap)
|
Highlights:
- Plays nicely with PEP 484.
- Dead-easy to use.
- Looks pretty!!
For instance:
class MyClass:
myattr1: int # to appease type checkers!
# The auto_assign decorator will pick out all the
# AutoAssign annotations and assume those are what
# should be assigned to `self`.
# Note that, thanks to some Union magic, this will still
# type-check correctly. e.g. Mypy won't flag MyClass(1)
# or similar because of type errors.
# This is inspired a bit by both Dart and Crystal. The
# __init__(self, myattr1: AutoAssign[int]) corresponds to
# MyClass(this.myattr) or initialize(@myattr1 : Int32).
@auto_assign
def __init__(self, myattr1: AutoAssign[int]):
print(self.myattr1)
MyClass(123)
There should be a
return
after line 38 or elseargs[0]
on line 40 will raise an exception.