Lesson 12: Function in Python: Defining, Submitting, and Matching Arguments

This lesson introduces the concept of function in the Python programming language.

Level: Medium

Headlines

Lesson 12: Function in Python: Defining, Submitting, and Matching

Arguments
Introduction
Syntax
Name and domain spaces
Submit an argument
Matching arguments

Introduction

A function is a block of commands that need to be called to execute, and this call can be performed more than once. The function can receive values ​​when called and return a value as a result if necessary.

It is an alternative function for duplicate parts of the program, which can be avoided by changing the program once and writing it several times and calling and executing it. Using functions increases the reusability of code and reduces redundancy. Functions are tools to break program logic into smaller executives to facilitate the construction of large programs.

The concept of function is introduced into mathematics from programming and means a tool that can calculate the result (or output) based on input data. If all the basic parts of the program are developed with such an approach, this method of development is called “Functional Programming”, the way you see it in programming languages ​​like Scala, Scheme, Lisp and Haskell. Functions are also available in other languages ​​such as C, Pascal, and C ++, although these languages ​​fall into the “imperative programming” category, and their approach to the function is quite different from what exists in functional programming languages. has it. Explaining the difference between the two is a bit difficult, but you will surely find out by programming with the languages ​​in these two categories. 🙂

The approach of this tutorial to the function is based on declarative programming. The Python language also offers functional programming capabilities, along with other approaches such as declaration or object-orientation. For functional programming using Python, see Functional Programming in Python by O’Reilly (download pdf – free).

In addition to the term function in programming, there is another similar term as “procedure”. Procedures and functions are similar in structure, except that procedures do not return values. In Python, there is no syntax for defining routines, but functions that do not return a value are also called routines because the functions in Python return a value under any circumstances, even if this value is None.

Syntax

Functional Syntax in Python Programming Language Like any other compound command, it contains a header and a body – Lesson 6. The header section contains the def keyword, a user-preferred name, and a parenthesis, which indicates where the function parameters are located. Each function can accept any, one or more parameters:

def function_name(param1, param2,... paramN):
    statements

As has been said many times, everything is an object in Python, when the program execution reaches the def keyword, first a function object is created and then it is referenced by the function name (here: function_name):

>>> def func_name():
...     pass
...
>>>

>>> type(func_name)
<class 'function'>
tip

PEP 8 Recommendation: The function name should consist of lowercase letters whose words are separated by underscores. Such as: my_function. MixCase mode such as: myFunction is also valid as long as the function names are written with the same pattern throughout the code.

The body will not run until the function is called. The function name + parentheses is used to call the function, and if parameters are included in the function definition, arguments corresponding to these parameters must be sent when calling:

function_name(arg1, arg2,... argN)
consideration

In the discussion of functions, the variables that are defined in the function header are called parameters (Parameter) and the data that is sent when the function is called is called Argument. For each parameter considered in the function definition, a similar argument must be passed to it. In fact, arguments are values ​​that we want to assign to the parameters of a function. There is no need for similar arguments and parameters to be synonymous, but the existence of a synonym makes the code more readable.

The body of the function can also contain the return keyword. In fact, return is a command that is given anywhere in the body, the execution of the function stops at that point and returns a value (in Python, of course, it is correct to say: object) as a result to the function call:

def function_name(param1, param2,... paramN):
    ...
    return value

In the example code above, value is the value returned by the return to the calling location. value can not be explicitly a value but an expression such as: param1 ** 2 or param1> 3, etc., in which case the result of the expression is evaluated first and then returned. If no value is specified, None will be returned:

>>> def my_pow(x, y):
...     return x**y
...
>>>

>>> a = 2

>>> b = 3
>>>

>>> my_pow(a, b)
8
>>>
tip

If the return command is not written at the end of the function, the Python interpreter implicitly considers the return None command. Therefore, by calling such functions in the Python language, the value None will be returned (albeit a rather boring one) after the instructions inside the body are fully executed.

tip

Recommendation PEP 8: Operate uniformly to return the value. If you use compound statements such as if / else, either none of the sections should explicitly return, or if at least one section needs to return a value, the rest of the sections should return a value, even if this value is None. To be taken:

YES:

def foo(x):
    if x >= 0:
        return math.sqrt(x)
    else:
        return None


NO:

