Pourquoi foo (*arg, x) n'est pas autorisé en Python?

Regardez l'exemple suivant

point = (1, 2)
size = (2, 3)
color = 'red'

class Rect(object):
    def __init__(self, x, y, width, height, color):
        pass

Il serait très tentant d'appel:

Rect(*point, *size, color)

les solutions de rechange possibles seraient:

Rect(point[0], point[1], size[0], size[1], color)

Rect(*(point + size), color=color)

Rect(*(point + size + (color,)))

Mais pourquoi est Rect(*point, *size, color) non autorisé, y a-t-il une ambiguïté sémantique ou un désavantage général auquel vous pourriez penser?

EDIT: Specific Questions

pourquoi les extensions multiples *arg ne sont-elles pas autorisées dans les appels de fonction?

pourquoi les arguments de position ne sont pas autorisés après *arg les expansions?

28
demandé sur Chris 2011-06-17 18:52:12

5 réponses

autant Que je sais, c'est un choix de conception, mais il semble y avoir une logique derrière elle.

EDIT:*args la notation dans un appel de fonction a été conçue pour que vous puissiez passer dans un nuage de variables d'une longueur arbitraire qui pourrait changer entre les appels. Dans ce cas, avoir quelque chose comme f(*A, *b, c) n'a pas de sens en tant qu'appel, comme si a change la longueur tous les éléments de b sont assignés aux mauvaises variables, et c n'est pas au bon endroit soit.

garder le langage simple, puissant et standardisé est une bonne chose. Le garder en phase avec ce qui se passe réellement dans le traitement des arguments est également une très bonne chose.

pensez à la façon dont le langage décompose votre appel de fonction. Si plusieurs *arg sont autorisés dans n'importe quel ordre, comme Rect(*point, *size, color), notez que tout ce qui importe pour déballer correctement est que le point et la taille ont un total de quatre éléments. Donc point=(),size=(1,2,2,3) etcolor='red') autoriser Rect(*point, *size, color) pour travailler comme un appel correct. Fondamentalement, le langage lorsqu'il analyse le *point et le *size le traite comme une combinaison *arg tuple, donc Rect(*(point + size), color=color) plus fidèle représentation.

il n'y a jamais besoin de deux tuples d'arguments passés sous la forme *args, vous pouvez toujours la représenter comme une. Puisque l'assignation des paramètres ne dépend que de l'ordre dans cette combinaison *arg liste, il est logique de le définir comme tel.

Si vous pouvez faire des appels de fonction comme f (*A, *b), le langage demande presque de vous permettre de définir des fonctions avec plusieurs *args dans la liste des paramètres, et celles-ci ne pouvaient pas être traitées. E. g.,

 def f(*a, *b): 
     return (sum(a), 2*sum(b))

Comment f(1,2,3,4) seront-elles traitées?

je pense que c'est la raison pour laquelle pour syntactical concretess, la fonction des forces de langage appelle et définit sous la forme spécifique suivante; comme f(a,b,x=1,y=2,*args,**kwargs) qui dépend de l'ordre.

Tout ce qui existe a un sens spécifique dans une fonction définition et appel de fonction. a et b paramètres sont définis sans valeurs par défaut, suivant x et y sont des paramètres définis avec des valeurs par défaut (qui peuvent être sautées; donc viennent après les paramètres sans défaut). Ensuite, *args est peuplé comme un tuple avec tous les args remplis du reste des paramètres d'un appel de fonction qui n'étaient pas des paramètres de mots clés. Cela vient après les autres, comme cela pourrait changer la longueur, et vous ne voulez pas quelque chose qui pourrait changer longueur entre les appels pour affecter l'attribution des variables. A la fin **kwargs prend tous les arguments de mots clés qui n'ont pas été définis ailleurs. Avec ces béton définitions vous n'avez jamais besoin d'avoir plusieurs *args ou **kwargs.

9
répondu dr jimbob 2011-06-17 16:41:29

Je ne vais pas vous expliquer pourquoi le déballage de tuple multiple ne fait pas partie de Python, mais je vous ferai remarquer que vous ne faites pas le lien entre votre classe et vos données dans votre exemple.

Vous avez le code suivant:

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

mais une meilleure façon d'exprimer vos Rect objet serait comme suit:

class Rect:
    def __init__(self, point, size, color):
        self.point = point
        self.size = size
        self.color = color

r = Rect(point, size, color)

en général, si vos données sont en tuples, demandez à votre constructeur de prendre tuples. Si vos données sont dans un dict, demandez à votre constructeur de prendre un dict. Si vos données est une objet, demandez à votre constructeur de prendre un objet, etc.

En général, vous voulez travailler avec les idiomes de la langue, plutôt que d'essayer de travailler autour d'eux.


modifier Vu la popularité de cette question, je vais vous donner un décorateur qui vous permet d'appeler le constructeur comme vous voulez.

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

maintenant vous pouvez initialiser votre objet comme vous le souhaitez.

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)

bien que je ne recommande pas de l'utiliser dans la pratique (il viole le principe que vous ne devriez avoir qu'une seule façon de le faire), il sert à démontrer qu'il y a habituellement une façon de faire quelque chose en Python.

11
répondu ironchefpython 2011-06-17 16:41:58

*point dit que vous êtes de passage dans toute une série d'éléments - quelque chose comme tous les éléments dans une liste, mais pas comme une liste.

dans ce cas, vous ne pouvez pas limiter le nombre d'éléments transmis. Par conséquent, il n'existe aucun moyen pour l'interprète de savoir quels éléments de la séquence sont partie de *points et *size

Par exemple, si vous avez passé le suivant en entrée: 2, 5, 3, 4, 17, 87, 4, 0 pouvez-vous me dire lesquels de ces nombres sont représentés par *points et par *size? C'est le même problème que l'interprète visage

Espérons que cette aide

0
répondu inspectorG4dget 2011-06-17 15:04:32

Python est plein de ces petits problèmes subtils. Par exemple, vous pouvez faire:

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

Et vous ne pouvez pas faire:

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

mais en Python 3 vous pouvez maintenant.

votre suggestion sera probablement suggérée dans un PEP et intégrée ou rejetée un jour.

0
répondu e-satis 2011-06-17 15:24:16

Eh bien, en Python 2, Vous pouvez dire:

point = 1, 2
size = 2, 3
color = 'red'

class Rect(object):
    def __init__(self, (x, y), (width, height), color):
        pass

Alors vous pouvez dire:

a_rect= Rect(point, size, color)

en prenant soin que les deux premiers arguments soient des séquences de len == 2.

NB: cette capacité a été retirée de Python 3.

0
répondu tzot 2011-06-19 22:43:11