# Functions and other things

By on

### 🐍 Functions and etc.

Viktor Tiulpin @ SPbPU · 2021

Syntax 1

``````def get_lectures_stream():
return "youtu.be/dQw4w9WgXcQ"

>>> get_lectures_stream()
"youtu.be/dQw4w9WgXcQ"
``````

Syntax 2

• name: number can’t be the first symbol, but almost any unicode symbol can be used
• return: if none, then returns `None`
``````def get_lectures_stream():
"youtu.be/dQw4w9WgXcQ"

>>> get_lectures_stream()
None
``````

Syntax 3

``````def get_lectures_stream() -> str:
"""Returns the last lecture stream."""
return "youtu.be/dQw4w9WgXcQ"
``````

### 1️⃣ What is First-Class object

• Created at runtime
• Assigned to a variable or element in a data structure
• Passed as an argument to a function
• Returned as the result of a function

Integers, strings, dictionaries, and functions in Python

### 🚀 Function as an object

``````>>> get_lectures_stream.__doc__
'Returns the last lecture stream.'
``````

### 🚲 Let’s create a function

Let’s create a function 1

``````def min(x, y):
return x if x < y else y

>>> min(-5, 0)
-5
``````

Let’s create a function 2

``````def min(x, y):
return x if x < y else y

>>> min(a=-5, b=0)
-5
``````

Let’s create a function 3

``````def min(x, y):
return x if x < y else y

>>> min(x=-5, z=0)
# ???
``````

``````>>> min(1, 2, 3)
1
>>> min({1, 2, 3})
1
>>> bounded_min(-42, 0, 42, low=0, high=255)
0
``````

### 📦 Packing and unpacking

We did it!

``````def min(*args):  # what's type(args)?
res = float("inf")
for arg in args:
if arg < res:
res = arg
return res
``````

``````>>> min(-5, 12, 13)
-5
>>> min()  # how to require at least 1 arg?
inf
``````

The solution

``````def min(first, *args):
res = first
# ...
``````

🧐 How to apply the function to a collection? (list, tuple, set)

``````>>> xs = {-5, 12, 13}
>>> min(*xs)
-5
>>> min(*[-5, 12, 13])
-5
>>> min(*(-5, 12, 13))
-5
``````

Let’s get back…

``````>>> def bounded_min(first, *args, lo=float("-inf"), hi=float("inf")):
res = hi
for arg in (first, ) + args:  # what's the algorithmic complexity?
if arg < res and lo < arg < hi:
res = arg
return max(res, lo)
...
>>> bounded_min(-5, 12, 13, lo=0, hi=255)
12
``````

👀 What’s wrong?

``````>>> def unique(iterable, seen=set()):
acc = []
for item in iterable:
if item not in seen:
acc.append(item)
return acc
``````

Hint: when do default values initialize?

``````>>> xs = [1, 1, 2, 3]
>>> unique(xs)
[1, 2, 3]
>>> unique(xs)
[]
>>> unique.__defaults__
({1, 2, 3},)
``````

How to solve it?

The solution

``````>>> def unique(iterable, seen=None):
seen = set(seen or []) # None – falsy.
acc = []
for item in iterable:
if item not in seen:
acc.append(item)
return acc
...
>>> xs = [1, 1, 2, 3]
>>> unique(xs)
[1, 2, 3]
>>> unique(xs)
[1, 2, 3]
``````

🤔 How-to: positional-only parameters

