Вопрос

I have a class that inherits from 2 other classes. These are the base classes:

class FirstBase(object):
      def __init__(self, detail_text=desc, backed_object=backed_object,
                   window=window, droppable_zone_obj=droppable_zone_obj,
                   bound_zone_obj=bound_zone_object,
                   on_drag_opacity=on_drag_opacity):
          # bla bla bla

class SecondBase(object):
      def __init__(self, size, texture, desc, backed_object, window):
          # bla bla bla

And this is the child:

class Child(FirstBase, SecondBase):
       """ this contructor doesnt work
       def __init__(self, **kwargs):
          # PROBLEM HERE
          #super(Child, self).__init__(**kwargs)
       """
       #have to do it this TERRIBLE WAY
       def __init__(self, size=(0,0), texture=None, desc="", backed_object=None,
                    window=None, droppable_zone_obj=[], bound_zone_object=[],
                    on_drag_opacity=1.0):
          FirstBase.__init__(self, detail_text=desc, backed_object=backed_object,
                             window=window, droppable_zone_obj=droppable_zone_obj,
                             bound_zone_obj=bound_zone_object,
                             on_drag_opacity=on_drag_opacity)
          SecondBase.__init__(self, size, texture, desc, backed_object, window)

I wanted to solve it all nicely with **kwargs but when I call the first commented out constructor I get TypeError: __init__() got an unexpected keyword argument 'size'.

Any ideas how I can make it work with **kwargs?

Это было полезно?

Решение

Your problem is that you only tried to use super in the child class.

If you use super in the base classes too, then this will work. Each constructor will "eat" the keyword arguments it takes and not pass them up to the next constructor. When the constructor for object is called, if there are any keyword arguments left over it will raise an exception.

class FirstBase(object):
    def __init__(self, detail_text=None, backed_object=None,
                 window=None, droppable_zone_obj=None,
                 bound_zone_obj=None, on_drag_opacity=None, **kwargs):
        super(FirstBase, self).__init__(**kwargs)

class SecondBase(object):
    def __init__(self, size=(0,0), texture=None, desc="",
                 backed_object=None, window=None, **kwargs):
        super(SecondBase, self).__init__(**kwargs)

class Child(FirstBase, SecondBase):
    def __init__(self, **kwargs):
        super(Child, self).__init__(**kwargs)

As you can see, it works, except when you pass a bogus keyword argument:

>>> Child()
<__main__.Child object at 0x7f4aef413bd0>
>>> Child(detail_text="abc")
<__main__.Child object at 0x7f4aef413cd0>
>>> Child(bogus_kw=123)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "test.py", line 14, in __init__
    super(Child, self).__init__(**kwargs)
  File "test.py", line 5, in __init__
    super(FirstBase, self).__init__(**kwargs)
  File "test.py", line 10, in __init__
    super(SecondBase, self).__init__(**kwargs)
TypeError: object.__init__() takes no parameters

Другие советы

I don't like this solution but you can do this:

class FirstBase(object):
  def __init__(self, *args, **kargs):
      kargs.get('karg name', 'default value')
      # bla bla bla

class SecondBase(object):
  def __init__(self, *args, **kargs):
      kargs.get('karg name', 'default value')
      # bla bla bla

class Child(FirstBase, SecondBase):
   def __init__(self, *args, **kargs):
      FirstBase.__init__(self, *args, **kargs)
      SecondBase.__init__(self, *args, **kargs)

Because FirstBase.__init__ doesn't have the size argument. You can either add the size argument or add **kwargs to FirstBase.__init__.

Try something like this.

class FirstBase(object):
      def __init__(self, detail_text, backed_object, window, droppable_zone_obj, bound_zone_obj, on_drag_opacity):
          # bla bla bla

class SecondBase(object):
      def __init__(self, texture, desc, backed_object, window, size=None):
          # bla bla bla

a possible hack (but hacking is bad, remember), is to appeal to the inspect module to obtain the signature of the function that you are going to use:

import inspect
#define a test function with two parameters function
def foo(a,b):
    return a+b

#obtain the list of the named arguments
acceptable = inspect.getargspec(f).args

print acceptable
#['a', 'b']

#test dictionary of kwargs
kwargs=dict(a=3,b=4,c=5)

#keep only the arguments that are both in the signature and in the dictionary
new_kwargs = {k:kwargs[k] for k in acceptable if k in kwargs}

#launch with the sanitized dictionary
f(**new_kwargs)

This is not a code you should use in production code. If the function refuses some arguments it means that you can obtain the signature reading the code, and passing only the necessary keys. The hack with inspect is far less stable and readable, so use only when you haven't any other way!

Since you have control over the code, the easy way to let yourself just pass around **kwargs the way you want is to add a **kwargs parameter to the __init__ of both base classes, as so:

class FirstBase(object):
  def __init__(self, detail_text=desc, backed_object=backed_object,
               window=window, droppable_zone_obj=droppable_zone_obj,
               bound_zone_obj=bound_zone_object,
               on_drag_opacity=on_drag_opacity, **kwargs):
      # bla bla bla

class SecondBase(object):
      def __init__(self, size, texture, desc, backed_object, window, **kwargs):
          # bla bla bla

The failure you had encountered is because your base classes only had named parameters, therefore when you pass an unknown name, the bouncer says you're not on my list. By adding **kwargs parameters to the __init__ functions of the base classes, they suddenly allow any arbitrary named value to simply be passed in, which is understandable since, after all, **kwargs is an open ended catch all, nothing of which is named, so how would it know what to exclude? It simply ends up a dictionary of named values in the eyes of the called code, which uses them or doesn't. You could completely ignore **kwargs.

The answer which at the moment of writing is currently accepted as the right one, by Dietrich, confused the essential issue -- it has nothing to do with super() at all, and his correction works simply because **kwargs were added and that opened the door for any arbitrary name.

If you don't have control over the code, you can:

  • pass arguments explicitly so as not to unpack *args too long, or **kwargs with unspecified names,
  • pop extraneous parameters out of kwargs using the dictionary pop function,
  • or you could use this helper meta-function:

    import inspect
    
    def anykwarg(function, *args, **kwargs):
        validargs = inspect.getargspec(function).args
        removals = [key for key in kwargs.keys() if key not in validargs]
        for removal in removals:
            del kwargs[removal]
        return function(*args, **kwargs)
    

This anykwarg helper could be improved to check for a kwargs parameter and if present not remove any values from the kwargs passed in, and inspect.getargspec used above returns that information. Note, it is okay to del from kwargs in anykwarg because it is not a reference to some dictionary passed in, it is a local dictionary generated from parameters.

You should not have to call super() in the base classes, as in the accepted answer by @Dietrich Epp. All you're doing is passing any leftover arguments in kwargs up to the object base class, where they're thrown away; this is sloppy.

When you do super(ChildClass, self).__init__(foo, bar, **kwargs), the super class(es) of ChildClass must expect all the arguments foo, bar, and **kwargs. So in your case, getting the error TypeError: __init__() got an unexpected keyword argument 'size' implies your parent classes of Child either don't have size or don't have **kwargs in their __init__() methods.

When your super() call goes to each of Firstbase and Secondbase, their constructors will try and unpack **kwargs into their keyword arguments. Everything in **kwargs needs to go somewhere. Anything leftover (i.e. not specified by a keyword argument), like size, will raise your TypeError, unless there is another **kwargs for the leftovers to go into.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top