How can I inherit defaultdict and use its copy method in a subclass method?

There is a code.

from collections import defaultdict class A(defaultdict): def __init__(self): super(A, self).__init__(lambda :0) self.x = 1 def my_copy(self): return self.copy() if __name__ == '__main__': a = defaultdict(lambda :0) b = a.copy() # no error when using the base class directly a = A() b = a.my_copy() 

An error occurred:

 Traceback (most recent call last): File "/Applications/PyCharm.app/Contents/helpers/pydev/pydevd.py", line 1591, in <module> globals = debugger.run(setup['file'], None, None, is_module) File "/Applications/PyCharm.app/Contents/helpers/pydev/pydevd.py", line 1018, in run pydev_imports.execfile(file, globals, locals) # execute the script File "/Applications/PyCharm.app/Contents/helpers/pydev/_pydev_imps/_pydev_execfile.py", line 18, in execfile exec(compile(contents+"\n", file, 'exec'), glob, loc) File "/Users/liu/project/scir/pytorch_test/t.py", line 14, in <module> b = a.my_copy() File "/Users/liu/project/scir/pytorch_test/t.py", line 8, in my_copy return self.copy() TypeError: __init__() takes 1 positional argument but 3 were given 

I don’t know how to inherit the copy method, and also don’t know why I give 3 arguments.

+7
python collections inheritance copy defaultdict
source share
3 answers

When calling copy , defaultdict calls the constructor with arguments to pass the default_factory function and data.

Your constructor takes no arguments, so it can only create empty dict with a fixed factory.

Correct your constructor as follows:

 def __init__(self,*args): 

But you must pass args to the mother class, or your copied dictionary will be empty (not what you want).

Since you specialize in the default factory, you need to make a special case if args empty:

 class A(defaultdict): def __init__(self,*args): if args: super(A, self).__init__(*args) else: super(A, self).__init__(int) # better than lambda : 0 

Or maybe simpler with a triple:

 class A(defaultdict): def __init__(self,*args): super(A, self).__init__(*(args or (int,))) 
  • When args not empty (called from copy ), then the copy takes the properties of the original (function and data).
  • When args empty, it means you are creating a new dict, so it just fixes the factory argument by default.

Alternatively, you can replace (lambda :0) with (int) .

EDIT: a more complex way, but which ensures that the user cannot change the default value, was to ignore the first argument and force int (possibly with a warning if the first argument is not int ):

 super(A, self).__init__(*([int]+list(args[1:]))) 

This will work, but I don't like the idea of ​​strongly ignoring the argument.

As a conclusion, inheritance for built-in types is generally complex and should be used with caution (see another example trying to do this using pandas dataframe: building a class from an existing one ). Sometimes, creating a class with the defaultdict argument as an argument and that mimics / relays only the methods you plan to use will lead to less side effects.

+9
source share

defaultdict.__init__() takes three arguments:

  1. self (of course)
  2. Additional factory call for missing keys, and
  3. An optional set of keys: values ​​(which can be either a dict or a sequence of pairs (key, value) ).

defaultdict.copy() will create a new instance of defaultdict and pass it the factory call and a shallow copy of the current key: the values ​​are set.

Your subclass __init__ takes only self as an argument, but is ultimately called with three.

The fix here is to rewrite A.__init__ so that it can handle both cases:

 class A(defaultdict): def __init__(self, *args): # make sure we force the factory args = (int,) + args[1:] super(A, self).__init__(*args) 
+2
source share

I decided to expand on what was a small comment on the answer. Although the answers that have already been given, a perfect analysis was carried out, I do not like the proposed modification of the argument. Both defaultdict and base dict have a nontrivial signature (using arguments). The code below does not affect the arguments and passes them unchanged to the original implementation:

 def __init__(self, *args, **kwargs): super(A, self).__init__(*args, **kwargs) self.default_factory = int 

Also stored are quargi, for example. A(a=1,b=2) works.

+1
source share

All Articles