__init__ function definition without self argument

  • A+
Category:Languages

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...

  • Why would one implement the __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?
  • Why are only specific methods on the same class implemented like this?
  • Under what circumstance would these methods be called without any args (if any), triggering the first TypeError?
  • In what circumstances would these methods be called with 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...").

 


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).

Comment

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: