The special methods, with names between two __
as __new__
and __init__
are special for a reason: they are part of the language specification - so
the rules for these methods are at least as good as the "rule" that says a class method has to be a "@classmethod".
I said "at the very least", but in fact language specification has the prerogative to say which special methods will be classmethods, or any other behavior, without needing a decorator.
The document with these specifications and which describes, at least in summary form, all the special methods of the language is the Data Model and it’s always worth a read. (Some of the magic methods are used in protocols implemented in the standard library, but are not part of the language, so they may not be in this Data Model document. This is the case of the special methods used by pickle
). Methods that are classmethod by default are __init_subclass__
and __class_getattr__
, for example.
Returning to __new__
: it is actually not even a classmethod either - as a matter of implementation it is a static method (@staticmethod) - and is so special that when the language will call the __new__
it includes the attribution cls
in the arguments by a mechanism other than what is used when the cls
is automatically included to call a classmethod. I don’t know the real reasons for this - but it may just be a matter of inheritance - before there is the call super()
from Python, the way to call a method in a superclass was to put an explicit reference to the superclass, with the name of the method. Only by doing this when you were modifying the __new__
and was to call, from within object.__new__
, if __new__
were a standard classmethod would receive object
as an argument for "cls" - when you actually have to receive the current class (the class or subclass for which you are implementing the new __new__
).
I come back to this staticmethod question - first, I draw attention to one thing: you cite the "zen of Python" - but although it is in the language, it lists general principles, and has been taken very seriously by more than one generation of Pythonists (including myself)It is more an "artistic" work with general lines than rules. But even if they were - you quote, "Special cases aren’t special enough to break the Rules.":
- what the next verse is just "Although practicality Beats purity."
- and if you take into account that there must be an "obvious way of doing things" ("There should be one-- and preferably only one --obvious way to do it.") it is clear that the method that goes create an instance when it does not exist, cannot be an instance method - it has to be a class method
- and most important of all: the rules nay are broken in this case. That’s what we’ll see in the rest of the text.
The rules are not broken
Well, as already described above, as the rules are given by the language specification, so the rule is "the special method with the name __new__
" is a static method that will be called with the class as first argument" is the rule. It is not broken.
But the thing goes beyond that, and therein comes a point where even without knowing the definition of __new__
in the datamodel, you had a misinterpretation of what is the "rule for classmethod". The fact is that it is not the "classmethod" decorator who makes a method a classmethod. A classmethod is a method that will act taking into account the class and not class instances - and Python has convenient mechanisms to automatically inject the class as an argument into a call. The "classmethod" decorator is one of the ways to enable this mechanism.
But for example, in Python 3, if you write a method without any special markup, and call it from the class, the language will not inject any arguments into the first parameter - you can explicitly pass the class in the first parameter, and uses it exactly as if it were a method created with @classmethod
:
class A:
def metodo_de_Classe(cls):
print(cls)
A.metodo_de_classe(A)
Only when called from an instance will Python (Python3) inject the instance as the first argument.
What classmethod does is create an object that is a wrapper for the decorated function that causes when it is recovered, either as class attribute, or instance attribute, the cls
be injected - this is very easy to do, and you can create a class that works exactly like the classmethod with very few lines of Python:
class MyClassMethod:
def __init__(self, method):
self.method = method
def __get__(self, instance, owner):
return lambda *args, **kwargs: self.method(owner, *args, **kwargs)
Ready - this "Myclassmethod" can be used anywhere you would use the built-in "classmethod" of the language. What I want to make clear with this is: the decorator "@classmethod" is not part of the syntax nor the language specification itself, it is part of the "built-in callables" - which are always available without any import, but which are not a vital part of the language. (Please do not take this to iron and fire - I do not intend to fall into the discussion whether the builtins,and even the standard library, are part of the "language specification" or not - will depend more on the context than on absolute terms)
The mechanism that makes it work - what will call the method __get__
in the code excerpt above is the protocol descriptors (Descriptor Protocol), this yes, is part of the specification of the language (and its role is described in the Data Model, which I mentioned above).
But even more, there are other mechanisms that can make a method work as a class method without using the classmethod, nor explicitly using something that implements the descriptor protocol: any "instance method" in the metaclass of a class will be a class method for it.
And this will work naturally without any other code being needed, by the normal mechanisod and attribute search - which, by not finding an attribute in the instance, will look for it in the class.
So if I want a metaclass that doesn’t change any other class behavior, but enter class methods "automatically" just do:
class AutoClassMethod(type):
def register(cls):
print(f"Called with {cls}")
class A(metaclass=AutoClassMethod):
pass
A.register()
And actually, it’s more that kind of thing - the fact that an instance method in the class of a class (the metaclass) is automatically a class method is what makes those who understand the language say that it respects "Special cases aren’t special enough to break the Rules" - it only works this way because of the natural way access to attributes works - (that is: there is no explicit reference to this in the documentation - it only works because this is how methods work: if you call a method in an instance, it is called with the instance as the first argument - and classes are instances of its metaclasses).
An interesting note about methods defined like this: again, on account of the existing rules (and that are not broken, after all "Special cases aren’t special enough to..."), a method in the metaclass will function as class method, but will not be visible or chargeable from instances of that class.
That is, continuing the example above, if I do:
a = A()
a.register()
"Attributeerror" is coming up. Some Python modules take advantage of this to implement Apis for classes that do not interfere with instances - for example, if you inherit your abc.ABC
from the standard library, your class will have a method register
, but her instances will not have this register
.
TL;DR: with all that has been said, and rereading its question, yes, I guess you could say __new__
be a classmethod viola the part that says "special cases are not special enough to break the Rules" - because of a surprise, so much so that surprised you. I hope, however, to have become clear in the context that it is the "zen of Python" that is something that is not so "iron and fire" so - and he himself has provisions for this when saying that "pratility Beats purity".
jsbueno, the answer is excellent! In addition to answering the questions I had and explaining things I understood but didn’t explain in the question, you further explained questions I hadn’t even thought about yet.
– fernandosavio
I will wait one more day to see if anyone else answers and then mark the answer as accepted. Thank you!
– fernandosavio