def foo(x):
    if x >= 0:
        return math.sqrt(x)

 In the Python programming language, a function is a “first-class” entity, which means that a function can be dynamically created or destroyed like other objects, passed as an argument to other functions, returned as a result, and so on. The result can be defined as a function inside the body of control statements (while, if, etc.) or inside the body of another function:

>>> def outer(num1):
...     def inner_increment(num1):  
# hidden from outer code
...         return num1 + 1
...     num2 = inner_increment(num1)
...     print(num1, num2)
...
>>>

>>> outer(1)
1 2

It’s a good idea to use “Docstring” in functions to help better document and read the program – Lesson 6:

def function_with_docstring(param1, param2):
    """Example function with types documented in the 
docstring.

    Args:
        param1 (int): The first parameter.
        param2 (str): The second parameter.

    Returns:
        bool: The return value. True for success, 
False otherwise.
    """

Name and domain spaces

There are many names in each Python program, for example: variables, function names, class names, and so on. Obviously, letters need to be unique to identify objects, which is difficult to do in an even small program. In Python, there is a structure called “Namespaces” to categorize and prevent letters from interfering. Each space contains the name of a part of the letters in the program. In general, Python namespaces are depicted on three levels: “Local”, “Global” and Built-in:

nested-namespaces-pythonEach Python module forms a global space name that is isolated from other module names. Spatial Name All modules are created within a larger spatial space known as the Built-in space, and the names of all the ready-made functions such as open () that we used to use are located in this space. Your structure within the namespace enables us to access built-in functions anywhere in the program without having to import a specific module.

Each module can contain a number of functions and classes. By calling each function, a local space name for that function is created within the space module of the corresponding module, and when the function is executed, it disappears. The same thing happens with classes. Accordingly, we can create different variables inside the function, but with the same name as the variables outside the function in the module, because they are located in two different space names. The function also had access.

We said that space is the name of the modules isolated from each other. Therefore, to access the names in other modules, we must first import those modules, in which case they can be accessed using the module name – as a prefix. For example, obtaining the name getcwd, which refers to a function from the OS module space, is shown in the example code below:

>>> import os

>>> os.getcwd()
'/home/saeid'

But what about using the letters of a module within itself? Where there are other namespaces such as functions, but there is no prefix such as module name that can distinguish letters within these different spaces. To understand how each module name is accessed anywhere in the same module, we are introduced to another concept called “Scope“. Generally, domains are areas of the program where a name can be used without the use of any prefix and without interfering with other names. The domain discussion is only within each module.

Field rules:

  • The body of a module – meaning the areas outside the body of functions and classes – is the Global Scope. Note that the word “global” in the domain (or space name) discussion only refers to all the code inside each module, not the entire program. In general, remember the module wherever you hear (or read) the word Global in the Python language:
# This is a global variable
a = 0

if a == 0:
    # This is still a global variable
    b = 1

In the code example above, the domain definition for both variables a and b is global. The body of control commands does not have a separate space name, and the definition of the variable in these areas of the program falls within the global domain.

  • The body of each function is a Local Scope, and by default all variables generated within the functions are located within the local domain unless specified using global or nonlocal keywords. If we want to do within the attribution function to one of the names in the global domain, we must use the global command. Note the sample codes below:
def my_function(c):
    # this is a local variable
    d = 3
>>> a = 0
>>>

>>> def my_function():
...    a = 3
...    print(a)
...
>>>

>>> a
0

>>> my_function()
3

>>> a
0
>>>
>>> a = 0
>>>

>>> def my_function():
...     global a
...     a = 3
...     print(a)
...
>>>

>>> a
0

>>> my_function()
3

>>> a
3
>>>

It does not make a difference in nested functions either, each function that is called creates a separate space for it and will have its own local domain. The nonlocal command is provided in Python 3 and is used in nested functions. When we want to assign an internal function inside the body to a name defined in one of its external functions, we must use this command to specify the desired name:

>>> def outer():
...     x = 1
...     def inner():
...         x = 2
...         print("inner:", x)
...     inner()
...     print("outer:", x)
...
>>>

>>> outer()
inner: 2
outer: 1
>>>
>>> def outer():
...     x = 1
...     def inner():
...         nonlocal x
...         x = 2
...         print("inner:", x)
...     inner()
...     print("outer:", x)
...
>>>

>>> outer()
inner: 2
outer: 2
>>>
  • When using a variable, the Python interpreter must first identify its domain and space name in order to find the object to which the variable refers. Suppose a variable inside a phrase is used in the body of a function. It goes on to finally examine the global scope of the module and then the Built-in; If no result, a NameError exception occurs:
>>> x = 0
>>>

>>> def outer():
...     x = 1
...     def inner():
...         print(x)
...     inner()
...
>>> outer()
1
>>> x = 0
>>>

>>> def outer():
...     def inner():
...         print(x)
...     inner()
...
>>> outer()
0
>>> x = 0
>>>

>>> def outer():
...     def inner():
...         print(z)
...     inner()
...
>>> outer()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in outer
  File "<stdin>", line 3, in inner
NameError: name 'z' is not defined
>>>

Submit an argument

By automatically submitting an argument to the function, local variables are created by assigning argument objects to the names of the parameters in the function header:

>>> def f(a):
...     print(a*a)
...
>>>

>>> b = 3

>>> f(b)
9

By calling the function f in the above code instance, the local variable a is created, which refers to the correct object 3.

Note that by assigning a new object to the function parameters, sending an argument is practically ineffective:

>>> def f(a):
...     a = 2
...     print(a*a)
...
>>> b = 3

>>> f(b)
4

The important thing in submitting an argument is to pay attention to how it is!

Among programming languages, there are two common ways to send an argument: “by value” and “by reference“. In the by value method, a copy of the value of the argument is sent to the function, and as a result, by changing the value of the corresponding parameter in the function, the value of the argument sent outside the function remains unchanged. Consider the Python example below:

>>> def f(a):
...     a = 2
...     print(a*a)
...
>>> b = 3

>>> f(b)
4

>>> b
3

As can be seen in the code example above, the value of variable b remains unchanged.

But in the by reference method, instead of sending a copy of the value of the argument, a reference is sent from the argument to the function. It can be assumed that the corresponding parameter in the function is the same argument outside the function. As a result, by changing the value of the corresponding parameter in the function, the value of the argument outside the function also changes. Consider the Python example below:

>>> def f(a):
...    a[0] = 3
...    print(a)
...
>>> b = [1, 2]

>>> f(b)
[3, 2]

>>> b
[3, 2]

These are two common methods in programming languages, but what is it like to send a parameter specifically in the Python programming language? In Python, everything is an object, and as a result, arguments are passed “by reference” in any situation.

And if the question is what is the reason for the difference in behavior in the previous two examples? We need to know that the reason is related to the nature of the submitted argument objects. Sending immutable objects such as boolean, numbers, strings, and tuple types to the function will cause behavior similar to the by value method, but not sending mutable objects such as list, dictionary, and collection types. Note the pictures below:

l12-python-passing-arguments-01 l12-python-passing-arguments-02Variable objects in Python are objects whose value can be changed without changing their id (). The output of the id‍‍ () function for each object represents the unique identifier of that object, which is actually its address in memory [Python Documents] – Lesson Five.

To prevent changeable objects within the function, you can create a copy of this type of object as described in Lesson 8 and then send it to the function as an argument:

>>> def f(a):
...     a[0] = 3
...     print(a)
...

>>> b = [1, 2]

>>> f(b[:])      # Pass a copy
[3, 2]

>>> b
[1, 2]

In the example code above, since all members of the variable b list object are all immutable types, a Shallow Copy of the object is sufficient, but otherwise a Deep Copy of the object must be sent – Lesson Eight.

Of course, sometimes we really need to have the modified value of the variable sent to the function outside the function as well. For this purpose, in some programming languages, it is possible to send by reference according to the request of the programmer. For example in php this is done by putting an & after the desired parameter:

<?php
function foo(&$var)
{
    $var++;
}

$a=5;
foo($a);
// $a is 6 here
?>

There is no such feature in Python, at least for immutable objects! But it can be covered using the ability to return multiple objects with the return command. Using this method, any number of required parameters can be transferred out of the function:

>>> def multiple(x, y):
...     x = 2
...     y = [3, 4]
...     return x, y
...

>>> X = 1

>>> Y = [1, 2]
>>>

>>> X, Y = multiple(X, Y)
>>>

>>> X
2

>>> Y
[3, 4]

Note that in this case, the return statement returns all of these objects in the form of a tuple object:

>>> multiple(X, Y)
(2, [3, 4])

Matching arguments

Earlier it was mentioned that the number of arguments submitted must match the parameters in the function header:

