4. More Control Flow Tools
Control flow - some information about control flow.
If you’re comparing the same value to several constants, or checking for
specific types or attributes, you may also find the
match
statement useful.
For statement in Python iterates over the sequence items, in the order that they appear. And finish when sequence is exhausted.
Code that modifies a collection while iterating over that same collection can be tricky to get right. Instead, it is usually more straight-forward to loop over a copy of the collection or to create a new collection:
range(X)
function is sort of converting given length input to known array
(0,1,...,X-1
). It generates mathematics arithmetic progression.
If fact range()
function generate equal to argument number (if passed only
one) elements, we just start from 0 and increment by 1.
Last index of range(10)
?::9
, from 0 to 9
You can select range start number (not index). Some like to generate numbers from X to Y-1.
How to generate numbers form 1 to 100 using range()
?
And you can also specify increment for
step size. Which can be positive or negative. Positive step size will generate
sequence in increasing order and negative step size will generate sequence in
decreasing order (alternative to reversed()
function).
What you will see in interactive python session if you enter this:
list(range(10, 0))
, list(range(10, 0, -1))
and this list(range(10, -1, -1))
.
To iterate over the indices of a sequence, you can combine range()
and len()
as follows:
But better just use enumerate()
function when you want to repeatedly access to
iterable items (more suited for this).
If we don’t iterate over range and just print it, we will get object reference.
Because range return iterable object, to get list we need to use
==list()
== function.
What you will see with this code: sum(range(4))
?
0 + 1 + 2 + 3 = 6
The
break
statement, like in C,breaks out
of the innermost enclosing ==for
orwhile
loop==.
In python for
and while
statements may have a else
clause.
It is executed if loop ==wasn’t break
ed== (we iterated over all items in for
or
while
condition become False
).
When else
runs in try-except
statement?
In try-except
statement, else
runs if no exception was raised.
If you worked on something (try, for, while) and you aren’t interrupt
(break/exception), we call else method as this is likely unusual?
The ==continue
== statement - continue loop with the next iteration of the
loop (skip anything from current place to end of loop body).
pass
is a ==null operation== (name, this is statement, while
None
is an object). When it is executed, nothing happens. It is useful as
a placeholder when a statement is required syntactically, but no code needs
to be executed.
A match
statement comparig expression with successive patterns (state).
This is pattern matching like in Rust or Haskell.
Only the first pattern that matches gets executed, and it can also extract components (sequence elements or object attributes) from the value into variables in case block.
TODO: need improve Can I use objects (classes for example) in pattern matching, can I bind class
attributes to variables?
Yes, you can use objects (in case blocks), place them with arguments (like
a constructor), capturing class attributes also supported.
Patterns can look like unpacking assignments (like tuple), and can be used to bind variables into case block:
Can I customize class arguments order with pattern matching?
Yes, by setting the __match_args__
special attribute in your classes. If
it’s set to ("x", "y")
tuple, the following patterns are all equivalent (and all
bind the y
attribute to the var
variable):
Which variable names can be assigned (bind variable to value) to in a match
statement?
Only standalone names (like var
) are assigned to by a match statement.
Dotted names (like foo.bar
), attribute names (the x=
and y=
) or class
names (recognized by the ”(…)” next to them like Point
above) are never
assigned to.
Can patterns in match case be nested?
Yes, patterns can be arbitrarily nested. For example, if we have a short list
of Points, with __match_args__
added, we could match it like this:
Can I use if
statements in pattern matching, why it can be useful?
Yes, if
clause in pattern known as a “guard”. If the guard
is false, match
goes on to try the next case block. Note that value
capture happens before the guard is evaluated:
Several other key features of this statement:
- Like unpacking assignments, tuple and list match patterns (case block) have
exactly the same meaning and actually match arbitrary sequences. An important
exception is that they don’t match iterators or strings. In other words
you can’t unpack for example
"00"
value with(x, y)
pattern.
- Sequence pattern matching support extended unpacking:
[x, y, *rest]
and(x, y, *rest)
work similar to unpacking assignments. The name after*
may also be_
, so(x, y, *_)
matches a sequence of at least two items without binding the remaining items.
- Mapping patterns:
{"bandwidth": b, "latency": l}
captures the"bandwidth"
and"latency"
values from a dictionary and save them intob
andl
variables. Unlike sequence patterns, extra keys are ignored. An unpacking like**rest
is also supported. But ==**_
== would be redundant, so it is not allowed.
- Subpatterns may be captured using the ==
as
== keyword, sort of alternative name:
- Most literals are compared by equality in pattern matching, however the singletons (which one)
==
True
,False
andNone
== are compared by identity
How to use named constants (Enum
) in pattern matching?
- Patterns may use named constants. These must be dotted names to prevent them from being interpreted as capture variable:
PEP 636 – Structural Pattern Matching: Tutorial.
We can create a function that writes the Fibonacci series to an arbitrary
boundary, can you explain how (function code)?
How to define a function in Python?
The keyword def
introduces a function definition.
Example of function definition:
The first statement of the function body can optionally be a string literal
(usually in triple quotes); this string literal is the function’s documentation
string, or ==docstring
==.
Why include docstring is recommended to make a habit?
There some tools, which produce online or printed documentation, or to let
the user interactively browse through code. Also, many IDEs and editors
support showing documentation based on this docstrings.
Order of variable references looking (tables)?
- Local symbol table
- Local symbol table of enclosing functions (parents)
- Global symbol table
- Built-in names table
These priorities also reason why
global
andnonlocal
statements are needed.
How to change (or use) variable from global scope in some function, when are you
trying to assign value to it, python create new local variable with the same
name?
Use global
statement.
The
nonlocal
statement causes the listed identifiers to refer to previously bound variables in the nearest enclosing scope excluding globals.
Closures in nested functions: we can use the nonlocal
keyword to work with
variables in nested scope which shouldn’t be declared in the inner functions.
When a function calls another function, or calls itself recursively, a new local symbol table is created for that call (related to arguments/variables).
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, true for immutable objects (where the value is always an object reference, not the value of the object). Actually, call by object reference would be a better description, since if a mutable object is passed, the caller will see any changes the callee makes to it (items inserted into a list).
A function definition associates the function name with the function object in the current symbol table.
Is there procedures in Python?
In Python there no procedures, only functions. Functions without a return
statement do return a value. This value is called None
(it’s a built-in name).
What is object method in Python?
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.
You can define default values for arguments in a function, when it can be
required?
This allows to omit some arguments when calling the function, sort of default
settings.
in
keyword tests that a sequence contains a certain value.
What this code will print (question about default value behavior and evaluation)?
The default values are evaluated at the point of function definition in
the defining scope, so that code will print 5
, so order of evaluation is
important, and default value evaluated only once if it’s not mutable object
(list, dictionary, or instances of most classes). Last example if you want
default to be not shared between subsequent calls.
Functions can also be called using keyword arguments, kwarg=value
.
When a final formal parameter of the form **name
is present, it
receives a dictionary (see typesmapping
) containing all keyword
arguments except for those corresponding to a formal parameter.
This may be combined with a formal parameter of the form *name
(described in
the next subsection) which receives a tuple <tut-tuples>
containing
the positional arguments beyond the formal parameter list. (*name
must
occur before **name
.) For example, if we define a function like this:
Length of tuple with positional arguments and dictionary with keyword arguments?
Check cheeseshop
function call.
Two positional arguments and three keyword arguments.
Positional-only, positional-or-keyword, and keyword-only parameters can be
combined in a single function definition, how to do this?
A function definition may look like this:
Will this code work, if not why and how to fix it?
Since the name
parameter is a positional and keyword argument, it will always
bind to the first parameter (name). So, the code will raise a TypeError
:
foo() got multiple values for argument ‘name’.
You can fix this by using /
(positional-only arguments) to allow name
as a
positional argument only, the names of positional-only parameters can be used in
**kwds
without ambiguity.
When positional-only arguments are useful in function definition?
Use positional-only if you want the name of the parameters to not be
available to the user. This is useful when parameter names have no real meaning,
if you want to enforce the order of the arguments when the function is called or
if you need to take some positional parameters and arbitrary keywords.
When keyword-only arguments in function definition are useful?
Use keyword-only when names have meaning and the function definition is more
understandable by being explicit with names or you want to prevent users relying
on the position of the argument being passed.
For an API, use positional-only to prevent breaking API changes if the parameter’s name is modified in the future. But don’t forget about too many positional-only parameters, it can make the function hard to understand.
You can specify that a function can be called with an arbitrary number of arguments. These arguments will be wrapped up in a tuple. Before the variable number of arguments, zero or more normal arguments may occur.
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’
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 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:
Sometimes you need to unpack data from list or tuple for a function call,
which require separate positional arguments, for example range
function
expects start
and stop
arguments separately. You can write the function
call with the ==*
-operator, list(range(*args))
to unpack the arguments out
of a list or tuple.
Dictionaries can deliver keyword arguments with the ==**
==-operator.
Small anonymous functions can be created with the ==lambda
== keyword.
They can be used wherever function objects are required. Syntactically
restricted to a single expression, semantically they are just syntactic sugar
for a normal function definition.
Lambda function format?
lambda arguments : expression
Like nested function definitions, lambda functions can reference variables from the containing scope.
How to use function/lambda function with list and sort method?
Documentation Strings used to document functions, methods, classes, and modules.
Docstring code style, at least basic information?
The first line should always be a short, concise summary of the object’s
purpose. For brevity, it should not explicitly state the object’s name or type,
since these are available by other means (except if the name happens to be a
verb describing a function’s operation). This line should begin with a capital
letter and end with a period.
If there are more lines in the documentation string, the second line should be
blank, visually separating the summary from the rest of the description. The
following lines should be one or more paragraphs describing the object’s calling
conventions, its side effects, etc.
The Python parser does not strip indentation from multi-line string literals in
Python, so tools that process documentation have to strip indentation if
desired. This is done using the following convention. The first non-blank line
after the first line (third) of the string determines the amount of
indentation for the entire documentation string. (We can’t use the first line
since it is generally adjacent to the string’s opening quotes so its indentation
is not apparent in the string literal.) Whitespace “equivalent” to this
indentation is then stripped from the start of all lines of the string. Lines
that are indented less should not occur, but if they occur all their leading
whitespace should be stripped. Equivalence of whitespace should be tested after
expansion of tabs (to 8 spaces, normally).
Here is an example of a multi-line docstring:
\
Function annotations <function>
are completely optional metadata
information about the types used by user-defined functions
(see PEP 3107 - Function Annotations and
PEP 484 - Type hints).
Function annotation are stored in the ==__annotations__
== attribute of the
function as a dictionary and have no effect on any other part of the function.
Parameter annotations are defined by a colon after the parameter name, followed by an expression evaluating to the value of the annotation.
Return annotations are defined by a literal ==->
==, followed by an expression.
The following example has a required argument, an optional argument, and the return value annotated:
Python Coding Style
Most languages can be written (or more concise, formatted) in different styles; some are more readable than others. Making it easy for others to read your code is always a good idea, and adopting a nice coding style helps tremendously for that.
PEP 8 – Style Guide for Python Code core information:
Python code style usually use 4-space indentation, and no tabs (compromise between small and large indentation).
Wrap lines so that they don’t exceed 79 characters. Useful for small displays and also prevent you writing complex code.
Use blank lines to separate functions and classes, and larger blocks of code inside functions, sort of grouping.
When possible, put comments on a line of their own (separate line).
How to document code in Python?
Use docstrings, which follow docstrings rules.
Use spaces around operators and after commas, but not directly inside
==bracketing constructs: a = f(1, 2) + g(3, 4)
==.
Name your classes and functions consistently; the convention is to use
UpperCamelCase
for classes and lowercase_with_underscores
for functions and
methods. Always use self
as the name for the first method argument.
Don’t use fancy encodings if your code is meant to be used in international environments. Python’s default, UTF-8, or even plain ASCII (subset) work best in any case.
Likewise, don’t use non-ASCII characters in identifiers if there is only the slightest chance people speaking a different language will read or maintain the code.