Why are there no pointers in Python

Python course

Parameter transfer


Transfer of value or transfer of reference

In programming languages, a distinction is essentially made between two types of parameter transfer:

  • Transfer of value (call by value)
    When passing by value, each argument represents a value that is evaluated when a function is called. This value is then transferred to the corresponding formal parameter, i.e. the value of the i-th expression (parameter) is assigned to the i-th parameter when it is called. The expressions can consist of literals, variables or any expressions (such as "a + b + 7". Values โ€‹โ€‹of transferred parameters cannot be changed!
  • Handover of reference(call by reference)
    As the name implies, when passing by reference, references (memory addresses) are passed, i.e. the memory address of the respective actual parameter. The formal parameter thus becomes a pointer to the memory address of the calling parameter. This means that all changes to this parameter within the function also have an effect in the calling part of the program. However, this also means that the actual parameters when called can only be expressions whose address can be calculated, i.e. variables.
Note: The name parameters (call by name) as used in ALGOL 60 and COBOL are no longer common today.

and what about python?

In many books or introductions to Python one reads that Python has one or the other transfer mechanism, ie "call by reference" or "call by value". What is right now?
Humpty Dumpty gives us the explanation:
"When I use a word," said Humpty Dumpty, rather disdainfully, "it means exactly what I make it mean and nothing else."
"The question is," said Alice, "whether you can just give the words so many different meanings".
"The question is," said Humpty Dumpty, "who has power - and that's all. [...]"
Lewis Carroll, Through the Looking-Glass

The authors "stretch" the original terms so that they fit.
Python uses a mechanism called "Call-by-Object" (also called "Call by Object Reference" or "Call by Sharing").

In Python, when a function is called, pointers to the underlying objects are not transferred, but rather pointers to the underlying objects.
To explain this, consider the following example: def ref_demo (x): print "x =", x, "id =", id (x) x = 42 print "x =", x, "id =", id (x ) If you call this function (see below) and at the same time use the build-in function id () to check the identity of the variable x, you will see that the global x corresponds to the local x of the function until you get x assigns a different value in the function. So Python first behaves like "call by reference", then like "call by value". However, in contrast to a real "call by reference", you can transfer any expressions when calling, as when passing by value. >>> x = 9 >>> ref_demo (x) x = 9 id = 29488104 x = 42 id = 29489304 >>> id (x) 29488104 >>>

Side effects

From a function call, one expects that the function returns the correct value for the arguments and otherwise does not cause any effects, e.g. changing memory states. While some programmers deliberately use side effects to solve their problems, it is generally considered bad style.
It is worse, however, when side effects occur that were not anticipated and cause errors. This can happen in Python as well. This can happen when lists or dictionaries are passed as parameters.
In the following example the list fib, which is passed as the current parameter to the function s (), is concatenated with the list [47,11]. >>> def s (list): ... print id (list) ... list + = [47,11] ... print id (list) ... >>> fib = [0,1,1 , 2,3,5,8] >>> id (fib) 24746248 >>> s (fib) 24746248 24746248 >>> id (fib) 24746248 >>> fib [0, 1, 1, 2, 3, 5 , 8, 47, 11] You can prevent this if you pass a copy of the list as a parameter instead of a list. Using the slicing function, you can easily create a copy. So in s (fib [:]) not the list fib but a complete copy is passed. The call does not change the value of fib, as can be seen in the following interactive program section: >>> def s (list): ... list + = [47,11] ... print list ... >>> fib = [0,1,1,2,3,5,8] >>> s (fib [:]) [0, 1, 1, 2, 3, 5, 8, 47, 11] >>> fib [0, 1, 1, 2, 3, 5, 8]

Passing arguments

You can pass arguments not only to functions, but also to a Python script. If a Python script is called from a shell, the arguments are listed after the script name, separated by a space. In the Python script itself, these arguments can be called up under the list variable sys.argv. The name of the script (file name) can be reached under sys.argv [0].
The script (argumente.py) lists all given arguments in the standard output:
# Module sys is imported: import sys # Iteration over all arguments: for eachArg in sys.argv: print eachArg Example of a call, if the script has been saved under argumente.py: python argumente.py python course for beginners This call creates the following Output argumentse.py python course for beginners

Variable number of parameters

We are now introducing functions that can be called with any number of arguments. Those with programming experience in C and C ++ know the concept as varargs. An asterisk "*" is used in Python to denote a variable number of parameters. To do this, the asterisk is placed immediately in front of a variable name. >>> def varpafu (* x): print (x) ... >>> varpafu () () >>> varpafu (34, "Do you like Python?", "Of course") (34, 'Do you like Python? ',' Of course ') >>> From the previous example we learn that the arguments that are passed to a function call to varpafu are collected in a tuple. This tuple can be accessed as a "normal" variable in the body of the function. If you call the function without any arguments, x is an empty tuple.

Sometimes it is necessary to have a fixed number of positional parameters that can be followed by any number of parameters. This is possible, but the positional parameters must always come first.

In the following example we use a position parameter "city", which must always be specified, followed by any number of further cities "other_cities":
>>> def locations (city, * other_cities): print (city, other_cities) ... >>> locations ("Berlin") ('Berlin', ()) >>> locations ("Berlin", "Freiburg" , "Stuttgart", "Konstanz", "Frankfurt") ('Berlin', ('Freiburg', 'Stuttgart', 'Konstanz', 'Frankfurt')) >>>

exercise

Write a function that calculates the arithmetic mean of a variable number of values.

solution

def arithmetic_mean (x, * l): "" "The function calculates the arithmetic mean of a non-empty arbitrary number of numbers" "" sum = x for i in l: sum + = i return sum / (1.0 + len ( l)) One might ask why we used both a positional parameter "x" and a parameter for a variable number of values โ€‹โ€‹"* l" in our function definition. The idea is that we wanted to force our function to always be called with a non-empty number of arguments. This is necessary to avoid division by 0, which would cause an error.

In the following interactive Python session, we will learn how to use this function. We assume that the function arithmetic_mean has been saved in a file with the name statistics.py. >>> from statistics import arithmetic_mean >>> arithmetic_mean (4,7,9) 6.666666666666667 >>> arithmetic_mean (4,7,9,45, -3.7,99) 26.71666666666667 That works fine, but there is a catch. What if someone wants to call the function with a list instead of a variable number of numbers?

In the following we can see that an error is then caused: >>> l = [4,7,9,45, -3.7,99] >>> arithmetic_mean (l) Traceback (most recent call last): File " ", line 1, in file" statistics.py ", line 8, in arithmetic_mean return sum / (1.0 + len (l)) TypeError: unsupported operand type (s) for /: 'list' and 'float 'The rescue is to use another asterisk: >>> arithmetic_mean (* l) 26.71666666666667 >>>

* in function calls

An asterisk can also appear in a function call, as we saw in the previous exercise: The semantics in this case are "inverse" to the use of an asterisk in the function definition. An argument is unpacked and not packed. In other words: The elements of a list or a tuple are singled out: >>> def f (x, y, z): ... print (x, y, z) ... >>> p = (47,11 , 12) >>> f (* p) (47, 11, 12) Needless to say, this way of calling our function is more convenient than the following: >>> f (p [0], p [1], p [2]) (47, 11, 12) >>> In addition to the fact that this call is less convenient, the previous call type is generally not applicable, ie if lists of "unknown" lengths are to be used. "Unknown" means that the length is only known during runtime and not while the script is being written.

Any keyword parameter

There is also a mechanism for any number of keyword parameters. To make this possible, a double asterisk "**" was introduced as a notation: >>> def f (** args): ... print (args) ... >>> f () {} >>> f ( de = "Germnan", en = "English", fr = "French") {'fr': 'French', 'de': 'Germnan', 'en': 'English'} >>>

Double asterisk in the function call

The following example illustrates the use of ** in a function call: >>> def f (a, b, x, y): ... print (a, b, x, y) ... >>> d = { 'a': 'append', 'b': 'block', 'x': 'extract', 'y': 'yes'} >>> f (** d) ('append', 'block', 'extract', 'yes')