.. _quickpython: ************* Python Basics ************* Types and Operators =================== This is quick reference guide for Python 3 Syntax that can be usefull during Zerynth scripts development. This section is based on the `official Python 3 guide `_. In this guide input (what is wrote in the Zerynth Python script) and output (the results of the operations) are distinguished by the presence or absence of the prompts line characters (>>> and ...). This reference has been adopted in order to be aligned with the official Python 3 guide and also to allow testing these examples, if necessary for better understanding, also on the the Python interpreter. **Let's go step by step.** Comments -------- Comments in Python start with the hash character, ``#``, and extend to the end of the physical line. Some examples:: # this is the first comment spam = 1 # and this is the second comment # ... and now a third! text = "# This is not a comment because it's inside quotes." Let's see some simple Python operations. Numbers ------- The operators ``+``, ``-``, ``*`` and ``/`` work just like in most other languages; parentheses ``()`` can be used for grouping. For example:: >>> 2 + 2 4 >>> 50 - 5*6 20 >>> (50 - 5*6) / 4 5.0 >>> 8 / 5 # division always returns a floating point number 1.6 The integer numbers (e.g. ``2``, ``4``, ``20``) have type :class:`int`, the ones with a fractional part (e.g. ``5.0``, ``1.6``) have type :class:`float`. Division (``/``) always returns a float. To do :term:`floor division` and get an integer result (discarding any fractional result) you can use the ``//`` operator; to calculate the remainder you can use ``%``:: >>> 17 / 3 # classic division returns a float 5.666666666666667 >>> >>> 17 // 3 # floor division discards the fractional part 5 >>> 17 % 3 # the % operator returns the remainder of the division 2 >>> 5 * 3 + 2 # result * divisor + remainder 17 With Python, it is possible to use the ``**`` operator to calculate powers :: >>> 5 ** 2 # 5 squared 25 >>> 2 ** 7 # 2 to the power of 7 128 The equal sign (``=``) is used to assign a value to a variable. :: >>> width = 20 >>> height = 5 * 9 If a variable is not "defined" (assigned a value), trying to use it will give you an error. Strings ------- Besides numbers, Python can also manipulate strings, which can be expressed in several ways. They can be enclosed in single quotes (``'...'``) or double quotes (``"..."``) with the same result. ``\`` can be used to escape quotes:: >>> 'spam eggs' # single quotes 'spam eggs' >>> 'doesn\'t' # use \' to escape the single quote... "doesn't" >>> "doesn't" # ...or use double quotes instead "doesn't" >>> '"Yes," he said.' '"Yes," he said.' >>> "\"Yes,\" he said." '"Yes," he said.' >>> '"Isn\'t," she said.' '"Isn\'t," she said.' Strings can be concatenated (glued together) with the ``+`` operator, and repeated with ``*``:: >>> # 3 times 'un', followed by 'ium' >>> 3 * 'un' + 'ium' 'unununium' Two or more *string literals* (i.e. the ones enclosed between quotes) next to each other are automatically concatenated. :: >>> 'Py' 'thon' 'Python' This only works with two literals though, not with variables or expressions:: >>> prefix = 'Py' >>> prefix 'thon' # can't concatenate a variable and a string literal ... SyntaxError: invalid syntax >>> ('un' * 3) 'ium' ... SyntaxError: invalid syntax If you want to concatenate variables or a variable and a literal, use ``+``:: >>> prefix + 'thon' 'Python' This feature is particularly useful when you want to break long strings:: >>> text = ('Put several strings within parentheses ' 'to have them joined together.') >>> text 'Put several strings within parentheses to have them joined together.' Strings can be *indexed* (subscripted), with the first character having index 0. There is no separate character type; a character is simply a string of size one:: >>> word = 'Python' >>> word[0] # character in position 0 'P' >>> word[5] # character in position 5 'n' Indices may also be negative numbers, to start counting from the right:: >>> word[-1] # last character 'n' >>> word[-2] # second-last character 'o' >>> word[-6] 'P' Note that since -0 is the same as 0, negative indices start from -1. In addition to indexing, *slicing* is also supported. While indexing is used to obtain individual characters, *slicing* allows you to obtain substring:: >>> word[0:2] # characters from position 0 (included) to 2 (excluded) 'Py' >>> word[2:5] # characters from position 2 (included) to 5 (excluded) 'tho' Note how the start is always included, and the end always excluded. This makes sure that ``s[:i] + s[i:]`` is always equal to ``s``:: >>> word[:2] + word[2:] 'Python' >>> word[:4] + word[4:] 'Python' Slice indices have useful defaults; an omitted first index defaults to zero, an omitted second index defaults to the size of the string being sliced. :: >>> word[:2] # character from the beginning to position 2 (excluded) 'Py' >>> word[4:] # characters from position 4 (included) to the end 'on' >>> word[-2:] # characters from the second-last (included) to the end 'on' Python strings cannot be changed --- they are :term:`immutable`. Therefore, assigning to an indexed position in the string results in an error:: >>> word[0] = 'J' ... TypeError: 'str' object does not support item assignment >>> word[2:] = 'py' ... TypeError: 'str' object does not support item assignment If you need a different string, you should create a new one:: >>> 'J' + word[1:] 'Jython' >>> word[:2] + 'py' 'Pypy' The built-in function :func:`len` returns the length of a string:: >>> s = 'supercalifragilisticexpialidocious' >>> len(s) 34 Lists ----- Python knows a number of *compound* data types, used to group together other values. The most versatile is the *list*, which can be written as a list of comma-separated values (items) between square brackets. Lists might contain items of different types, but usually the items all have the same type. :: >>> squares = [1, 4, 9, 16, 25] >>> squares [1, 4, 9, 16, 25] Like strings (and all other built-in :term:`sequence` type), lists can be indexed and sliced:: >>> squares[0] # indexing returns the item 1 >>> squares[-1] 25 >>> squares[-3:] # slicing returns a new list [9, 16, 25] All slice operations return a new list containing the requested elements. This means that the following slice returns a new (shallow) copy of the list:: >>> squares[:] [1, 4, 9, 16, 25] Lists also support operations like concatenation:: >>> squares + [36, 49, 64, 81, 100] [1, 4, 9, 16, 25, 36, 49, 64, 81, 100] Unlike strings, which are :term:`immutable`, lists are a :term:`mutable` type, i.e. it is possible to change their content:: >>> cubes = [1, 8, 27, 65, 125] # something's wrong here >>> 4 ** 3 # the cube of 4 is 64, not 65! 64 >>> cubes[3] = 64 # replace the wrong value >>> cubes [1, 8, 27, 64, 125] You can also add new items at the end of the list, by using the :meth:`~list.append` *method* (we will see more about methods later):: >>> cubes.append(216) # add the cube of 6 >>> cubes.append(7 ** 3) # and the cube of 7 >>> cubes [1, 8, 27, 64, 125, 216, 343] Assignment to slices is also possible, and this can even change the size of the list or clear it entirely:: >>> letters = ['a', 'b', 'c', 'd', 'e', 'f', 'g'] >>> letters ['a', 'b', 'c', 'd', 'e', 'f', 'g'] >>> # replace some values >>> letters[2:5] = ['C', 'D', 'E'] >>> letters ['a', 'b', 'C', 'D', 'E', 'f', 'g'] >>> # now remove them >>> letters[2:5] = [] >>> letters ['a', 'b', 'f', 'g'] >>> # clear the list by replacing all the elements with an empty list >>> letters[:] = [] >>> letters [] The built-in function :func:`len` also applies to lists:: >>> letters = ['a', 'b', 'c', 'd'] >>> len(letters) 4 It is possible to nest lists (create lists containing other lists), for example:: >>> a = ['a', 'b', 'c'] >>> n = [1, 2, 3] >>> x = [a, n] >>> x [['a', 'b', 'c'], [1, 2, 3]] >>> x[0] ['a', 'b', 'c'] >>> x[0][1] 'b' Control Flow Tools ================== Python knows the usual control flow statements known from other languages, with some twists. :keyword:`if` Statements ------------------------ Perhaps the most well-known statement type is the :keyword:`if` statement. For example:: >>> x = int(input("Please enter an integer: ")) Please enter an integer: 42 >>> if x < 0: ... x = 0 ... print('Negative changed to zero') ... elif x == 0: ... print('Zero') ... elif x == 1: ... print('Single') ... else: ... print('More') ... More There can be zero or more :keyword:`elif` parts, and the :keyword:`else` part is optional. The keyword ':keyword:`elif`' is short for 'else if', and is useful to avoid excessive indentation. An :keyword:`if` ... :keyword:`elif` ... :keyword:`elif` ... sequence is a substitute for the ``switch`` or ``case`` statements found in other languages. :keyword:`while` Statements --------------------------- The :keyword:`while` statement is in Python similar to C and other most used languages. The basic example is: :: while True: a=a+1 print(a) This code will print 1, 2, 3..... until the execution is killed. The :keyword:`True` can be replaced by any boolean expression: :: x=0 while x<5: x=x+1 print(x) ... ... 1 2 3 4 :keyword:`for` Statements ------------------------- The :keyword:`for` statement in Python differs a bit from what you may be used to in C or Pascal. Rather than always iterating over an arithmetic progression of numbers (like in Pascal), or giving the user the ability to define both the iteration step and halting condition (as C), Python's :keyword:`for` statement iterates over the items of any sequence (a list or a string), in the order that they appear in the sequence. For example (no pun intended): :: >>> # Measure some strings: ... words = ['cat', 'window', 'defenestrate'] >>> for w in words: ... print(w, len(w)) ... cat 3 window 6 defenestrate 12 If you need to modify the sequence you are iterating over while inside the loop (for example to duplicate selected items), it is recommended that you first make a copy. Iterating over a sequence does not implicitly make a copy. The :func:`range` Function -------------------------- If you do need to iterate over a sequence of numbers, the built-in function :func:`range` comes in handy. It generates arithmetic progressions:: >>> for i in range(5): ... print(i) ... 0 1 2 3 4 The given end point is never part of the generated sequence; ``range(10)`` generates 10 values, the legal indices for items of a sequence of length 10. It is possible to let the range start at another number, or to specify a different increment (even negative; sometimes this is called the 'step'):: range(5, 10) 5 through 9 range(0, 10, 3) 0, 3, 6, 9 range(-10, -100, -30) -10, -40, -70 To iterate over the indices of a sequence, you can combine :func:`range` and :func:`len` as follows:: >>> a = ['Mary', 'had', 'a', 'little', 'lamb'] >>> for i in range(len(a)): ... print(i, a[i]) ... 0 Mary 1 had 2 a 3 little 4 lamb :keyword:`break` and :keyword:`continue` Statements, and :keyword:`else` Clauses on Loops ----------------------------------------------------------------------------------------- The :keyword:`break` statement, like in C, breaks out of the smallest enclosing :keyword:`for` or :keyword:`while` loop. Loop statements may have an ``else`` clause; it is executed when the loop terminates through exhaustion of the list (with :keyword:`for`) or when the condition becomes false (with :keyword:`while`), but not when the loop is terminated by a :keyword:`break` statement. This is exemplified by the following loop, which searches for prime numbers:: >>> for n in range(2, 10): ... for x in range(2, n): ... if n % x == 0: ... print(n, 'equals', x, '*', n//x) ... break ... else: ... # loop fell through without finding a factor ... print(n, 'is a prime number') ... 2 is a prime number 3 is a prime number 4 equals 2 * 2 5 is a prime number 6 equals 2 * 3 7 is a prime number 8 equals 2 * 4 9 equals 3 * 3 (Yes, this is the correct code. Look closely: the ``else`` clause belongs to the :keyword:`for` loop, **not** the :keyword:`if` statement.) When used with a loop, the ``else`` clause has more in common with the ``else`` clause of a :keyword:`try` statement than it does that of :keyword:`if` statements: a :keyword:`try` statement's ``else`` clause runs when no exception occurs, and a loop's ``else`` clause runs when no ``break`` occurs. For more on the :keyword:`try` statement and exceptions, see :ref:`tut-handling`. The :keyword:`continue` statement, also borrowed from C, continues with the next iteration of the loop:: >>> for num in range(2, 10): ... if num % 2 == 0: ... print("Found an even number", num) ... continue ... print("Found a number", num) Found an even number 2 Found a number 3 Found an even number 4 Found a number 5 Found an even number 6 Found a number 7 Found an even number 8 Found a number 9 :keyword:`pass` Statements -------------------------- The :keyword:`pass` statement does nothing. It can be used when a statement is required syntactically but the program requires no action. For example:: >>> while True: ... pass # Busy-wait for keyboard interrupt (Ctrl+C) ... This is commonly used for creating minimal classes:: >>> class MyEmptyClass: ... pass ... Another place :keyword:`pass` can be used is as a place-holder for a function or conditional body when you are working on new code, allowing you to keep thinking at a more abstract level. The :keyword:`pass` is silently ignored:: >>> def initlog(*args): ... pass # Remember to implement this! ... .. _tut-functions: Defining Functions ================== We can create a function that writes the Fibonacci series to an arbitrary boundary:: >>> def fib(n): # write Fibonacci series up to n ... """Print a Fibonacci series up to n.""" ... a, b = 0, 1 ... while a < n: ... print(a, end=' ') ... a, b = b, a+b ... print() ... >>> # Now call the function we just defined: ... fib(2000) 0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 The keyword :keyword:`def` introduces a function *definition*. It must be followed by the function name and the parenthesized list of formal parameters. The statements that form the body of the function start at the next line, and must be indented. The first statement of the function body can optionally be a string literal; this string literal is the function's documentation string, or :dfn:`docstring`. There are tools which use docstrings to automatically produce online or printed documentation, or to let the user interactively browse through code; it's good practice to include docstrings in code that you write, so make a habit of it. The *execution* of a function introduces a new symbol table used for the local variables of the function. More precisely, all variable assignments in a function store the value in the local symbol table; whereas variable references first look in the local symbol table, then in the local symbol tables of enclosing functions, then in the global symbol table, and finally in the table of built-in names. Thus, global variables cannot be directly assigned a value within a function (unless named in a :keyword:`global` statement), although they may be referenced. The actual parameters (arguments) to a function call are introduced in the local symbol table of the called function when it is called; thus, arguments are passed using *call by value* (where the *value* is always an object *reference*, not the value of the object). [#]_ When a function calls another function, a new local symbol table is created for that call. It is simple to write a function that returns a list of the numbers of the Fibonacci series, instead of printing it:: >>> def fib2(n): # return Fibonacci series up to n ... """Return a list containing the Fibonacci series up to n.""" ... result = [] ... a, b = 0, 1 ... while a < n: ... result.append(a) # see below ... a, b = b, a+b ... return result ... >>> f100 = fib2(100) # call it >>> f100 # write the result [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89] This example, as usual, demonstrates some new Python features: * The :keyword:`return` statement returns with a value from a function. :keyword:`return` without an expression argument returns ``None``. Falling off the end of a function also returns ``None``. * The statement ``result.append(a)`` calls a *method* of the list object ``result``. A method is a function that 'belongs' to an object and is named ``obj.methodname``, where ``obj`` is some object (this may be an expression), and ``methodname`` is the name of a method that is defined by the object's type. Different types define different methods. Methods of different types may have the same name without causing ambiguity. (It is possible to define your own object types and methods, using *classes*, see :ref:`tut-classes`)The method :meth:`append` shown in the example is defined for list objects; it adds a new element at the end of the list. In this example it is equivalent to ``result = result + [a]``, but more efficient. Default Argument Values ----------------------- The most useful form is to specify a default value for one or more arguments. This creates a function that can be called with fewer arguments than it is defined to allow. For example:: def ask_ok(prompt, retries=4, complaint='Yes or no, please!'): while True: ok = input(prompt) if ok in ('y', 'ye', 'yes'): return True if ok in ('n', 'no', 'nop', 'nope'): return False retries = retries - 1 if retries < 0: raise OSError('uncooperative user') print(complaint) This function can be called in several ways: * giving only the mandatory argument: ``ask_ok('Do you really want to quit?')`` * giving one of the optional arguments: ``ask_ok('OK to overwrite the file?', 2)`` * or even giving all arguments: ``ask_ok('OK to overwrite the file?', 2, 'Come on, only yes or no!')`` This example also introduces the :keyword:`in` keyword. This tests whether or not a sequence contains a certain value. The default values are evaluated at the point of function definition in the *defining* scope, so that :: i = 5 def f(arg=i): print(arg) i = 6 f() will print ``5``. **Important warning:** The default value is evaluated only once. This makes a difference when the default is a mutable object such as a list, dictionary, or instances of most classes. For example, the following function accumulates the arguments passed to it on subsequent calls:: def f(a, L=[]): L.append(a) return L print(f(1)) print(f(2)) print(f(3)) This will print :: [1] [1, 2] [1, 2, 3] If you don't want the default to be shared between subsequent calls, you can write the function like this instead:: def f(a, L=None): if L is None: L = [] L.append(a) return L Keyword Arguments ----------------- Functions can also be called using :term:`keyword arguments ` of the form ``kwarg=value``. For instance, the following function:: def parrot(voltage, state='a stiff', action='voom', type='Norwegian Blue'): print("-- This parrot wouldn't", action, end=' ') print("if you put", voltage, "volts through it.") print("-- Lovely plumage, the", type) print("-- It's", state, "!") accepts one required argument (``voltage``) and three optional arguments (``state``, ``action``, and ``type``). This function can be called in any of the following ways:: parrot(1000) # 1 positional argument parrot(voltage=1000) # 1 keyword argument parrot(voltage=1000000, action='VOOOOOM') # 2 keyword arguments parrot(action='VOOOOOM', voltage=1000000) # 2 keyword arguments parrot('a million', 'bereft of life', 'jump') # 3 positional arguments parrot('a thousand', state='pushing up the daisies') # 1 positional, 1 keyword but all the following calls would be invalid:: parrot() # required argument missing parrot(voltage=5.0, 'dead') # non-keyword argument after a keyword argument parrot(110, voltage=220) # duplicate value for the same argument parrot(actor='John Cleese') # unknown keyword argument In a function call, keyword arguments must follow positional arguments. All the keyword arguments passed must match one of the arguments accepted by the function (e.g. ``actor`` is not a valid argument for the ``parrot`` function), and their order is not important. This also includes non-optional arguments (e.g. ``parrot(voltage=1000)`` is valid too). No argument may receive a value more than once. Here's an example that fails due to this restriction:: >>> def function(a): ... pass ... >>> function(0, a=0) Traceback (most recent call last): File "", line 1, in ? TypeError: function() got multiple values for keyword argument 'a' In Python when a final formal parameter of the form ``**name`` is present, it receives a dictionary (see :ref:`typesmapping`) containing all keyword arguments except for those corresponding to a formal parameter. However this syntax is not yet supported in Zerynth. Arbitrary Argument Lists ------------------------ .. index:: statement: * Finally, a frequently used option is to specify that a function can be called with an arbitrary number of arguments. These arguments will be wrapped up in a tuple (see :ref:`tut-tuples`). Before the variable number of arguments, zero or more normal arguments may occur. :: def write_multiple_items(file, separator, *args): file.write(separator.join(args)) Normally, these ``variadic`` arguments will be last in the list of formal parameters, because they scoop up all remaining input arguments that are passed to the function. Any formal parameters which occur after the ``*args``parameter are 'keyword-only' arguments, meaning that they can only be used as keywords rather than positional arguments. :: >>> def concat(*args, sep="/"): ... return sep.join(args) ... >>> concat("earth", "mars", "venus") 'earth/mars/venus' >>> concat("earth", "mars", "venus", sep=".") 'earth.mars.venus' .. _tut-unpacking-arguments: Unpacking Argument Lists ------------------------ The reverse situation occurs when the arguments are already in a list or tuple but need to be unpacked for a function call requiring separate positional arguments. For instance, the built-in :func:`range` function expects separate *start* and *stop* arguments. If they are not available separately, write the function call with the ``*``\ -operator to unpack the arguments out of a list or tuple:: >>> list(range(3, 6)) # normal call with separate arguments [3, 4, 5] >>> args = [3, 6] >>> list(range(*args)) # call with arguments unpacked from a list [3, 4, 5] .. index:: statement: ** In Python, dictionaries can deliver keyword arguments with the ``**``\ -operator. However this syntax is not yet supported in Zerynth