Подтвердить что ты не робот

Почему foo (* arg, x) не разрешено в Python?

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

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) не разрешено, есть ли какая-либо смысловая двусмысленность или общий недостаток, о котором вы могли бы подумать?

EDIT: конкретные вопросы

Почему множественные расширения * arg не допускаются при вызове функций?

Почему позиционные аргументы не допускаются после расширений * arg?

4b9b3361

Ответ 1

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

РЕДАКТИРОВАТЬ: нотация *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) работать как правильный вызов. В принципе, язык, когда он анализирует * точку и * размер, обрабатывает его как один комбинированный кортеж *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 заполняется как кортеж со всеми аргументами, заполненными остальными параметрами, из вызова функции, не являющегося параметрами ключевого слова. Это происходит после других, поскольку это может изменить длину, и вы не хотите что-то, что может изменить длину между вызовами, чтобы повлиять на назначение переменных. В конце ** kwargs принимает все аргументы ключевого слова, которые не были определены в другом месте. С этими конкретными определениями вам никогда не нужно иметь несколько *args или **kwargs.

Ответ 2

Я не буду говорить, почему многократная распаковка не является частью 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. Если ваши данные являются объектами, пусть ваш конструктор возьмет объект и т.д.

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


ИЗМЕНИТЬ Видя, насколько популярен этот вопрос, я дам вам декоратор, который позволяет вам вызвать конструктор, как вам нравится.

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.

Ответ 3

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

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

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

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

Ответ 4

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

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

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

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

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

Ваше предложение, вероятно, будет предложено в PEP и интегрировано или отклонено в один прекрасный день.

Ответ 5

Ну, в 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.
NB: эта возможность была удалена из Python 3.