Functions#
What is a function?#
A reusable piece of code
def FUNC_NAME(ARGUMENTS):
BODY
Why do we need functions?#
Simplify the maintenance of the code base due to the code reuse
Logical structuring since it’s easier to work with smaller pieces of code
Self-documenting of the code via using meaningful names for function names and arguments
Function declaration#
def function():
pass
Hint: The keyword pass is a placeholder for do_nothing statement
Function call and value#
result = function()
print(result)
None
Info: In Python functions always reutrn a value. If the key word return is missing in the function's body, it returns None
A function of little use#
def meaning_of_life():
return 42
print(meaning_of_life)
print(meaning_of_life())
<function meaning_of_life at 0x1068a3520>
42
A more useful function#
def is_prime(n):
"""
Returns True iff n is prime
It's ok to have multiple returns in the function body
"""
if n == 1:
return False
for i in range(2, n):
if n % i == 0:
return False
return True
Now it’s time to test it in the contest!
Scope#
def add_a(x):
# local scope
x = x + a
return x
# global scope
a = 8
z = add_a(10)
print(z)
18
Variables from the global scope are available in all local scopes
def add_b(x):
# local scope
b = 8
x = x + b
return x
# global scope
z = add_b(10)
print(b)
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Input In [22], in <cell line: 9>()
7 # global scope
8 z = add_b(10)
----> 9 print(b)
NameError: name 'b' is not defined
Variables from the local scope are not available in the global scope
dir()
lists the global scope#
A_GLOBAL_SCOPE_CONSTANT = 42
dir()
['A_GLOBAL_SCOPE_CONSTANT',
'In',
'Out',
'_',
'_23',
'_24',
'_25',
'_26',
'_3',
'_5',
'_6',
'_7',
'__',
'___',
'__builtin__',
'__builtins__',
'__doc__',
'__loader__',
'__name__',
'__package__',
'__spec__',
'_dh',
'_i',
'_i1',
'_i10',
'_i11',
'_i12',
'_i13',
'_i14',
'_i15',
'_i16',
'_i17',
'_i18',
'_i19',
'_i2',
'_i20',
'_i21',
'_i22',
'_i23',
'_i24',
'_i25',
'_i26',
'_i27',
'_i28',
'_i3',
'_i4',
'_i5',
'_i6',
'_i7',
'_i8',
'_i9',
'_ih',
'_ii',
'_iii',
'_oh',
'a',
'add_a',
'add_b',
'exit',
'function',
'get_ipython',
'meaning_of_life',
'quit',
'register_cell_magic',
'result',
'spades',
'typecheck',
'z']
def add_1(x):
x = x + 1 # Which x to use? Local or global?
return x
x = 10 # creates a global variable x
z = add_1(12)
print(z)
print(x)
13
10
def add_1(x):
print("id of local x =", id(x))
x = x + 1 # creates a new local variable x
print("id of new local x =", id(x))
return x
x = 10 # creates a global variable x
print("id of global x =", id(x))
z = add_1(12)
id of global x = 4351427088
id of local x = 4351427152
if of new local x = 4351427184
LEGB#
n = 1000
def outer():
n = 100
def inner(lst):
return(sum(lst) / n)
print(inner([1, 2, 3, 42]))
outer()
0.48
Aliasing once again#
def list_modify(sample):
# here sample is local but aliasing!
last_element = sample.pop()
return sample
sample = [1, 2, 3] # global variable
new_sample = list_modify(sample)
print(f"old_sample = {sample}, new_sample = {new_sample}")
old_sample = [1, 2], new_sample = [1, 2]
def list_modify(sample, num):
# use copy to protect the global list
new_sample = sample.copy()
new_sample.append(num)
return new_sample
sample = [1, 2, 3] # global variable
new_sample = list_modify(sample, 42)
print(f"old_sample = {sample}, new_sample = {new_sample}")
old_sample = [1, 2, 3], new_sample = [1, 2, 3, 42]
Positional, keyword and default arguments#
def final_price(price, discount):
return price - price * discount / 100
# Both arguments are positional
print(final_price(1000, 5))
# The order is important!
print(final_price(5, 1000))
950.0
-45.0
def final_price(price, discount):
return price - price * discount / 100
# Both are keyword arguments
print(final_price(price=5000, discount=10))
# Any order of keyword arguments is allowed
print(final_price(discount=2, price=200))
# Positional and keyword
print(final_price(2000, discount=6))
4500.0
196.0
1880.0
All positional arguments should go before keyword ones
print(final_price(price=10000, 20))
Input In [38]
print(final_price(price=10000, 20))
^
SyntaxError: positional argument follows keyword argument
PEP 8: No spaces around the equality sign are allowed for keyword and default arguments
def final_price(price, discount, bonus=0):
# bonus by default equals 0
return price - price * discount / 100 - bonus
# No bonus by default
print(final_price(price=5000, discount=10))
# bonus is a keyword argument
print(final_price(price=5000, discount=10, bonus=100))
# A default agrument cannot be positional
# print(final_price(price=5000, discount=10, 100))
4500.0
4400.0
Yet another list trap#
def function(list_argument=[]):
list_argument.append("Hi!")
return list_argument
function()
['Hi!']
function()
['Hi!', 'Hi!']
function()
['Hi!', 'Hi!', 'Hi!']
Anti-pattern: using mutable objects as values of default arguments
Advice: Use None as default argument value
def function(list_argument=None):
if list_argument is None:
list_argument = []
list_argument.append("Hi!")
return list_argument
function()
['Hi!']
function()
['Hi!']
function()
['Hi!']
Type hints and annotations#
def relative_difference(x, y):
delta = x - y
mean = (x + y) / 2
if mean == 0.0:
return None
return abs(delta / mean)
relative_difference(1, 100)
1.9603960396039604
help(relative_difference)
Help on function relative_difference in module __main__:
relative_difference(x, y)
import typing as tp
def relative_difference(x: float, y: float) -> tp.Optional[float]:
"""
Compares two quantities taking into account their absolute values
And another line just to make an example of multiline docstrings
"""
delta = x - y
mean = (x + y) / 2
if mean == 0.0:
return None
return abs(delta / mean)
help(relative_difference)
Help on function relative_difference in module __main__:
relative_difference(x: float, y: float) -> Optional[float]
Compares two quantities taking into account their absolute values
And another line just to make an example of multiline docstrings
Variadic arguments#
def root_mean_square(args: tp.List[float]) -> float:
if not args:
return 0.0
squares_sum = sum(x ** 2 for x in args)
mean = squares_sum / len(args)
return mean ** 0.5
root_mean_square([4, 8, 15, 16, 23, 42])
21.80978373727412
*args#
def root_mean_square(*args: float) -> float:
if not args:
return 0.0
squares_sum = sum(x ** 2 for x in args)
mean = squares_sum / len(args)
return mean ** 0.5
root_mean_square(4, 8, 15, 16, 23, 42, 0.34, 1098.3435)
388.78216259112406
**kwargs#
def root_mean_square(*args: float, **kwargs: tp.Any) -> float:
verbose = kwargs.get('verbose', False)
if not len(args):
if verbose:
print('Empty arguments list!')
return 0.0
squares_sum = sum(x ** 2 for x in args)
if verbose:
print(f'Sum of squares: {squares_sum}')
mean = squares_sum / len(args)
if verbose:
print(f'Mean square: {mean}')
return mean ** 0.5
root_mean_square(4, 8, 15, 16, 23, 42, verbose=True)
Sum of squares: 2854
Mean square: 475.6666666666667
21.80978373727412
root_mean_square(verbose=True)
Empty arguments list!
0.0
Lambda functions#
sorted(['привет', 'как', 'дела'])
['дела', 'как', 'привет']
sorted(['привет', 'как', 'дела'], key=lambda string: len(string))
['как', 'дела', 'привет']
def string_len(string):
return len(string)
Strings#
a = 'The word you are looking for is "Hello".'
b = "I'll wait you there"
c = '''Тройные кавычки
для строк с переносами.
Русский язык поддерживается из коробки.
Как и любой другой'''
"And also" " you can " \
"split them in pieces"
'And also you can split them in pieces'
("And also" " you can "
"split them in pieces")
'And also you can split them in pieces'
Escape sequences#
\n — new line character
\t — horizontal tab
print('Hey\tFrank!\nHow are you?')
Hey Frank!
How are you?
Base string methods#
first = 'The Government'
second = '...considers these people "irrelevant".'
print(list(first))
['T', 'h', 'e', ' ', 'G', 'o', 'v', 'e', 'r', 'n', 'm', 'e', 'n', 't']
'irrelevant' in second, 'man' in second
(True, False)
Comparisons#
'a' < 'b'
True
'test' < 'ti'
True
'ёжик' < 'медвежонок'
False
ord('ё') < ord('м') # 1105 vs 1084
False
ord()
and chr()
#
ord(ch)
returns the Unicode code of the symbol ch
, chr(code)
gets back the symbol by its code
ord('a'), chr(97)
(97, 'a')
ord("A"), chr(65)
(65, 'A')
ord('ы'), chr(1099)
(1099, 'ы')
ord('€'), chr(8364)
(8364, '€')
spades = '\u2660'
print(spades)
print(ord(spades), hex(ord(spades)), chr(9824))
♠
9824 0x2660 ♠
Register#
test = 'We don\'t.'
test.upper()
"WE DON'T."
test.lower()
"we don't."
test.title()
"We Don'T."
Searching in strings#
secret = 'Hunted by the authorities, we work in secret.'
"by" in secret, "but" in secret
(True, False)
print(secret.count('e'))
6
secret.find('god'), secret.find('work')
(-1, 30)
Predicates#
"You'll never find us".endswith("find us") # also: .startswith
True
"16E45".isalnum(), "16".isdigit(), "q_r".isalpha()
(True, True, False)
"test".islower(), "Test Me".istitle()
(True, True)
Split & join#
header = 'ID\tNAME\tSURNAME\tCITY\tREGION\tAGE\tWEALTH\tREGISTERED'
'ID\tNAME\tSURNAME\tCITY\tREGION\tAGE\tWEALTH\tREGISTERED'
print(header.split())
['ID', 'NAME', 'SURNAME', 'CITY', 'REGION', 'AGE', 'WEALTH', 'REGISTERED']
print(', '.join(s.lower() for s in header.split()))
id, name, surname, city, region, age, wealth, registered
Format#
from datetime import datetime
"[{}]: Starting new process '{}'".format(
datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
'watcher'
)
"[2022-11-17 13:33:07]: Starting new process 'watcher'"
"{1}! My name is {0}!".format("John", "Hi")
'Hi! My name is John!'
f-strings#
name = 'John'
surname = 'Reese'
f'{name} {surname}'
'John Reese'
for value in [0.6, 1.0001, 22.7]:
print(f'value is {value:07.4f}')
value is 00.6000
value is 01.0001
value is 22.7000
comment = 'Added yet another homework solution'
commit_hash = '7a721ddd315602f94a7d4123ea36450bd2af3e89'
f'{commit_hash=}, {comment=}'
# self-documenting string
"commit_hash='7a721ddd315602f94a7d4123ea36450bd2af3e89', comment='Added yet another homework solution'"
some_random_url = 'https://yandex.ru/images/'
some_random_url.strip('/') # rstrip, lstrip
'https://yandex.ru/images'
string module#
import string
string.ascii_letters
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
string.ascii_lowercase
'abcdefghijklmnopqrstuvwxyz'
string.punctuation
'!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'
def to_string(*args, sep=" ", end='\n'):
result = ""
for arg in args:
result += str(arg) + sep
result += end
return result
to_string(1, 2, 3)
'1 2 3\n'
'1 2 3 \n'