Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

There is the 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()

There is the error:

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

like image 335
Michael Liu Avatar asked Jul 31 '17 08:07

Michael Liu


People also ask

How does Defaultdict work Defaultdict will automatically?

The Python defaultdict type behaves almost exactly like a regular Python dictionary, but if you try to access or modify a missing key, then defaultdict will automatically create the key and generate a default value for it. This makes defaultdict a valuable option for handling missing keys in dictionaries.

How does Defaultdict work Defaultdict forces a dictionary?

A defaultdict can be created by giving its declaration an argument that can have three values; list, set or int. According to the specified data type, the dictionary is created and when any key, that does not exist in the defaultdict is added or accessed, it is assigned a default value as opposed to giving a KeyError .

Is Defaultdict faster than dict?

get method and the experiment shows that defaultdict more that two times faster than dict. get method.

How does the Defaultdict work?

A defaultdict works exactly like a normal dict, but it is initialized with a function (“default factory”) that takes no arguments and provides the default value for a nonexistent key. A defaultdict will never raise a KeyError. Any key that does not exist gets the value returned by the default factory.


2 Answers

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

Your constructor doesn't take any arguments, so it's only able to build empty dicts with fixed factory.

Fix your constructor like this:

def __init__(self,*args):

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

Since you're specializing the default factory, you have to make a special case if args are 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 ternary:

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

Aside: you could replace (lambda :0) by (int).

EDIT: a more complicated way but which makes sure that user cannot change default would be to ignore first argument and force int (maybe with a warning if first argument isn't int):

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

That would work, but I don't like the idea of ignoring an argument much.

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

like image 153
Jean-François Fabre Avatar answered Sep 21 '22 09:09

Jean-François Fabre


I decided to expand what was a small comment to an answer. While a perfect analysis was given in answers already given, I dislike the proposed argument modification. Both defaultdict and the underlying dict have a non-trivial signature (usage of arguments). The code below does not touch 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 the kwargs are preserved, e.g. A(a=1,b=2) works.

like image 31
VPfB Avatar answered Sep 24 '22 09:09

VPfB