Reputation: 18107
If I have a class like this:
class foo(object):
def __init__(self, a, b=None, c=None, d=None):
print a, b, c, d
and a derived class like this:
class bar(foo):
def __init__(self, *args, **kwargs):
if "c" in kwargs:
kwargs['c'] = 'else' # CHANGE C IFF IT IS PRESENT
super(bar, self).__init__(*args, **kwargs)
when someone calls this constructor, they could do it like this:
bar('a','b','c','d')
or they could call it like this:
bar('a', c='something')
In the second case, my constructor works as planned, but in the case of the first call c
sneaks thru in the args array. This looks like I would have to watch the length of the args array as well as kwargs, and that seems brittle to the point of unusable. Is there anything you can do to make this situation better, other than just enumerate the arguments from foo in bar? (A somewhat brittle practice itself, but easier to recognize).
Upvotes: 2
Views: 445
Reputation: 369164
How about populating kwargs
with args
?
class bar(foo):
def __init__(self, *args, **kwargs):
for name, value in zip(['a', 'b', 'c', 'd'], args): # <---
kwargs[name] = value # <---
args = () # <---
if "c" in kwargs:
kwargs['c'] = 'else'
super(bar, self).__init__(*args, **kwargs)
UPDATE
Alternative that use inspect.getcallargs
:
import inspect
class bar(foo):
def __init__(self, *args, **kwargs):
kwargs = inspect.getcallargs(foo.__init__, self, *args, **kwargs)
kwargs.pop('self')
if 'c' in kwargs:
kwargs['c'] = 'else'
super(bar, self).__init__(**kwargs)
Upvotes: 2
Reputation: 21089
This is more brittle than you think. Have you considered the situation where someone passes a keyword argument not present in foo.__init__
's argument list (e.g. bar('a', f='something')
)? My recommendation would be requiring bar
to take keyword arguments only and then filter the keys not present in foo.__init__
's argument list (which you can determine via introspection using inspect.getargspec
[or related functions for newer versions of Python, particularly the Signature
and Parameter
objects starting in 3.3] if the arguments may change). That in itself has its own form of brittleness, of course, as a programmer using bar
would need to know the relevant argument names for foo
's constructor, but depending on what bar
is being used for they may need to know what arguments foo
takes anyway, and when someone knows that they usually know what the names of the arguments are as well.
Now that I'm looking at inspect
again, I realize that there's another method you could use: inspect.getcallargs
, which might be more useful for you. With this, you could do, say, inspect.getcallargs(super(bar, self).__init__, *[self, 1], **{'c':3, 'd':4})
and obtain the following dict: {'a': 1, 'self': <bar object>, 'b': None, 'c': 3, 'd': 4}
. Then you can modify that dictionary and supply it as super(bar, self).__init__(**fixed_dict)
or something of that sort. You'd still have the issue with keyword arguments not present in foo.__init__
's argument list, though (getcallargs
raises the same errors foo.__init__
will when passed invalid arguments).
Upvotes: 1