In digging through the python Counter
class in collections
, I found something I thought was peculiar: They don't explicitly use the self
argument in the __init__
function's arguments.
See code below (copied directly without the docstring):
class Counter(dict):
def __init__(*args, **kwds):
if not args:
raise TypeError("descriptor '__init__' of 'Counter' object "
"needs an argument")
self, *args = args
if len(args) > 1:
raise TypeError('expected at most 1 argments, got %d' % len(args))
super(Counter, self).__init__()
self.update(*args, **kwds)
Later in this same class, the update
and subtract
methods are also defined this same way.
Before you point me to questions about how self
works in classes, I will note that I don't believe this is a duplicate question. I understand how self
works typically and that self
is not a keyword (just standard practice) etc. I also understand that this code works (I'm not questioning the validity of the *
unpack/explode/starred-expressions syntax)
My question is more related to why...
__init__
and other normal (non-@static
/@class
methods) of a class like this and in what circumstances should I consider using this in the future?self
filled in manually (e.g. Counter.__init__(some_counter)
)? Or other examples?I have to think that it has something to do with the TypeError("descriptor...").
In Python 3.8, PEP570 introduces Positional-Only Parameters, eliminating the need to use (*args, **kwds)
in this way. This can be seen in the Python 3.8 Counter code:
def __init__(self, iterable=None, /, **kwds):
__init__ does act like a constructor. You'll need to pass "self" to any class functions as the first argument if you want them to behave as non-static methods.
You can still instantiate a class that doesn't specify the __init__ method. Leaving it out does not make your class abstract.
This is nothing more than a convention: the name self has absolutely no special meaning to Python. Note, however, that by not following the convention your code may be less readable to other Python programmers, and it is also conceivable that a class browser program might be written that relies upon such a convention.
No, it isn't necessary.
This code is intended to make self
positional-only. Otherwise, a call like
d = {'self': 5}
Counter(**d)
would fail due to __init__
receiving two values of self
.
Most classes don't need anything like this special handling, but Counter
is supposed to handle keyword arguments like dict
does, where they become keys of the resulting mapping, even if the key is 'self'
. The other Counter
methods that have this handling are the ones that need the same keyword argument behavior.
If you need to treat self
as a valid keyword argument in your own code, you should probably do something similar.
As for the TypeError
, that's there to match the error message from dict.__init__
:
>>> dict.__init__()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: descriptor '__init__' of 'dict' object needs an argument
>>> Counter.__init__()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python3.7/collections/__init__.py", line 560, in __init__
raise TypeError("descriptor '__init__' of 'Counter' object "
TypeError: descriptor '__init__' of 'Counter' object needs an argument
The most likely way for this to come up in practice is probably people subclassing Counter
and forgetting to pass self
to Counter.__init__
(or use super
).
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