%%HTML
<blockquote class="reddit-card" data-card-created="1570178057"><a href="https://www.reddit.com/r/ProgrammerHumor/comments/cqhqwa/oh_my_using_system/">Oh my using System;</a> from <a href="http://www.reddit.com/r/ProgrammerHumor">r/ProgrammerHumor</a></blockquote>
<script async src="//embed.redditmedia.com/widgets/platform.js" charset="UTF-8"></script>
%%HTML
<blockquote class="reddit-card" data-card-created="1570177684"><a href="https://www.reddit.com/r/ProgrammerHumor/comments/6ee70n/whats_the_point_in_declaring_the_data_type_of_a/">What's the point in declaring the data type of a variable anyway?</a> from <a href="http://www.reddit.com/r/ProgrammerHumor">r/ProgrammerHumor</a></blockquote>
<script async src="//embed.redditmedia.com/widgets/platform.js" charset="UTF-8"></script>
%%HTML
<blockquote class="reddit-card" data-card-created="1570177858"><a href="https://www.reddit.com/r/ProgrammerHumor/comments/dbuo2k/its_share_time/">It's share time</a> from <a href="http://www.reddit.com/r/ProgrammerHumor">r/ProgrammerHumor</a></blockquote>
<script async src="//embed.redditmedia.com/widgets/platform.js" charset="UTF-8"></script>
! pip3.8 install --user black blackcellmagic isort
%load_ext blackcellmagic
# in:
import os
def very_important_function(template: str, *variables, file: os.PathLike, engine: str, header: bool = True, debug: bool = False):
"""Applies `variables` to the `template` and writes to `file`."""
with open(file, 'w') as f:
...
%%black
# out:
def very_important_function(
template: str,
*variables,
file: os.PathLike,
engine: str,
header: bool = True,
debug: bool = False,
):
"""Applies `variables` to the `template` and writes to `file`."""
with open(file, "w") as f:
...
Example function usages from open source projects: https://www.programcreek.com/python/
Recomended python blog: https://realpython.com
%%HTML
<img src="https://i.imgflip.com/3ca7td.jpg" title="made at imgflip.com"/>
Python compiles source code .py files to python bytecode .pyc files (__pycache__ directory). Bytes code is then interpreted by python.
import this
numbers = [1, 2, 3, 4]
def is_even(n):
return n % 2 == 0
list(filter(is_even, numbers))
list(map(is_even, numbers))
[number * 2 for number in range(10) if number % 2 != 0]
list(map(lambda number: number * 2, filter(lambda number: number % 2 != 0, range(10))))
Btw, there's also dict comprehension
list(zip(names, surnames))
names = ["Janusz", "Guido"]
surnames = ["Pawlak", "van Rossum"]
{name: surname for name, surname in zip(names, surnames)}
Python oneliners are great
import os
open("key", "wb").write(os.urandom(16))
Read binary file and output it in uppercase hex strnig with spaces in between:
" ".join(f"{byte_:02X}" for byte_ in open("key", "rb").read())
%%HTML
<blockquote class="reddit-card" data-card-created="1570177536"><a href="https://www.reddit.com/r/ProgrammerHumor/comments/d46pmg/from_hell_import_depression_as_magic/">from hell import depression as magic</a> from <a href="http://www.reddit.com/r/ProgrammerHumor">r/ProgrammerHumor</a></blockquote>
<script async src="//embed.redditmedia.com/widgets/platform.js" charset="UTF-8"></script>
x = 10
"x = %d" % x # python2 style
"x = " + str(x) # get's complex with multiple concatenation
"x = {}".format(x)
"x = {x}".format(x=x)
f"x = {x}" # python 3.6
f"{x + 2 * 2}" # any valid expression works
"x = {x}".format(**vars())
f"{x=}" # python 3.8
f"{x + 2 * 2 =}" # python 3.8
2 * 3
2 ** 3
def f(*args):
print(f"{type(args) = }")
print(f"{args = }")
f(1, "a", [1,2,3])
def f(x, *just_an_identifier):
print(f"{type(just_an_identifier) = }")
print(f"{x = }")
print(f"{just_an_identifier = }")
f(1, "a", [1, 2, 3])
def f(x, *args, **kwargs):
print(f"{type(kwargs) = }")
print(f"{locals() = }")
f(1, "a", [1, 2, 3], named_param="b")
f(1, "a", named_param="b", [1, 2, 3])
Problem:
point = (1, 2)
def f(x, y):
print(f"{locals() = }")
f(point[0], point[1])
f(*point)
f(*"ab")
Same goes for dict:
def f(a, b, c):
print(f"{locals() = }")
kwargs = {"a": 1, "b": 2, "c": 3}
# you can also generate the same dict using zip :D
kwargs = dict(zip("abc", range(1, 4)))
f(a = kwargs['a'], b = kwargs['b'], c = kwargs['c'])
f(**kwargs)
Most common usecase - parameter forwarding:
def forward_params(func, *args, **kwargs):
func(*args, **kwargs)
Using the same argument set in many function calls:
import subprocess
subprocess.run("ls", stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
subprocess.run("whoami", stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
subprocess.run("pwd", stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, check=True)
run_kwargs = {"stdout": subprocess.PIPE, "stderr": subprocess.PIPE, "shell": True}
subprocess.run("ls", **run_kwargs)
subprocess.run("whoami", **run_kwargs)
subprocess.run("pwd", **run_kwargs, check=True)
point = (1, 2, 3)
x, y, z = point
for var_name in "xyz":
print(f"{var_name} = {locals()[var_name]}")
point3d = (1, 2, 3)
*point2d, z = point3d
print(f"{z = }")
print(f"{point2d = }")
student_with_grades = ("Antoni", 2, 2, 3, 2, 2)
student, *grades = student_with_grades
print(f"{student = }")
print(f"{grades = }")
student_with_grades = ("Antoni", 2, 2, 3, 2, 2)
_, *grades = student_with_grades
print(f"{_ = }")
print(f"{grades = }")
_ is just an identifier. The same as grades. It's a common practice to use _ when you want to ignore some value
def f(*, a, b):
pass
f(1, 2)
f(a=1, b=2)
def f(a, *, b):
pass
f(1, 2)
f(1, b=2)
Btw - positional only arguments:
def f(a, / , b): # python 3.8
pass
f(a=1, b=2)
Btw valrus operator in python 3.8:
a = [0]*11
if (n := len(a)) > 10:
print(f"List is too long ({n} elements, expected <= 10)")
Functions are objects
def f():
if not hasattr(f, "call_count"):
f.call_count = 0
f.call_count += 1
f()
f()
print(f.call_count)
def Person(name):
def new_person():
pass
new_person.say_hello = lambda: f"My names is {new_person.name}"
new_person.name = name
return new_person
janusz = Person(name="Janusz")
janusz.say_hello()
janusz.name
Numbers are also objects:
x = 1
dir(x)
from inspect import getfullargspec
getfullargspec(x.to_bytes)
Function documentation shortcut in ipython
x.to_bytes?
x.to_bytes??
help(x.to_bytes)
x.to_bytes(8,'big')
Identity vs equality
a = [1, 2, 3]
b = [1, 2, 3]
a == b
a is b
x = 1
y = 1
x is y
x = -25
y = -25
x is y
That's because python preallocates most commonly used numbers (-5; 255) as singletons to improve performance.
The is keyword is mostly used to compare True, False and None objects
True is not None
The implementation of "is" is to compare id of the compared objects. Id in Cpython is just an memory address.
id(None)
A reference (variable) can change the type it's pointing to:
x = "a"
x = 1
You can even change class of some existing object:
class A:
x = 10
def f(self):
return "A"
class B:
def f(self):
return "B"
a, b = A(), B()
a.__class__ = b.__class__
a.f()
a.x
a.x = 10
a.x
a.f = lambda: "A"
a.f()
a.f = B.f
a.f(a)
Python has to compute the same value in a loop, because you could change the function from different thread:
from threading import Timer
from time import sleep
def some_long_function(a, b):
sleep(0.3)
return a + b
def break_long_function():
global some_long_function
some_long_function = None
Timer(0.6, break_long_function).start()
for i in range(10000000):
print(i)
some_long_function(1, 1)
Useful when you want to do something before or after each function call. For example, measuring execution time:
from datetime import datetime
def call_and_print_time(func):
now = datetime.now()
func()
print("Execution time: ", datetime.now() - now)
def f():
print("Doing work")
from time import sleep
sleep(0.3)
call_and_print_time(f)
def decorator(f):
return f
@decorator
def decorated():
pass
It works because @ is just a syntactic sugar for:
decorated = decorator(decorated)
Simplest functional decorator
from datetime import datetime
def call_and_print_time(func):
now = datetime.now()
func()
print("Execution time: ", datetime.now() - now)
def execution_time_printed(func):
return lambda: call_and_print_time(func)
@execution_time_printed
def f():
print("Doing work")
from time import sleep
sleep(0.3)
f()
But since functions can be defined inside other functions it's a common practice to do it this way:
from datetime import datetime
def execution_time_printed(func):
def call_and_print_time():
now = datetime.now()
func()
print("Execution time: ", datetime.now() - now)
return call_and_print_time
@execution_time_printed
def f():
print("Doing work")
from time import sleep
sleep(0.3)
f()
Let's add arguments delegation and return value
from datetime import datetime
def execution_time_printed(func):
def call_and_print_time(*args, **kwargs):
now = datetime.now()
return_value = func(*args, **kwargs)
print("Execution time: ", datetime.now() - now)
return return_value
return call_and_print_time
@execution_time_printed
def add(x, y):
return x + y
add(1, 2)
add.__name__
"\U0001f622"
We could fix this by adding call_and_print_time.__name__ = func.__name__ but there's more metadata to fix (i.e. docstring). @wraps to the rescue
from datetime import datetime
from functools import wraps
def execution_time_printed(func):
@wraps(func)
def call_and_print_time(*args, **kwargs):
now = datetime.now()
return_value = func(*args, **kwargs)
print("Execution time: ", datetime.now() - now)
return return_value
return call_and_print_time
@execution_time_printed
def add(x, y):
return x + y
add.__name__
Here comes the real inception - decorator with arguments have to return a decorator function
@execution_time_printed(file=sys.stderr) <- bolded must return a decorator
def decorator():
def real_decorator(func):
return func
return real_decorator
@decorator()
def decorated():
pass
decorated = decorator()(decorated)
import io
import sys
from datetime import datetime
from functools import wraps
def execution_time_printed(file=sys.stdout):
def decorator(func):
@wraps(func)
def call_and_print_time(*args, **kwargs):
now = datetime.now()
return_value = func(*args, **kwargs)
print("Execution time: ", datetime.now() - now, file=file)
return return_value
return call_and_print_time
return decorator
string_stream = io.StringIO()
@execution_time_printed(file=string_stream)
def add(x, y):
return x + y
add(1, 2)
string_stream.seek(0)
print(f"{string_stream.read() = }")
But now you always needs to call the decorator
@execution_time_printed
def add(x, y):
return x + y
add(1,2)
@execution_time_printed()
def add(x, y):
return x + y
add(1,2)
That's how all decorators with arguments should be implemented:
import io
import sys
from datetime import datetime
from functools import wraps
def execution_time_printed(func_=None, file=sys.stdout):
def decorator(func):
@wraps(func)
def call_and_print_time(*args, **kwargs):
now = datetime.now()
return_value = func(*args, **kwargs)
print("Execution time: ", datetime.now() - now, file=file)
return return_value
return call_and_print_time
if func_ is None:
return decorator
else:
return decorator(func_)
@execution_time_printed
def add(x, y):
return x + y
add(1, 2)
@execution_time_printed()
def add(x, y):
return x + y
add(1, 2)
@execution_time_printed(file=sys.stderr)
def add(x, y):
return x + y
add(1, 2)
Create an object with a variable that specifies the type:
cls = list
list([1,2,3])
cls([1,2,3])
Classes can also be decorated:
import functools
def singleton(cls):
"""Make a class a Singleton class (only one instance)"""
@functools.wraps(cls)
def wrapper_singleton(*args, **kwargs):
if not wrapper_singleton.instance:
wrapper_singleton.instance = cls(*args, **kwargs)
return wrapper_singleton.instance
wrapper_singleton.instance = None
return wrapper_singleton
@singleton
class TheOne:
pass
a, b = TheOne(), TheOne()
a is b
from itertools import count
def add_id(original_class):
orig_init = original_class.__init__
id_counter = count()
def __init__(self, *args, **kws):
self.id = next(id_counter)
orig_init(self, *args, **kws)
original_class.__init__ = __init__
return original_class
@add_id
class Person:
def __init__(self, name):
self.name = name
Person('Janusz').__dict__
vars(Person('Guido'))
Example of class decorator from standard library - dataclass.
from dataclasses import dataclass, field
@dataclass()
class Person:
name: str
surname: str
id = field
import functools
class CountCalls:
def __init__(self, func):
functools.update_wrapper(self, func)
self.func = func
self.num_calls = 0
def __call__(self, *args, **kwargs):
self.num_calls += 1
print(f"Call {self.num_calls} of {self.func.__name__!r}")
return self.func(*args, **kwargs)
@CountCalls
def say_whee():
print("Whee!")
say_whee()
say_whee()
Inheritance with decorator :D (Don't do this)
def inherit_from(*bases):
def wrapper(cls):
class Derived(cls, *bases):
pass
return Derived
return wrapper
class A:
def f(self):
print("Hello world")
@inherit_from(A)
class B:
pass
B().f()
def factorial(n: int)-> int:
if n < 2:
return n
return n * factorial(n-1)
%%timeit
factorial(1000)
def cache_factorial(n: int) -> int:
if n not in cache_factorial.__cache__:
cache_factorial.__cache__[n] = n * cache_factorial(n - 1)
return cache_factorial.__cache__[n]
cache_factorial.__cache__ = {1: 1}
Now the second call with the same params will return cached value:
%time _ = cache_factorial(1000)
%time _ = cache_factorial(1000)
%%timeit
cache_factorial(1000)
This mechanism can be generalised into a very simple decorator:
import functools
def cache(func):
"""Keep a cache of previous function calls"""
@functools.wraps(func)
def wrapper_cache(*args, **kwargs):
cache_key = args + tuple(kwargs.items())
if cache_key not in wrapper_cache.cache:
wrapper_cache.cache[cache_key] = func(*args, **kwargs)
return wrapper_cache.cache[cache_key]
wrapper_cache.cache = dict()
return wrapper_cache
@cache
def factorial(n):
if n < 2:
return n
return n * factorial(n-1)
%%timeit
factorial(1000)
There is a similar decorator in python standard library:
from functools import lru_cache
@lru_cache(maxsize=1010)
def factorial(n):
if n < 2:
return n
return n * factorial(n - 1)
%%timeit
factorial(1000)
It's faster than my implementation because it's implemented in C :P
https://github.com/python/cpython/blob/master/Modules/_functoolsmodule.c#L863
Sometimes the expected object type can tell you more than documentation. It's also very useful to make code autocomplete in your IDE more inteligent:
from typing import Optional, Union, List, Dict, Any
def f(a: int, b: List[int], c: Dict[str, List[str]], e: Any, d: Optional[str] = None) -> Union[int, str]:
if a == 1:
return 1
return "1"
def get_int():
return 2
x : int = get_int()
import functools
from inspect import getfullargspec
def typechecked(func):
@functools.wraps(func)
def wrapper(**kwargs):
argspec = getfullargspec(func)
for arg_name, declared_type in argspec.annotations.items():
if not isinstance(kwargs.get(arg_name), declared_type):
raise RuntimeError(
f"error: invalid conversion from {type(kwargs.get(arg_name))} to {declared_type} [-fpermissive]"
)
return func(**kwargs)
return wrapper
@typechecked
def f(a: int, b: str):
pass
f(a=1, b="2")
f(a="2", b=1)
It's implemented in: https://github.com/prechelt/typecheck-decorator
Make the whole module type-checked :D
import types
import functools
def decorate_all_in_module(module, decorator):
for name in dir(module):
obj = getattr(module, name)
if isinstance(obj, types.FunctionType):
setattr(module, name, decorator(obj))
import some_module
decorate_all_in_module(some_module, typechecked)
some_module.f1(int_arg="a")
%%HTML
<blockquote class="reddit-card" data-card-created="1570178753"><a href="https://www.reddit.com/r/ProgrammerHumor/comments/d900nx/ah_yespython_finally_pays_out/">ah yes...python finally pays out.</a> from <a href="http://www.reddit.com/r/ProgrammerHumor">r/ProgrammerHumor</a></blockquote>
<script async src="//embed.redditmedia.com/widgets/platform.js" charset="UTF-8"></script>