How can I inherit defaultdict and use its copy method in subclass method?
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 fromcopy
), 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.
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.
defaultdict.__init__()
takes three arguments:
self
(of course),- An optional factory callable for missing keys, and
- An optional set of key:values (which and be either a
dict
or a sequence of(key, value)
pairs).
defaultdict.copy()
will create a new defaultdict
instance and pass it it's factory
callable AND a shallow copy of it's current key:values set.
Your subclass's __init__
only takes self
as argument, but ends up being called with three.
The fix here is to rewrite A.__init__
so 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)