请看以下示例
point = (1, 2)
size = (2, 3)
color = 'red'
class Rect(object):
def __init__(self, x, y, width, height, color):
pass
打电话会非常诱人:
Rect(*point, *size, color)
可能的解决方法是:
Rect(point[0], point[1], size[0], size[1], color)
Rect(*(point + size), color=color)
Rect(*(point + size + (color,)))
但是为什么Rect(*point, *size, color)
不被允许,你能想到的是否存在任何语义模糊或普遍缺点?
为什么在函数调用中不允许多个* arg扩展?
为什么在* arg扩展后不允许使用位置参数?
答案 0 :(得分:11)
我不打算说为什么多元组解包不是Python的一部分,但我会指出你的类不符合你的例子中的数据。
您有以下代码:
point = (1, 2)
size = (2, 3)
color = 'red'
class Rect(object):
def __init__(self, x, y, width, height, color):
self.x = x
self.y = y
self.width = width
self.height = height
self.color = color
但表达Rect对象的更好方法如下:
class Rect:
def __init__(self, point, size, color):
self.point = point
self.size = size
self.color = color
r = Rect(point, size, color)
通常,如果您的数据位于元组中,请让构造函数使用元组。如果您的数据在dict中,请让您的构造函数采用dict。如果您的数据是对象,请让构造函数获取对象等
一般来说,你想要使用语言的习语,而不是尝试解决它们。
EDIT 看到这个问题有多受欢迎,我会给你一个装饰器,让你可以随意调用构造函数。
class Pack(object):
def __init__(self, *template):
self.template = template
def __call__(self, f):
def pack(*args):
args = list(args)
for i, tup in enumerate(self.template):
if type(tup) != tuple:
continue
for j, typ in enumerate(tup):
if type(args[i+j]) != typ:
break
else:
args[i:i+j+1] = [tuple(args[i:i+j+1])]
f(*args)
return pack
class Rect:
@Pack(object, (int, int), (int, int), str)
def __init__(self, point, size, color):
self.point = point
self.size = size
self.color = color
现在您可以按照自己喜欢的方式初始化对象。
r1 = Rect(point, size, color)
r2 = Rect((1,2), size, color)
r3 = Rect(1, 2, size, color)
r4 = Rect((1, 2), 2, 3, color)
r5 = Rect(1, 2, 2, 3, color)
虽然我不建议在实践中使用它(它违反了你应该只有一种方法来实现它的原则),但它确实有助于证明通常有一种方法可以用Python做任何事情。
答案 1 :(得分:9)
据我所知,这是一个设计选择,但它背后似乎有一个逻辑。
编辑:函数调用中的*args
符号的设计使您可以传入任意长度的变量元组,这些变量可能会在调用之间发生变化。在这种情况下,像f(* a,* b,c)这样的东西作为一个调用没有意义,好像a
改变了长度b的所有元素被分配给错误的变量,并且c isn也是在正确的地方。
保持语言简单,强大和标准化是一件好事。保持与处理参数的实际情况同步也是一件非常好的事情。
考虑语言如何解压缩函数调用。如果*arg
之类的任何顺序允许多个Rect(*point, *size, color)
,请注意正确解压缩的所有重点是该点和大小总共有四个元素。因此,point=()
,size=(1,2,2,3)
和color='red')
会允许Rect(*point, *size, color)
作为正确的呼叫。基本上,解析* point和* size时的语言将其视为一个组合*arg
元组,因此Rect(*(point + size), color=color)
更忠实于表示。
永远不需要在*args
形式中传递两个参数元组,您始终可以将其表示为一个。由于参数的赋值仅取决于此组合*arg
列表中的顺序,因此将其定义为有意义。
如果你可以像f(* a,* b)那样进行函数调用,那么语言几乎可以让你在参数列表中定义带有多个* args的函数,而这些函数无法处理。如,
def f(*a, *b):
return (sum(a), 2*sum(b))
如何处理f(1,2,3,4)
?
我认为这就是为什么语法具体性,语言强制函数调用和定义具有以下特定形式;像f(a,b,x=1,y=2,*args,**kwargs)
这是依赖于订单的。
在函数定义和函数调用中,其中的所有内容都具有特定含义。 a
和b
是没有默认值定义的参数,下一个x
和y
是使用默认值定义的参数(可以跳过;所以在无默认参数之后) 。接下来,*args
被填充为元组,其中所有args都填充了函数调用中不是关键字参数的其余参数。这是在其他人之后,因为这可能会改变长度,并且您不希望某些内容可以改变调用之间的长度以影响变量的赋值。最后** kwargs获取所有未在其他地方定义的关键字参数。通过这些具体定义,您永远不需要拥有多个*args
或**kwargs
。
答案 2 :(得分:0)
*point
表示您传递了一系列项目 - 类似于列表中的所有元素,但不是列表。
在这种情况下,您无法限制传入的元素数量。因此,解释器无法知道序列的哪些元素属于*points
,哪些属于{{1} }
例如,如果您将以下内容作为输入传递:*size
,您能告诉我,这些数字中的哪一个由2, 5, 3, 4, 17, 87, 4, 0
表示,哪些数字由*points
表示?这也是解释器面临的同样问题
希望这有帮助
答案 3 :(得分:0)
Python充满了这些微妙的故障。例如,你可以这样做:
first, second, last = (1, 2, 3)
你做不到:
first, *others = (1, 2, 3)
但是在Python 3中你现在可以。
您的建议可能会在PEP中被建议,并且有一天会被整合或拒绝。
答案 4 :(得分:0)
嗯,在Python 2中,您可以说:
point = 1, 2
size = 2, 3
color = 'red'
class Rect(object):
def __init__(self, (x, y), (width, height), color):
pass
然后你可以说:
a_rect= Rect(point, size, color)
注意前两个参数是len == 2的序列 注意:此功能已从Python 3中删除。