La grande histoire des arguments de fonction en Python

Eh bien, en fait, l'histoire des arguments en Python n'est pas si grande.



J'ai toujours été surpris que pour travailler avec des arguments de fonctions Python, il vous suffit de comprendre *argset **kwargs. Et je n'ai pas été surpris en vain. Il s'avère que les disputes sont loin d'être faciles. Dans cet article, je veux donner un aperçu général de tout ce qui concerne les arguments de fonction en Python. J'espère qu'à la fin je pourrai effectivement montrer le tableau général du travail avec des arguments, et que cet article ne deviendra pas une autre publication dans laquelle le lecteur ne pourra rien trouver de nouveau. Et maintenant - au point.







La plupart des lecteurs de cet article, je pense, comprennent l'essence des arguments de fonction. Pour les débutants, laissez-moi vous expliquer que ce sont des objets envoyés à une fonction par l'initiateur de son appel. Lors du passage d'arguments à une fonction, de nombreuses actions sont effectuées, en fonction du type d'objets distribués à la fonction (objets mutables ou immuables). Un initiateur d'appel de fonction est une entité qui appelle une fonction et lui transmet des arguments. En parlant d'appels de fonctions, il y a certaines choses à considérer dont nous allons maintenant discuter.



Les arguments, dont les noms sont spécifiés lors de la déclaration de la fonction, stockent les objets passés à la fonction lors de l'appel. De plus, si quelque chose est assigné aux variables locales correspondantes des fonctions, leurs paramètres, cette opération n'affecte pas les objets immuables passés aux fonctions. Par exemple:



def foo(a):
    a = a+5
    print(a)             #  15

a = 10
foo(a)
print(a)                 #  10


Comme vous pouvez le voir, l'appel de fonction n'a aucun effet sur la variable a. C'est exactement ce qui se passe lorsqu'un objet immuable est passé à une fonction.



Et si des objets mutables sont passés à des fonctions, vous pouvez rencontrer un comportement système différent de celui ci-dessus.



def foo(lst):
    lst = lst + ['new entry']
    print(lst)                #  ['Book', 'Pen', 'new entry']

lst = ['Book', 'Pen']
print(lst)                    #  ['Book', 'Pen']
foo(lst)
print(lst)                    #  ['Book', 'Pen']


Avez-vous remarqué quelque chose de nouveau ici? Si vous répondez «non», vous avez raison. Mais si nous influencons d'une manière ou d'une autre les éléments de l'objet mutable transmis à la fonction, nous assisterons à quelque chose de différent.



def foo(lst):
    lst[1] = 'new entry'
    print(lst)                #  ['Book', 'new entry']

lst = ['Book', 'Pen']
print(lst)                     #  ['Book', 'Pen']
foo(lst)
print(lst)                     #  ['Book', 'new entry']


Comme vous pouvez le voir, l'objet du paramètre a lstété modifié après l'appel de la fonction. Cela est dû au fait que nous travaillons avec une référence à un objet stocké dans un paramètre lst. Par conséquent, la modification du contenu de cet objet est en dehors de la fonction. Vous pouvez éviter cela en faisant simplement des copies complètes de ces objets et en les écrivant dans les variables locales de la fonction.



def foo(lst):
    lst = lst[:]
    lst[1] = 'new entry'
    print(lst)                   #  ['Book', 'new entry']

lst = ['Book', 'Pen']
print(lst)                       #  ['Book', 'Pen']
foo(lst)
print(lst)                       #  ['Book', 'Pen']


Cela ne vous a pas encore surpris? Sinon, je voudrais m'assurer que vous sautez ce que vous savez et que vous passez immédiatement à un nouveau matériel pour vous. Et si oui - alors, notez mes mots, vous, en vous familiarisant avec les arguments, apprendrez des choses beaucoup plus intéressantes.



Alors, voici ce que vous devez savoir sur les arguments de fonction:



  1. L'ordre dans lequel les arguments de position sont passés aux fonctions.
  2. L'ordre dans lequel les arguments nommés sont passés aux fonctions.
  3. Attribution de valeurs d'argument par défaut.
  4. Organisation du traitement d'ensembles d'arguments de longueur variable.
  5. Déballage des arguments.
  6. Utilisation d'arguments qui ne peuvent être passés que par nom (mot-clé uniquement).


Regardons chacun de ces points.



1. Ordre de passage des arguments de position aux fonctions



Les arguments positionnels sont traités de gauche à droite. Autrement dit, il s'avère que la position de l'argument passé à la fonction est en correspondance directe avec la position du paramètre utilisé dans l'en-tête de la fonction lorsqu'elle a été déclarée.



def foo(d, e, f):
    print(d, e, f)

a, b, c = 1, 2, 3
foo(a, b, c)                  #  1, 2, 3
foo(b, a, c)                  #  2, 1, 3
foo(c, b, a)                  #  3, 2, 1


Les variables a, bet cont les valeurs 1, 2 et 3. Ces variables jouent le rôle des arguments avec lesquels la fonction est appelée foo. Ils, au premier appel de la fonction, correspondent aux paramètres d, eet f. Ce mécanisme s'applique à presque tous les 6 points ci-dessus sur ce que vous devez savoir sur les arguments de fonction en Python. L'emplacement de l'argument positionnel passé à la fonction lors de son appel joue un rôle majeur dans l'attribution de valeurs aux paramètres de la fonction.



2. Ordre de passage des arguments nommés aux fonctions



Les arguments nommés sont passés aux fonctions avec les noms de ces arguments correspondant aux noms qui leur ont été assignés lorsque la fonction a été déclarée.



def foo(arg1=0, arg2=0, arg3=0):
    print(arg1, arg2, arg3)

a, b, c = 1, 2, 3
foo(a,b,c)                          #  1 2 3
foo(arg1=a, arg2=b, arg3=c)         #  1 2 3
foo(arg3=c, arg2=b, arg1=a)         #  1 2 3
foo(arg2=b, arg1=a, arg3=c)         #  1 2 3


Comme vous pouvez le voir, la fonction fooprend 3 arguments. Ces arguments sont nommés arg1, arg2et arg3. Faites attention à la façon dont nous modifions les positions des arguments lors de l'appel de la fonction. Les arguments nommés sont traités différemment des arguments positionnels, bien que le système continue de les lire de gauche à droite. Python prend en compte les noms des arguments, pas leurs positions, lors de l'affectation des valeurs appropriées aux paramètres de fonction. En conséquence, il s'avère que la fonction produit la même chose quelles que soient les positions des arguments qui lui sont passés. Ça l'est toujours 1 2 3.



Veuillez noter que les mécanismes décrits au paragraphe 1 continuent de fonctionner ici.



3. Attribution des valeurs d'argument par défaut



Les valeurs par défaut peuvent être attribuées à des arguments nommés. Lors de l'utilisation de ce mécanisme dans une fonction, certains arguments deviennent facultatifs. La déclaration de ces fonctions ressemble à ce que nous avons considéré au point # 2. La seule différence est la façon dont ces fonctions sont appelées.



def foo(arg1=0, arg2=0, arg3=0):
    print(arg1, arg2, arg3)

a, b, c = 1, 2, 3
foo(arg1=a)                         #  1 0 0
foo(arg1=a, arg2=b )                #  1 2 0
foo(arg1=a, arg2=b, arg3=c)         #  1 2 3


Veuillez noter que dans cet exemple, nous ne transmettons pas tous les arguments à la fonction comme décrit dans sa déclaration. Dans ces cas, les paramètres correspondants reçoivent les valeurs par défaut. Continuons avec cet exemple:



foo(arg2=b)                         #  0 2 0
foo(arg2=b, arg3=c )                #  0 2 3

foo(arg3=c)                         #  0 0 3
foo(arg3=c, arg1=a )                #  1 0 3


Ce sont des exemples simples et compréhensibles d'utilisation des mécanismes décrits ci-dessus pour appeler des fonctions en leur passant des arguments nommés. Maintenant, compliquons nos expériences en combinant ce dont nous avons parlé jusqu'à présent aux points 1, 2 et 3:



foo(a, arg2=b)                      #  1 2 0
foo(a, arg2=b, arg3=c)              #  1 2 3
foo(a, b, arg3=c)                   #  1 2 3

foo(a)                              #  1 0 0
foo(a,b)                            #  1 2 0


Ici, les arguments positionnels et nommés sont utilisés lors de l'appel de la fonction. Lors de l'utilisation d'arguments positionnels, l'ordre dans lequel ils sont spécifiés continue de jouer un rôle critique dans la transmission correcte de l'entrée à la fonction.



Je voudrais ici attirer votre attention sur un détail remarquable. Cela consiste en ce que les arguments de position ne peuvent pas être spécifiés après des arguments nommés. Voici un exemple pour vous aider à mieux comprendre cette idée:



foo(arg1=a, b)
>>>
foo(arg1=a, b)
           ^
SyntaxError: positional argument follows keyword argument
foo(a, arg2=b, c)
>>>
foo(a, arg2=b, c)
              ^
SyntaxError: positional argument follows keyword argument


Vous pouvez le prendre comme règle. Les arguments positionnels ne doivent pas suivre les arguments nommés lors de l'appel d'une fonction.



4. Organisation du traitement des ensembles d'arguments de longueur variable



Ici, nous parlerons des constructions *argset **kwargs. Lorsque ces constructions sont utilisées dans une déclaration de fonction, nous prévoyons que lorsque la fonction est appelée, des ensembles d'arguments de longueurs arbitraires seront représentés sous forme de paramètres argset kwargs. Lorsque la construction est appliquée *args, le paramètre argsreçoit des arguments de position représentés sous forme de tuple. Lorsqu'il est appliqué **kwargsà l' kwargsautomne, des arguments nommés, répertoriés dans un dictionnaire.



def foo(*args):
    print(args)

a, b, c = 1, 2, 3

foo(a, b, c)                #  (1, 2, 3)
foo(a, b)                   #  (1, 2)
foo(a)                      #  (1)
foo(b, c)                   #  (2, 3)


Ce code prouve que le paramètre argsstocke un tuple contenant ce qui a été passé à la fonction lors de son appel.



def foo(**kwargs):
    print(kwargs)

foo(a=1, b=2, c=3)        #  {'a': 1, 'b': 2, 'c': 3}
foo(a=1, b=2)             #  {'a': 1, 'b': 2}
foo(a=1)                  #  {'a': 1}
foo(b=2, c=3)             #  {'b': 2, 'c': 3}


Le code ci-dessus montre que le paramètre kwargsstocke un dictionnaire de paires clé-valeur représentant les arguments nommés passés à la fonction lorsqu'elle est appelée.



Cependant, il convient de noter qu'une fonction conçue pour accepter des arguments de position ne peut pas recevoir d'arguments nommés (et vice versa).



def foo(*args):
    print(args)

foo(a=1, b=2, c=3)
>>>
foo(a=1, b=2, c=3)
TypeError: foo() got an unexpected keyword argument 'a'
#########################################################
def foo(**kwargs):
    print(kwargs)

a, b, c = 1, 2, 3
foo(a, b, c)
>>>
TypeError: foo() takes 0 positional arguments but 3 were given


Maintenant, rassemblons tout ce que nous avons analysé aux points n ° 1, n ° 2, n ° 3 et n ° 4, et expérimentons tout cela, en examinant différentes combinaisons d'arguments qui peuvent être passées aux fonctions lorsqu'elles sont appelées.



def foo(*args,**kwargs):
    print(args, kwargs)

foo(a=1,)
# () {'a': 1}

foo(a=1, b=2, c=3)
# () {'a': 1, 'b': 2, 'c': 3}

foo(1, 2, a=1, b=2)
# (1, 2) {'a': 1, 'b': 2}

foo(1, 2)
# (1, 2) {}


Comme vous pouvez le voir, nous avons un tuple argset un dictionnaire à notre disposition kwargs.



Et voici une autre règle. Cela réside dans le fait que la structure *argsne peut pas être utilisée après la structure **kwargs.



def foo(**kwargs, *args):
    print(kwargs, args)
>>>
    def foo(**kwargs, *args):
                      ^
SyntaxError: invalid syntax


La même règle s'applique à l'ordre dans lequel les arguments sont spécifiés lors de l'appel de fonctions. Les arguments positionnels ne doivent pas suivre les arguments nommés.



foo(a=1, 1)
>>>
    foo(a=1, 1)
            ^
SyntaxError: positional argument follows keyword argument
foo(1, a=1, 2)
>>>
    foo(1, a=1, 2)
               ^
SyntaxError: positional argument follows keyword argument


Lors de la déclaration de fonctions, vous pouvez combiner des arguments de position, *argset *kwagrscomme suit:



def foo(var, *args,**kwargs):
    print(var, args, kwargs)

foo(1, a=1,)                            #  1
# 1 () {'a': 1}

foo(1, a=1, b=2, c=3)                   #  2
# 1 () {'a': 1, 'b': 2, 'c': 3}

foo(1, 2, a=1, b=2)                     #  3
# 1 (2,) {'a': 1, 'b': 2}
foo(1, 2, 3, a=1, b=2)                  #  4
# 1 (2, 3) {'a': 1, 'b': 2}
foo(1, 2)                               #  5
# 1 (2,) {}


Lors de la déclaration d'une fonction, foonous avons supposé qu'elle devait avoir un argument positionnel obligatoire. Il est suivi d'un ensemble d'arguments positionnels de longueur variable, et cet ensemble est suivi d'un ensemble d'arguments nommés de longueur variable. Sachant cela, nous pouvons facilement "décrypter" chacun des appels de fonction ci-dessus.



La 1fonction reçoit des arguments 1et a=1. Ce sont, respectivement, des arguments positionnels et nommés. 2Est une variété 1. Ici, la longueur de l'ensemble des arguments positionnels est égale à zéro.



Dans 3nous passons des fonctions 1, 2et a=1,b=2. Cela signifie qu'il accepte maintenant deux arguments positionnels et deux arguments nommés. Selon la déclaration de fonction, il s'avère que1pris comme argument positionnel requis, 2entre dans un ensemble d'arguments positionnels de longueur variable a=1et b=2se termine dans un ensemble d'arguments nommés de longueur variable.



Pour appeler correctement cette fonction, nous devons lui passer au moins un argument positionnel. Sinon, nous ferons face à une erreur.



def foo(var, *args,**kwargs):
    print(var, args, kwargs)

foo(a=1)
>>>
foo(a=1)
TypeError: foo() missing 1 required positional argument: 'var'


Une autre variante de cette fonction est une fonction qui déclare qu'elle prend un argument positionnel requis et un argument nommé, suivis par des ensembles de longueur variable d'arguments positionnels et nommés.



def foo(var, kvar=0, *args,**kwargs):
    print(var, kvar, args, kwargs)

foo(1, a=1,)                               #  1
# 1 0 () {'a': 1}

foo(1, 2, a=1, b=2, c=3)                   #  2
# 1 0 () {'a': 1, 'b': 2, 'c': 3}

foo(1, 2, 3, a=1, b=2)                     #  3
# 1 2 () {'a': 1, 'b': 2}

foo(1, 2, 3, 4, a=1, b=2)                  #  4
# 1 2 (3,) {'a': 1, 'b': 2}

foo(1, kvar=2)                             #  5
# 1 2 () {}


Les appels à cette fonction peuvent être "déchiffrés" de la même manière que lors de l'analyse de la fonction précédente.



Lors de l'appel de cette fonction, il faut lui passer au moins un argument de position. Sinon, nous rencontrerons une erreur:



foo()
>>>
foo()
TypeError: foo() missing 1 required positional argument: 'var'
foo(1)
# 1 0 () {}


Veuillez noter que l'appel foo(1)fonctionne correctement. Le point ici est que si une fonction est appelée sans spécifier de valeur pour un argument nommé, la valeur lui est automatiquement affectée.



Et voici quelques autres erreurs qui peuvent être rencontrées si cette fonction est appelée de manière incorrecte:



foo(kvar=1)                             #  1
>>>
TypeError: foo() missing 1 required positional argument: 'var'
foo(kvar=1, 1, a=1)                      #  2
>>>
SyntaxError: positional argument follows keyword argument
foo(1, kvar=2, 3, a=2)                   #  3
>>>
SyntaxError: positional argument follows keyword argument


Faites particulièrement attention à l'erreur d'exécution 3.



5. Déballage des arguments



Dans les sections précédentes, nous avons expliqué comment collecter des ensembles d'arguments passés aux fonctions dans des tuples et des dictionnaires. Et ici, nous allons discuter de l'opération inverse. À savoir, nous analyserons le mécanisme qui vous permet de décompresser les arguments fournis à l'entrée de la fonction.



args = (1, 2, 3, 4)
print(*args)                  #  1 2 3 4
print(args)                   #  (1, 2, 3, 4)

kwargs = { 'a':1, 'b':2}
print(kwargs)                 #  {'a': 1, 'b': 2}
print(*kwargs)                #  a b


Vous pouvez décompresser les variables en utilisant la syntaxe *et **. C'est ainsi qu'ils sont utilisés lors du passage de tuples, de listes et de dictionnaires à des fonctions.



def foo(a, b=0, *args, **kwargs):
    print(a, b, args, kwargs)

tup = (1, 2, 3, 4)
lst = [1, 2, 3, 4]
d = {'e':1, 'f':2, 'g':'3'}

foo(*tup)             # foo(1, 2, 3, 4)
# 1 2 (3, 4) {}

foo(*lst)             # foo(1, 2, 3, 4)
# 1 2 (3, 4) {}

foo(1, *tup)          # foo(1, 1, 2, 3, 4)
# 1 1 (2, 3, 4) {}

foo(1, 5, *tup)       # foo(1, 5, 1, 2, 3, 4)
# 1 5 (1, 2, 3, 4) {}

foo(1, *tup, **d)     # foo(1, 1, 2, 3, 4 ,e=1 ,f=2, g=3)
# 1 1 (2, 3, 4) {'e': 1, 'f': 2, 'g': '3'}

foo(*tup, **d)         # foo(1, 1, 2, 3, 4 ,e=1 ,f=2, g=3)
# 1 2 (3, 4) {'e': 1, 'f': 2, 'g': '3'}
d['b'] = 45
foo(2, **d)             # foo(1, e=1 ,f=2, g=3, b=45)
# 2 45 () {'e': 1, 'f': 2, 'g': '3'}


Déconstruisez chacun des appels de fonction présentés ici en utilisant la décompression d'arguments et notez à quoi ressembleraient les appels correspondants sans utiliser *et **. Essayez de comprendre ce qui se passe lorsque vous effectuez ces appels et comment les différentes structures de données sont décompressées.



En expérimentant avec des arguments de décompression, vous pouvez rencontrer une nouvelle erreur:



foo(1, *tup, b=5)
>>>
TypeError: foo() got multiple values for argument 'b'
foo(1, b=5, *tup)
>>>
TypeError: foo() got multiple values for argument 'b'


Cette erreur se produit en raison d'un conflit entre l'argument nommé ,, b=5et l'argument positionnel. Comme nous l'avons découvert dans la section # 2, l'ordre des arguments nommés n'a pas d'importance une fois passé. En conséquence, la même erreur se produit dans les deux cas.



6. Utilisation d'arguments qui ne peuvent être transmis que par nom (mot clé uniquement)



Dans certains cas, vous devez faire en sorte que la fonction accepte les arguments nommés requis. Si, lors de la déclaration d'une fonction, ils décrivent des arguments qui ne peuvent être passés que par leur nom, alors ces arguments doivent lui être transmis à chaque fois qu'elle est appelée.



def foo(a, *args, b):
    print(a, args, b)

tup = (1, 2, 3, 4)

foo(*tup, b=35)
# 1 (2, 3, 4) 35

foo(1, *tup, b=35)
# 1 (1, 2, 3, 4) 35

foo(1, 5, *tup, b=35)
# 1 (5, 1, 2, 3, 4) 35

foo(1, *tup, b=35)
# 1 (1, 2, 3, 4) 35

foo(1, b=35)
# 1 () 35

foo(1, 2, b=35)
# 1 (2,) 35

foo(1)
# TypeError: foo() missing 1 required keyword-only argument: 'b'

foo(1, 2, 3)
# TypeError: foo() missing 1 required keyword-only argument: 'b'


Comme vous pouvez le voir, on s'attend à ce que la fonction reçoive nécessairement un argument nommé b, qui, dans la déclaration de fonction, est spécifié après *args. Dans ce cas, dans la déclaration de fonction, vous pouvez simplement utiliser un symbole *, après quoi, séparés par des virgules, il y a des identificateurs d'arguments nommés qui ne peuvent être passés à la fonction que par leur nom. Une telle fonction ne serait pas conçue pour accepter un ensemble d'arguments positionnels de longueur variable.



def foo(a, *, b, c):
    print(a, b, c)

tup = (1, 2, 3, 4)

foo(1, b=35, c=55)
# 1 35 55

foo(c= 55, b=35, a=1)
# 1 35 55

foo(1, 2, 3)
# TypeError: foo() takes 1 positional argument but 3 were given

foo(*tup, b=35)
# TypeError: foo() takes 1 positional argument but 4 positional arguments (and 1 keyword-only argument) were given

foo(1, b=35)
# TypeError: foo() takes 1 positional argument but 4 positional arguments (and 1 keyword-only argument) were given


La fonction déclarée dans l'exemple précédent prend un argument positionnel et deux arguments nommés, qui ne peuvent être passés que par nom. Cela conduit au fait que pour qu'une fonction soit appelée correctement, elle doit passer les deux arguments nommés. Après cela, *vous pouvez également décrire les arguments nommés, qui reçoivent les valeurs par défaut. Cela nous donne une certaine liberté lors de l'appel de telles fonctions.



def foo(a, *, b=0, c, d=0):
    print(a, b, c, d)

foo(1, c=55)
# 1 0 55 0

foo(1, c=55, b=35)
# 1 35 55 0

foo(1)
# TypeError: foo() missing 1 required keyword-only argument: 'c'


Notez que la fonction peut être appelée normalement sans lui transmettre d'arguments bet dparce qu'elle a reçu des valeurs par défaut.



Résultat



Peut-être avons-nous, en effet, une très longue histoire d'arguments. J'espère que les lecteurs de ce matériel ont appris quelque chose de nouveau par eux-mêmes. Et au fait, l'histoire des arguments de fonction en Python continue. Peut-être en parlerons-nous plus tard.



Avez-vous appris quelque chose de nouveau sur les arguments de fonction en Python à partir de ce matériel?






All Articles