``````>>> def divmod(a, b, /, key=None):  # specify keyword after '/'
return (a // b, a % b)
...
>>> divmod(a=1, b=1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: divmod() got some positional-only arguments passed as keyword arguments: 'a, b'
``````

🤔 How-to: keyword-only parameters

``````>>> def flatten(xs, *, depth=None):
pass
...
>>> flatten([1, [2], 3], 2)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: flatten() takes 1 positional argument [...]
``````

``````>>> def runner(cmd, **kwargs):
if kwargs.get("verbose", True):
print("Logging enabled")
...
>>> runner("mysqld", limit=42)
Logging enabled
>>> runner("mysqld", **{"verbose": False})
>>> options = {"verbose": False}
>>> runner("mysqld", **options)
``````

🚀 Assignment (unpacking)

``````>>> x, y, z = [1, 2, 3]
>>> x, y, z = {1, 2, 3} # ???
>>> x, y, z = "xyz"
>>> first, *rest = range(1, 5)
>>> first, rest
(1, [2, 3, 4])
>>> first, *rest, last = range(1, 5)
>>> last
4
>>> for a, *b in [range(4), range(2)]:
print(b)
...
[1, 2, 3]
[1]
``````

Let’s look inside one time only

``````>>> import dis
>>> dis.dis("first, *rest, last = ('a', 'b', 'c')")
0 LOAD_CONST 4 (('a', 'b', 'c'))
3 UNPACK_EX 257
6 STORE_NAME 0 (first)
9 STORE_NAME 1 (rest)
12 STORE_NAME 2 (last)
18 RETURN_VALUE
>>> first, *rest, last = ['a', 'b', 'c'] # ???
``````

### Functions wrap up

• basic syntax
• minimal example
• packing, unpacking

### 👁 Scopes

``````>>> def wrapper():
def identity(x):
return x
return identity
...
>>> f = wrapper()
>>> f(42)
42
``````

LEGB Rule: LocalEnclosingGlobalBuilt-in
The search is done during execution, not definition.

``````>>> min                 # builtin
<built-in function min>
>>> min = 42            # global
>>> def f(*args):
min = 2
def g():        # enclosing
min = 4     # local
print(min)
...
``````

### 🤔 Is Python a functional programming language?

Guido van Rossum

I have never considered Python to be heavily influenced by functional languages, no matter what people say or think. I was much more familiar with imperative languages such as C and Algol 68 and although I had made functions first-class objects, I didn’t view Python as a functional programming language.

We have anonymous functions!

``````>>> lambda arguments: expression
# which is ...
>>> def <lambda>(arguments):
return expression
>>> lambda foo, *args, bar=None, **kwargs: 42
<function <lambda> at 0x100fb9730>
``````

map applies the given function to every iterable (sequence element)

``````>>> map(indentity, range(4))
<map object at 0x100fc4c88>
>>> list(map(identity, range(4)))
[0, 1, 2, 3]
>>> set(map(lambda x: x % 7, [1, 9, 16, -1, 2, 5]))
{1, 2, 5, 6}
>>> map(lambda s: s.strip(), open("./HBA1.txt"))
<map object at 0x100fc4cc0>
``````

filter removes any element, which is not satisfying the given predicat

``````>>> filter(lambda x: x % 2 != 0, range(10))
<filter object at 0x1011edfd0>
>>> list(filter(lambda x: x % 2 != 0, range(10)))
[1, 3, 5, 7, 9]
``````

zip builds tuple sequence from elements of multiple sequences

``````>>> list(zip("abc", range(3), [42j, 42j, 42j]))
[('a', 0, 42j), ('b', 1, 42j), ('c', 2, 42j)]
``````

We have sequence (list, dict, …) generators

``````>>> [x ** 2 for x in range(10) if x % 2 == 1]
[1, 9, 25, 49, 81]
``````

is better than

``````>>> list(map(lambda x: x ** 2,
filter(lambda x: x % 2 == 1,
range(10))))
[1, 9, 25, 49, 81]
``````

Which can be nested

``````>>> nested = [range(5), range(8, 10)]
>>> [x for xs in nested for x in xs] # flatten
[0, 1, 2, 3, 4, 8, 9]
``````

Sets and dictionaries generators

``````>>> {x % 7 for x in [1, 9, 16, -1, 2, 5]}
{1, 2, 5, 6}
>>> date = {"year": 2014, "month": "September", "day": ""}
>>> {k: v for k, v in date.items() if v}
{'month': 'September', 'year': 2014}
>>> {x: x ** 2 for x in range(4)}
{0: 0, 1: 1, 2: 4, 3: 9}
``````

### 🔥 Scopes and functional programminng wrap up

• LEGB Rule: LocalEnclosingGlobalBuilt-in
• one-line lambdas
• map, filter, zip
• generators

### � PEP-8

Why? To improve readability. Basic rules:

• 4 spaces for indentation
• 79 symbols for a line of code 🤔
• variables_and_functions, CONSTANTS_YEAH, JustClasses
• think how it can be read

To check equality…

• for objects: `!=` and `==`
• for singletons: `is` and `is not`
• for booleans: use the bool itself, e.g. `if foo:`

Write like it’s English

• `if not key in d` 🤢
• `if key not in` 🤗

And write docstrings!

Full set of rules: https://www.python.org/dev/peps/pep-0008/