>>> def f(a, b, c):
...     pass
...
>>>

>>> f(1, 2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: f() missing 1 required positional argument: 
'c'
>>>

>>> f(1, 2, 3, 4)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: f() takes 3 positional arguments but 
4 were given
>>>

In the following, we present the types of acceptable Python syntaxes in matching arguments (Argument Matching) with function parameters:

  • The usual syntax we have used so far is to explicitly send a unique argument for each parameter. The matching operation in this syntax is based on the position of the arguments, so the order of the arguments must correspond to the order of the parameters in the function header:
>>> def f(a, b, c):
...     print("a= ", a)
...     print("b= ", b)
...     print("c= ", c)
...

>>> f(1, 2, 3)
a=  1
b=  2
c=  3

>>> f("one", 2, [3,33,333])
a=  one
b=  2
c=  [3, 33, 333]
>>>
  • Name Syntax = Value In this syntax, arguments are assigned to the name of the parameters, and since the matching operation is based on the name of the parameters, the position or order of the arguments no longer matters:
>>> def f(a, b, c):
...     print(a, b, c)
...

>>> f(a=1, c=3, b=2)
1 2 3

These two syntaxes can also be used in combination. It is only necessary to note that the arguments whose matching action depends on the situation – in the order of the previous cases – first. Consider the example below:

>>> def f(a, b, c):
...     print("a= ", a)
...     print("b= ", b)
...     print("c= ", c)
...

>>> f(1, c=3, b=2)
a=  1
b=  2
c=  3

>>> f(1, 2, c=3)
a=  1
b=  2
c=  3
>>>

For the example function above, the following call modes are incorrect:

>>> f(c=3, b=2, 1)
  File "<stdin>", line 1
SyntaxError: positional argument follows keyword argument

>>> f(a=1, 2, c=3)
  File "<stdin>", line 1
SyntaxError: positional argument follows keyword argument

>>> f(a=1, 2, 3)
  File "<stdin>", line 1
SyntaxError: positional argument follows keyword argument

>>> f(2, a=1, c=3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: f() got multiple values for argument 'a'
  • Iterable * syntax In this syntax, an object of iterable type (iterable – lesson 9) such as strings, tuples, lists, etc., which is marked by an asterisk * * character, is sent to the function. In this case, based on the position order, the members inside the repeatable object are assigned to the function parameters:
>>> def f(a, b, c):
...     print("a= ", a)
...     print("b= ", b)
...     print("c= ", c)
...
>>>

>>>my_list = [1, 2, 3]
>>>

>>> f(*my_list)
a=  1
b=  2
c=  3
>>>

>>> my_list_2 = [1, 2, 3, 4]
>>>

>>> f(*my_list_2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: f() takes 3 positional arguments but 
4 were given
>>>
  • Syntax dict ** In this syntax, a dictionary object marked with two asterisk ** characters is sent to the function. The keys of this dictionary object must have the same name as the parameters defined in the function header. After calling the function, this dictionary object opens and based on the name of the key in the key pairs: the value inside it, the parameters of the function are set:
>>> def f(a, b, c):
...     print(a, b, c)
...

>>> b = {'a':1, 'c':3, 'b':2}

>>> f(**b)
1 2 3

These four syntaxes discussed the determination of arguments when calling a function, and in all of them the number of arguments submitted must be equal to the number of parameters defined in the function header. The value of the dictionary object is equal to the number of function parameters.

In the following, we will present the syntaxes in this field that are involved in determining the function parameters.

  • The usual syntax we have used so far is to explicitly define each parameter:
>>> def f(a, b, c):
...     print(a, b, c)
...

>>> f(1, 2, 3)
1 2 3
  • Syntax Set the default value for the parameters. When defining any parameter in the function definition, a value can also be assigned to it; In this case, if a similar argument is not sent with that parameter, the default value of that parameter will be considered. Such parameters are also called optional:
>>> def chaap(text=None):
...     if text:
...         print(text)
...     else:
...         print("Nothing!")
...
>>>

>>> chaap("Python :)")
Python :)
>>>

>>> chaap()
Nothing!
>>>

The parameter with the default value can be defined next to the mandatory parameters (without the default value), in which case the parameters with the default value must be placed at the end:

>>> def f(a, b=2, c=3):     
# a required, b and c optional
        print(a, b, c)
>>> f(1)          # Use defaults
1 2 3

>>> f(a=1)
1 2 3
>>> f(1, 4)       # Override defaults
1 4 3

>>> f(1, 4, 5)
1 4 5
>>> f(1, c=6)     # Choose defaults
1 2 6
  • The syntax name * receives all submitted arguments in the form of a topple object – this feature is very helpful when the number of submitted arguments varies:
>>> def f(*name):
...     print(type(name))
...     print(name)
...
>>>

>>> f(1)
<class 'tuple'>
(1,)

>>> f(1, 2, 3)
<class 'tuple'>
(1, 2, 3)
>>> def f(a, b=2, *args):
...     print("a= ", a)
...     print("b= ", b)
...     print("args= ", args)
...
>>>

>>> f(1)
a= 1
b= 2
args= ()


>>> f(1, 5)
a= 1
b= 5
args= ()


>>> f(1, "FF", 3)
a= 1
b= FF
args= (3,)


>>> f(1, "FF", 3, 4, 5)
a= 1
b= FF
args= (3, 4, 5)


>>> f()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: f() missing 1 required positional argument: 
'a'
>>> a_list = [3, (4, 5)]
>>>

>>> f(a_list)
a= [3, (4, 5)]
b= 2
args= ()
>>> f(1, 4, [8, 12, 16])
a= 1
b= 4
args= ([8, 12, 16],)
>>> a_list = [3, 6, 9, (10, 11)]
>>>

>>> f(*a_list)
a= 3
b= 6
args= (9, (10, 11))
>>> def f(a, *b, c):
...     print(a, b, c)
...

>>> f(a=1, b=2, c=3)

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: f() got an unexpected keyword argument 'b'
>>>

Note that the argument cannot be passed to the starred parameter using the name = value method.

Keyword-Only Arguments PEP 3102

In Python version 2x, after the starred parameter, no other parameter can be set, and this is only possible in Python version 3x. It should be noted that in this version (3x) the arguments, like all parameters that are placed after the starred parameter, must be sent as name = value.

Consider a function with a header def f (a, * b, c.) In such a case where parameter a is set using its position and parameter b receives any number of other arguments, it is no longer necessary to pass the argument to parameter c. There is no choice but to mention its name!

>>> def f(a, *b, c):
...     print(a, b, c)
...
>>>

>>> f(1, 2, c=3)
1 (2,) 3

>>> f(1, c=3)
1 () 3

>>> f(1, 2 ,3 ,4 , 5, c=30)
1 (2, 3, 4, 5) 30


>>> f(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: f() missing 1 required keyword-only 
argument: 'c'


>>> f(1, 2, 3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: f() missing 1 required keyword-only 
argument: 'c'


>>> f(1, 2 ,3 ,4 , 5, c=30, 40)
  File "<stdin>", line 1
SyntaxError: positional argument follows 
keyword argument
>>> def f(a, *b, c, d=5):
...     print(a, b, c, d)
...
>>>

>>> f(1, 2, 3, c=4)
1 (2, 3) 4 5

>>> f(1, 2, 3, 4, c=7, d=9)
1 (2, 3, 4) 7 9
>>> # Python 2.x

>>> def f(a, *b, c):
  File "<stdin>", line 1
    def f(a, *b, c):
                 ^
SyntaxError: invalid syntax
  • The syntax name ** receives all the key arguments: the value sent in the form of a dictionary object:
>>> def f(**name):
...     print(type(name))
...     print(name)
...
>>>

>>> f()
<class 'dict'>
{}

>>> f(a=1)
<class 'dict'>
{'a': 1}
>>> def f(a, b=2, **kwargs):
...     print(a, b, kwargs)
...
>>>

>>> f(1, c=3)
1 2 {'c': 3}

>>> f(b=10, a=5, c=15)
5 10 {'c': 15}
>>> def f(a, b=2, *args, **kwargs):
...     print(a, b , args, kwargs)
...
>>>

>>> f(*[1, 2, 3, 4 ,5], **{"c":7, "d":9})
1 2 (3, 4, 5) {'d': 9, 'c': 7}

>>> f(11, 12, 13, 14, 15)
11 12 (13, 14, 15) {}

>>> f(b=14, a=7, c=21, d=28)
7 14 () {'d': 28, 'c': 21}

😊 I hope it was useful

Leave a Reply

Your email address will not be published. Required fields are marked *