RFC: using `__call__` instead of `__new__`
Closed this issue · 1 comments
So it's that time of year when I finally have a bit more time to experiment with my projects. I've been thinking about the uninspiring performance when (and only when) a custom __init__
is used, as @TylerYep pointed out in #6 (comment). This arises because the current __call__
implementation used is generic and dynamically modifies the parameters to either __new__
or __init__
- what would be really good is to generate a static __call__
that does this with no overhead. To keep performance good and code simple, this method would both initialise the instance with its parameters and redirect any additional ones to __init__
if it's defined. However, because an object's __call__
method has to be defined on its type (i.e. in the case of a class its metaclass), this means we have to dynamically create a subclass of the metaclass at the time of decorator use.
I've implemented this, and though it works surprisingly well in my other tests, it breaking multiple inheritance (highlighted below) means it's nowhere near ready for rolling out in releases yet. Despite this, I'm curious to see how well it works in the code of others, if you would like to test it. The code is in the branch static-call
.
Advantages
- As-fast-as-possible initialiser performance
- Even simpler code in dataclassy since it only has to work with one method (
__call__
), not__call__
,__new__
and__signature__
- The current use of
__new__
could be considered hacky as it's not "supposed" to be used to initialise the class instance
Disadvantages
- You (the user) can no longer call
__new__
on a data class to instantiate it without__init__
being executed. I'm not sure if this is actually useful (or a good idea to do), but it is a feature nonetheless - Say class
B
is a subclass ofA
. With this method,type(A) is not type(B)
which is unusual and surprising. Onlyissubclass(type(B), type(A))
is true. However, Python supports this "dynamic metaclass" paradigm fine. Besides looking odd (and how often do you compare the types of classes, really!) I've found no side effects other than... - Multiple inheritance now becomes ugly. Whereas before it worked perfectly (e.g.
class C(A, B)
, now you have to do something like
class CMeta(type(A), type(B)):
pass
@dataclass(meta=CMeta)
class C:
...