简体   繁体   English

如何有效地传递参数(** python中的kwargs)

[英]how to pass arguments efficiently (**kwargs in python)

I have a class that inherits from 2 other classes. 我有一个继承自其他2个类的类。 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' . 我想用**kwargs很好地解决它,但当我调用第一个注释掉的构造函数时,我得到TypeError: __init__() got an unexpected keyword argument 'size'

Any ideas how I can make it work with **kwargs? 我有什么想法可以让它与**kwargs?

Your problem is that you only tried to use super in the child class. 您的问题是您只尝试在子类中使用super

If you use super in the base classes too, then this will work. 如果你也在基类中使用super ,那么这将有效。 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. 当调用object的构造函数时,如果剩下任何关键字参数,则会引发异常。

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. 因为FirstBase.__init__没有size参数。 You can either add the size argument or add **kwargs to FirstBase.__init__ . 您可以添加size参数或将**kwargs添加到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: 由于你可以控制代码,所以让你自己以你想要的方式传递** kwargs的简单方法是在两个基类的__init__中添加一个** kwargs参数,如下所示:

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? 通过将** kwargs参数添加到基类的__init__函数中,它们突然允许任意命名值被简单地传入,这是可以理解的,因为毕竟** kwargs是一个开放式的catch all,其中没有任何东西是命名,那怎么知道排除什么? 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. 你可以完全忽略** 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. 在写作的那一刻,目前被Dietrich认为是正确的答案,这个答案混淆了基本问题 - 它与super()完全没有关系,而且他的修正只是因为** kwargs被添加而且为任意名字打开了大门。

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, 显式传递参数,以便不会解压缩args太长时间,或者使用未指定名称的** kwargs,
  • pop extraneous parameters out of kwargs using the dictionary pop function, 使用字典pop函数弹出kwargs中的无关参数,
  • 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. 可以改进这个anykwarg帮助器以检查kwargs参数,如果存在则不从传入的kwargs中删除任何值,并且上面使用的inspect.getargspec返回该信息。 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. 注意,在anykwarg中可以从kwargs中删除它,因为它不是对传入的某些字典的引用,它是从参数生成的本地字典。

You should not have to call super() in the base classes, as in the accepted answer by @Dietrich Epp . 你不应该在基类中调用super() ,就像@Dietrich Epp接受的答案一样 All you're doing is passing any leftover arguments in kwargs up to the object base class, where they're thrown away; 你所做的只是将任何剩余的参数传递给kwargsobject基类,然后扔掉它们; 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 . 当你做super(ChildClass, self).__init__(foo, bar, **kwargs)ChildClass的超类必须要求所有参数foobar**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. 因此,在您的情况下,获取错误TypeError: __init__() got an unexpected keyword argument 'size'意味着您的父类Child没有size或者__init__()方法中没有**kwargs

When your super() call goes to each of Firstbase and Secondbase , their constructors will try and unpack **kwargs into their keyword arguments. 当你的super()调用转到FirstbaseSecondbase每一个时,它们的构造函数将尝试将**kwargs解包为其关键字参数。 Everything in **kwargs needs to go somewhere. **kwargs所有东西都需要去某个地方。 Anything leftover (ie not specified by a keyword argument), like size , will raise your TypeError, unless there is another **kwargs for the leftovers to go into. 剩下的任何东西(即没有由关键字参数指定),如size ,都会引发你的TypeError,除非剩下剩下的**kwargs进入。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM