TL;DR
To specifically answer the question, go straight to the topic "Specifically answering the question."
In Python we can call functions, sending "positional" and/or "named" arguments, to understand this we see a function that takes 3 arguments and only returns them in a tuple:
def f1(arg1, arg2, arg3):
return (arg1, arg2, arg3)
As these arguments have no values assigned (default values), they are considered mandatory arguments, so to call this function we have to pass the 3 arguments obligatorily, but we have two ways to do this:
Calling the arguments positionally:
>>> f1(1,2,3)
(1,2,3)
Or by name:
>>> f1(arg2=2, arg1=3, arg3=1)
(3,2,1)
Note that despite the mandatory sending of the 3 arguments the order of the arguments need not be the same when we call the function with the named form.
We can also make a mix, calling part positionally and part nominally, provided that the positional form anticipates the positioned form:
# Ok
>>> f1(1, 2, arg3=99)
(1, 2, 99)
# Error
>>> f1(1, arg2=2, 3)
SyntaxError: positional argument follows keyword argument
It is important to note that we must always send the 3 arguments, without repeating them, calls as the examples below will raise error exception:
>>> f1(1, 2, arg1=10)
TypeError: f1() got multiple values for argument 'arg1'
>>> f1(arg1=1, arg3=3)
f1() missing 1 required positional argument: 'arg2'
>>> f1(1,2)
TypeError: f1() missing 1 required positional argument: 'arg3'
Call the functions sending arguments positionally or named, which is best?
Most programmers use positional form, but the named form makes calls to functions clearer, more flexible and explicit, more in accordance with the python zen, let’s see that code:
def save_txt(out_file, contents):
with open(out_file,'w+') as f:
for line in contents:
f.write(line)
This function takes as a parameter out_file
and contents
takes lines of text and writes to the output file, which of the calls below are clearer and more flexible?
lines = '''
Por que usar argumentos nomeados?
Porque é mais claro e explícito
'''
file_name = 'lines.txt'
# Chamada com argumentos de forma posicional
save_txt(file_name, lines)
# Chamadas com argumentos na forma nomeada:
save_txt(out_file=file_name, contents=lines)
# Segunda opcao para chamada nomeada
save_txt(contents=lines, out_file=file_name)
Arguments "default":
Python also allows the arguments of a function to have values assigned by default (Default), these values are assigned in the function definition:
def f1(arg1, arg2, arg3=99):
return (arg1, arg2, arg3)
It is now mandatory to send the first 2 parameters in the call to the function, but the last one is optional, the call requires the same rules for the previous calls:
Valid:
>>> f1(1,2)
(1, 2, 99)
>>> f1(1,2,arg3=88)
(1, 2, 88)
>>> f1(arg3=3, arg2=2, arg1=1)
(1, 2, 3)
Invalid:
>>> f1(arg3=77,1,2)
SyntaxError: positional argument follows keyword argument
>>> f1(arg1=1,2,3)
SyntaxError: positional argument follows keyword argument
Arbitrary number of arguments, followed by obligation of named arguments:
One can also define a function that takes a variable number of arguments followed by mandatorily named arguments, using the operator *:
def f2(*args, default=99):
return (args, default)
In this example the first arguments (necessarily positional), no matter how many, will be received in the variable args
who’s kind tuple
, note that the example function returns a tuple, so when calling it we will return a tuple that contains the tuple args
and the value of the variable default
, let’s see some examples:
>>> f2(1,2,3)
((1, 2, 3), 99)
>>> f2(1,2,3,4,5,6,'teste',[1,2,3])
((1, 2, 3, 4, 5, 6, 'teste', [1, 2, 3]), 99)
>>> f2(1,2,default=3)
((1, 2), 3)
Only named arguments, mandatorily:
For the function to accept only named arguments, on a mandatory basis, without the need to accept an arbitrary number of positional arguments through the operator *, just use that same operator, with nothing in front of it:
def f3(*, arg1, arg2, arg3):
return(arg1, arg2, arg3)
Note that now it is mandatory that the call is made through the form of named arguments and obligatory sending all of them, although we can still vary the order of the arguments, so we have as valid calls:
>>> f3(arg3=1,arg2=2,arg1=3)
(3, 2, 1)
>>> f3(arg1=3,arg2=2,arg3=1)
(3, 2, 1)
But the calls below are invalid:
>>> f3(1, 2, 3)
f3() takes 0 positional arguments but 3 were given
>>> f3(arg1=1, arg2=2)
TypeError: f3() missing 1 required keyword-only argument: 'arg3'
Named arguments, including defaults:
it is also allowed to mix the mandatory named arguments with arguments defaults, so the last call above would not produce error, for that the function would have to be defined as below:
def f3(*, arg1, arg2, arg3=99):
return(arg1, arg2, arg3)
The asterisk can be positioned anywhere, for example one could define the function with only the first parameter being positional and the other two being mandatory named, thus:
def f4(arg1, *, arg2, arg3):
return(arg1, arg2, arg3)
In this case all arguments would be mandatory, and the last two would have to be called, obligatorily named, so we would have as invalid call, for example:
>>> f4(1, arg3=3)
f4() missing 1 required keyword-only argument: 'arg2'
In order for this last call to be valid, we would have to have defined the function as follows:
def f4(arg1, *, arg2=2, arg3):
return(arg1, arg2, arg3)
Arguments named arbitrarily
With Python it is also possible for a function to receive an arbitrary number of named arguments, let’s use as an example the function media_curso()
of a free course school that receives as a parameter, the name of the course and the names and grades of the students, to calculate the arithmetic average of the class.
def media_curso(curso, **notas):
return sum(list(notas.values()))/len(notas)
Let’s call the function to the class of 4 students of the Python course:
>>> media_curso('python', john=7, Doe=9, Foo=5, Bar=6)
6.75
Let’s say a student enters late in the course and next month the function would have to be called including her grade, so the call would be:
>>>> media_curso('python', john=9, Doe=6, Foo=8, Bar=5, Lena=9 )
7.4
What the two asterisks (**) do is take all named arguments that have been sent in sequence, package in a dictionary and assign to the argument in front of you (in this case notas
), another way to call the function would be by sending an already "solved" or "unpacked" dictionary, thus:
>>> d = {'john': 9, 'Doe': 6, 'Foo': 8, 'Bar': 5, 'Lena': 9}
>>> media_curso('python', **d )
7.4
All mixed up:
As seen, we can either pass or receive arbitrary numbers of arguments, whether named or not. This is why we often see calls of the type below, especially in the use of inheritance, very common in frameworks like Django.
def some_method(self, *args, **kwargs):
# do something
# ...
super().some_method(*args, **kwargs)
Note that the convention kwargs
is to denote arguments sent through "key/Value". This is a common practice when overriding methods of frameworks where you want to change the access parameters and then call the "parent" method in the call super()
.
Specifically answering the question:
After editing the question title I noticed that nay I explicitly left a way to meet it specifically, so I elaborated, based on the above, the following solution:
def f(**args):
if len(args)>0:
if ['nome', 'cpf'] != list(args):
return 'Erro' # Ou levante uma exceção aqui
# Faça o que for necessário
return 'Ok'
Testing:
# Sem nenhum argumento:
f()
'Ok'
# Somente com um argumento
f(nome='Foo')
'Erro'
# Com dois argumentos, sendo um com o nome errado
f(nome='Foo', idade=21)
'Erro'
# Com os dois argumentos exigidos
f(nome='Foo', cpf=1234)
'Ok'
Note that with this solution, the two arguments are mandatory if one of them is delivered, that is, either you send both or you send nothing. But you can put a mandatory parameter in front, for example:
def f(cidade, **args):
if len(args)>0:
if ['nome', 'cpf'] != list(args):
return 'Erro' # Ou levante uma exceção aqui
# Faça o que for necessário
return cidade
Now you can no longer call if you do not send at least the city argument, although the previous conditions for nome
and cpf
remain valid.
I would recommend a book where that answer is one of the chapters! : -) But I think it directly addresses only half of the question’s problem: how to force all arguments. But it does not address "how to allow any improvement" - of course understanding so far, one can build a very solid application. (myself, seeing all the possibilities described so I’m wondering if there is a simpler way than the decorators I suggested)
– jsbueno
@jsbueno, After editing the title by Anderson, I had another understanding of the question, so based on what I had already written, I worked out an approach to meet, I believe with a little more time could get better.
– Sidon