我试图在Python中创建子类时理解*args
和**kwds
的使用。
我想理解为什么这段代码的行为方式。如果我在调用*args
时忽略了**kwds
和super().__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__
的错误调用(我使用args
和kwds
代替*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 = True
或num_legs = 0
是关键字参数,并且将1
设置为词典?
在num_legs
中,它将两个变量解包为s3
(在类moves
中)作为元组。
我偶然发现了这一点,因为我试图理解参数解包。提前抱歉 - 我不知道如何更好地构建这个问题。
答案 0 :(得分:9)
在Snake.__init__
中,args
是poisonous
之后所有位置参数的元组,而kwds
是除poisonous
之外的所有关键字参数的字典。致电
super(Snake,self).__init__(args,kwds)
您在args
中将moves
分配给kwds
和num_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__
。
如果您不使用*
和**
,那么您原来是args
和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)
。
答案 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)
现在你有很大的变化,关键字参数的顺序并不重要。