11

我试图了解在 Python 中创建子类的用途*args和时间。**kwds

我想了解为什么这段代码的行为方式如此。如果我在对 的调用中省略*argsand ,我会得到一些奇怪的参数解包。**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__(我使用argsandkwds而不是*argsand的地方**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 : {}

那么为什么 in s1and假设and or是关键字参数s2,并将 the 设置为字典?__init__moves = Truenum_legs = 01num_legs

s3中,它将两个变量解包到moves(在 class 中Animal)作为一个元组。


当我试图理解参数拆包时,我偶然发现了这一点。提前抱歉——我不知道如何更好地提出这个问题。

4

3 回答 3

10

Snake.__init__,args之后是所有位置参数的元组,poisonous并且kwds是除了 之外的所有关键字参数的字典poisonous。通过调用

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

您分配argsmoveskwdsto num_legsin Animal.__init__。这正是您在输出中看到的。

前两个调用除了 之外没有任何位置参数poisonous,因此argsand consequentialmoves是一个空元组。第三个调用没有关键字参数,因此kwdsand consequentialnum_legs是一个空字典。

于 2013-09-11T22:01:54.560 回答
6

简而言之:def __init__(self,poisonous,*args,**kwds):意味着:捕获元组中的位置参数args和字典中的关键字参数kwds。类似地,意味着:将元组和字典super(Snake,self).__init__(*args, **kwds)解包到参数中,以便它们分别传递给.argskwds__init__

如果你不使用*and**那么你就传递了argsand kwds,这意味着你得到了一个元组和一个字典。


正如你所说,你需要写:

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).

于 2013-09-11T21:57:55.010 回答
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__ 标头的可能性:(还不够好)

# 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)
关键字参数更健壮,因为它们的一些错误可以自动报告。期望您重新定义 Animal 以增加其可变性:

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)

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

于 2013-09-12T12:36:50.937 回答