I like to use __init_subclass__ to implement simple plug-in systems, e.g.:
class OptionType:
name: str
__types__: dict[str, "OptionType"] = {}
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
print(cls.name, cls)
cls.__types__[cls.name] = cls
@classmethod
def instance_for(cls, name):
return cls.__types__[name]()
class Int(OptionType):
name: str = "int"
class IntRange(Int):
name: str = "int-range"
print(OptionType.instance_for("int"))
When this is run, __init_subclass__ is run exactly once per class:
int <class '__main__.Int'>
int-range <class '__main__.IntRange'>
<__main__.Int object at 0x101462c70>
This also works with attr.s:
import attr
class OptionType:
name: str
__types__: dict[str, "OptionType"] = {}
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
type_name = cls.name._default
print(type_name, cls)
cls.__types__[type_name] = cls
@classmethod
def instance_for(cls, name):
return cls.__types__[name]()
@attr.s
class Int(OptionType):
name: str = attr.ib("int")
@attr.s
class IntRange(Int):
name: str = attr.ib("int-range")
print(OptionType.instance_for("int"))
int <class '__main__.Int'>
int-range <class '__main__.IntRange'>
Int(name='int')
However, when I use attr.define, everything falls appart:
import attr
_OPTION_TYPES: dict[str, "OptionType"] = {}
@attr.define
class OptionType:
name: str
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
type_name = attr.fields(cls).name.default
print(type_name, cls)
_OPTION_TYPES[type_name] = cls
@classmethod
def instance_for(cls, name):
return _OPTION_TYPES[name]()
@attr.define
class Int(OptionType):
name: str = "int"
@attr.define
class IntRange(Int):
name: str = "int-range"
print(OptionType.instance_for("int"))
NOTHING <class '__main__.Int'> # WHAT?
int <class '__main__.Int'>
int <class '__main__.IntRange'> # ONOES! :-O
int-range <class '__main__.IntRange'>
IntRange(name='int') # 😿
The main reason is, that __init_subclass__ is now called for every class in the inheritance hierarchy. That means that:
- the base class must also use
attrs.define (or attr.fields(cls) (or cls.name.default) will break)
__types__ must move out of the class or I’ll get a TypeError: 'member_descriptor' object does not support item assignment (because since it is typed, an attrs attribute is created for it).
IntRange overrides the class for int, so OptionType.instance_for("int") now returns an IntRange() instead of an Int().
I have not yet time to dig deeper into it but I always had the impression that attr.define is just an alias for attr.s with nicer defaults but that seems not to be the case. 🤔
I like to use
__init_subclass__to implement simple plug-in systems, e.g.:When this is run,
__init_subclass__is run exactly once per class:This also works with
attr.s:However, when I use
attr.define, everything falls appart:The main reason is, that
__init_subclass__is now called for every class in the inheritance hierarchy. That means that:attrs.define(orattr.fields(cls)(orcls.name.default) will break)__types__must move out of the class or I’ll get aTypeError: 'member_descriptor' object does not support item assignment(because since it is typed, an attrs attribute is created for it).IntRangeoverrides the class forint, soOptionType.instance_for("int")now returns anIntRange()instead of anInt().I have not yet time to dig deeper into it but I always had the impression that
attr.defineis just an alias forattr.swith nicer defaults but that seems not to be the case. 🤔