Почему foo (* arg, x) не разрешено в Python? - PullRequest
28 голосов
/ 17 июня 2011

Посмотрите на следующий пример

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 ]

11 голосов
/ 17 июня 2011

Я не буду говорить о том, почему распаковка нескольких кортежей не является частью 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)

Как правило, если ваши данные находятся в кортежах, пусть ваш конструктор принимает кортежи. Если ваши данные находятся в поле зрения, пусть ваш конструктор определит его. Если ваши данные являются объектом, пусть ваш конструктор возьмет объект и т. Д.

В общем, вы хотите работать с идиомами языка, а не пытаться обходить их.


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 обычно есть способ сделать что-либо.

9 голосов
/ 17 июня 2011

Насколько я знаю, это был выбор дизайна, но, похоже, за этим стоит логика.

EDIT: нотация *args в вызове функции была разработана таким образом, чтобы вы могли передавать кортеж переменных произвольной длины, которые могут изменяться между вызовами. В этом случае наличие чего-то вроде f (* a, * b, c) не имеет смысла как вызов, как будто a изменяет длину, все элементы b присваиваются неправильным переменным, а c не либо в нужном месте.

Хорошо, когда язык простой, мощный и стандартизированный. Синхронизация с тем, что фактически происходит при обработке аргументов, также очень хорошая вещь.

Подумайте, как язык распаковывает вызов вашей функции. Если несколько *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), язык почти просит позволить вам определять функции с несколькими * аргументами в списке параметров, и они не могут быть обработаны. Например.,

 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 заполняется как кортеж со всеми аргументами, заполненными остальными параметрами из вызова функции, которые не были параметрами ключевого слова. Это происходит после других, так как это может изменить длину, и вы не хотите, чтобы что-то, что могло изменить длину между вызовами, влияло на назначение переменных. В конце ** kwargs принимает все ключевые аргументы, которые не были определены в другом месте. С этими конкретными определениями вам никогда не нужно иметь несколько *args или **kwargs.

0 голосов
/ 20 июня 2011

Ну, в 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.

0 голосов
/ 17 июня 2011

Python полон этих тонких глюков.Например, вы можете сделать:

first, second, last = (1, 2, 3)

А вы не можете сделать:

first, *others = (1, 2, 3)

Но в Python 3 вы теперь можете.

Ваше предложение, вероятно, будет предложено в ПКП и интегрировано или отклонено однажды.

0 голосов
/ 17 июня 2011

*point говорит, что вы передаете целую последовательность элементов - что-то вроде всех элементов в списке, но не в виде списка.

В этом случае вы не можете ограничить количество элементовпоэтому интерпретатор не может знать, какие элементы последовательности являются частью *points, а какие - *size

Например, если вы передали следующее в качестве входных данных:2, 5, 3, 4, 17, 87, 4, 0, можете ли вы сказать мне, какие из этих чисел представлены *points, а какие *size?Это та же проблема, с которой столкнулся бы и переводчик

Надеюсь, это поможет

...