python超级调用中的* args和** kwds

时间:2013-09-11 21:52:20

标签: python

我试图在Python中创建子类时理解*args**kwds的使用。

我想理解为什么这段代码的行为方式。如果我在调用*args时忽略了**kwdssuper().__init__,我会得到一些奇怪的参数解压缩。

这是我的测试用例:

class Animal(object):
    def __init__(self, moves, num_legs):
        self.moves = moves
        self.num_legs = num_legs

    def describe(self):
        print "Moves :{} , num_legs : {}".format(self.moves, self.num_legs)

class Snake(Animal):
    def __init__(self, poisonous, *args, **kwds):
        self.poisonous = poisonous
        print "I am poisonous:{}".format(self.poisonous)
        # This next line is key. You have to use *args , **kwds.
        # But here I have deliberately used the incorrect form,
        # `args` and `kwds`, and am suprised at what it does.
        super(Snake, self).__init__(args, kwds)

现在,当我创建Snake子类的实例时,其中包含对super(…).__init__的错误调用(我使用argskwds代替*args和{{ 1}}),我得到了一些有趣的“论证解包”。

**kwds

我得到的是:

s1 = Snake(False, moves=True, num_legs=0)
s2 = Snake(poisonous=False, moves=True, num_legs=1)
s3 = Snake(False, True, 3)
s1.describe()
s2.describe()
s3.describe()

那么为什么在Moves :() , num_legs : {'moves': True, 'num_legs': 0} Moves :() , num_legs : {'moves': True, 'num_legs': 1} Moves :(True, 3) , num_legs : {} s1中,s2假设__init__moves = Truenum_legs = 0是关键字参数,并且将1设置为词典?

num_legs中,它将两个变量解包为s3(在类moves中)作为元组。


我偶然发现了这一点,因为我试图理解参数解包。提前抱歉 - 我不知道如何更好地构建这个问题。

3 个答案:

答案 0 :(得分:9)

Snake.__init__中,argspoisonous之后所有位置参数的元组,而kwds是除poisonous之外的所有关键字参数的字典。致电

super(Snake,self).__init__(args,kwds)

您在args中将moves分配给kwdsnum_legs分配给Animal.__init__。这正是你在输出中看到的。

除了poisonous之外,前两个调用没有任何位置参数,因此args和后续moves是一个空元组。第三个调用没有关键字参数,因此kwds和后续num_legs是一个空字典。

答案 1 :(得分:6)

简而言之:def __init__(self,poisonous,*args,**kwds):表示:捕获元组args中的位置参数和字典kwds中的关键字参数。同样,super(Snake,self).__init__(*args, **kwds)表示:将元组args和字典kwds解包为参数,以便它们分别传递给__init__

如果您不使用***,那么您原来是argskwds,这意味着您将获得一个元组,字典。


正如你所说,你需要写:

super(Snake,self).__init__(*args, **kwds)

正确打包/解压缩参数。在您当前的代码中,您没有打包/解包参数,因此它将num_legs设置为字典,就像kwds目前的那样。


如果你没有给出参数名称,那么它们就是位置参数。因此Snake(False,True,3)都是位置参数。

如果您确实给出了参数名称,那么它们就是关键字参数:Snake(poisonous=False,moves=True,num_legs=1)

在第一种情况下,您将两个位置参数和两个关键字参数组合在一起:Snake(False,moves=True,num_legs=0)

答案 2 :(得分:1)

变异性比Snake(False, True, 3)更好,更直观:

Snake("Python", constrictor=True, poisonous=False)
Animal("Snail")    # Snail has a foot but no leg. Defaults are good for it.
# Cobra eat other snakes, including poisonous, fast attacks, snake fights.
Snake("Indian cobra", moves=True, poisonous=True)
Animal("Myriapod", num_legs=750)  # Changes for an idividual after every molting.

哦,关于Python的真正令人兴奋的问题,不仅仅是关于编程。 :)

在第一个地方拥有最多的个体参数是个好主意,这些参数对于所有子类都是通用的,就像通用的“自我”本身一样。下一个非常常见的是这个例子中的名称。

如果您认为您的课程将永远不会被修改,并且每次都会使用所有已实现的参数,并且您将永远不会以正确的顺序出错,您不需要任何变化。您可以继续使用固定位置参数。这种假设经常得不到满足。明天将没有人记住第一个 False 和第二个 True 应该是什么,而不会将其与关键字一起看到。

如果您需要Snake(False, True, 3)使用固定位置参数调用您的课程,则不能将**kwds用于任何这些参数。

A)
现在让我们期待您的示例Snake(False, True, 3)是必需的测试用例。然后,您不能将 ** kwds 用于位置参数 (poisonous, moves, num_legs)的任何内容。你只有这四种实现方式__init__ header :(没够好)

# the most fragile solution - easy extensible, not easy to observe the order
class Snake(Animal):
    def __init__(self, *args):
        self.poisonous = args.pop[0]
        # or better ...pop[-1]  that allows adding new parameters to the end
        super(Snake,self).__init__(*args)
        # now is args undefined if ancestors could eat parts from it but
        # everything is in self

# the most naive solution - easy readable, not easy extensible because not DRY
class Snake(Animal):
    def __init__(self, poisonous, moves, num_legs):
        self.poisonous = poisonous
        super(Snake,self).__init__(moves, num_legs)

# anythig between them combines disadvantages of both previous
class Snake(Animal):
    def __init__(self, poisonous, *args):
        self.poisonous = poisonous
        super(Snake,self).__init__(*args)
class Snake(Animal):
    def __init__(self, poisonous, moves, *args):
        self.poisonous = poisonous
        super(Snake,self).__init__(moves, *args)


B)
关键字参数更加强大,因为可以自动报告某些错误。 期望您重新定义动物以增加其变异性:

class Animal(object):
    def __init__(self,name, moves=True, num_legs=None):
        self.name = name
        self.moves = moves
        self.num_legs = num_legs

# The recommended Snail  !
class Snake(Animal):
    def __init__(self, *args, **kwds):
        """Snake: Implements.. (Docs important, otherwise real keywords not seen in help)

        kwds:                (only what defined here)
            poisonous:  Bla bla. default=True
            constrictor:  Bla bla bla. default=False
        """
        # A copy of kwds can be created, if manipulation with original is prohibited.
        self.poisonous = kwds.pop('poisonous', True)  # default: poisonous snake
        self.constrictor = kwds.pop('constrictor', False)
        # OK. This reports error if some keyword is misspelled and will not be consumed.
        super(Snake,self).__init__(*args, **kwds)

# This Snake is more readable, but its descendants would be more complicated,
# otherwise is possible: "TypeError: got multiple values for keyword argument 'xy'".
class Snake(Animal):
    def __init__(self, name, poisonous=True, constrictor=False, *args, **kwds):
        self.poisonous = poisonous
        self.constrictor = constrictor
        super(Snake,self).__init__(name, *args, **kwds)

现在你有很大的变化,关键字参数的顺序并不重要。