为什么Python中不允许使用foo(* arg,x)?

时间:2011-06-17 14:52:12

标签: python function variadic-functions

请看以下示例

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扩展后不允许使用位置参数?

5 个答案:

答案 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)这是依赖于订单的。

在函数定义和函数调用中,其中的所有内容都具有特定含义。 ab是没有默认值定义的参数,下一个xy是使用默认值定义的参数(可以跳过;所以在无默认参数之后) 。接下来,*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中删除。