====== Python 3 ======
===== New features =====
==== Format string ====
a = 3
b = 'hola'
c = f'{hola} {a} veces'
==== Template strings ====
import os
from string import Template
class TemplatesMixin:
TEMPLATES_DIR = None
def _read_template(self, template_path):
with open(os.path.join(self.TEMPLATES_DIR, template_path)) as template:
return template.read()
def render(self, template_path, **kwargs):
return Template(
self._read_template(template_path)
).substitute(**kwargs)
Hi $name
==== Merge dicts ====
>>> x = {'a': 1, 'b': 2}
>>> y = {'b': 3, 'c': 4}
>>> z = {**x, **y}
>>> z
{'c': 4, 'a': 1, 'b': 3}
==== Append lists ====
>>> a = [1, 2]
>>> b = [3, 4]
>>> a + b
[1, 2, 3, 4]
==== Data Classes ====
@dataclass
class Process:
PID: int
PPID: int
cmd: str
The ''__init__'' method will already be in your class.
Note that here type hinting is required, that is why I have used int and str in the example. If you don't know the type of your field, you [[https://docs.python.org/3/library/typing.html#typing.Any|can use Any from the typing module]].
The Data Class has many advantages compared to the proposed solutions:
* It is explicit: all fields are visible, which respects the Zen of Python and makes it readable and maintainable. Compare it to the use of **kwargs and loop over it calling setattr.
* It can have methods. Just like any other class. The absence of methods, if you want to use them, is one downside of namedtuple.
* It allows you to go beyond the automatic __init__ using the __post_init__ method.
==== Function Annotations ====
You can add documentation to the parameters and the return value:
def add(a:"first number" = 0,
b:"second number" = 0) -> "sum of a and b":
return a+b
for item in add.__annotations__.items():
print(item)
# ('a', 'first number')
# ('b', 'second number')
# ('return', 'sum of a and b')
===== asyncio package =====
Since Python 3.5 we can write co-routines with async/await keywords. Actions on co-routines are allowed by the ''asyncio'' package.
Co-routines are functions that in a way similar to green threads are executed in a single thread and process. However, they are not so costly as are not managed by the operating system but by the **event loop**.
For an advance use go to [[wiki2:python:notes#advanced_asyncio|Advanced asyncio]].
==== Basic points ====
* We define co-routines with the ''async'' keyword before a function definition.
* We can call co-routines once the event loop has been started.
* ''await'' keyword means that at that point is safe to change co-routine.
* In other words, the code of a co-routine is synchronous until it finds an ''await''.
* Awaitable objects are those that can be used with an ''await'' expression. There are: co-routines, tasks, and futures.
=== async functions are co-routines ===
async def a_greet(name):
print (f'Hello {name}')
print(a_greet('Potato')) #
=== We execute co-routines on the event loop ===
import asyncio
async def a_greet(name):
print (f'Hello {name}')
asyncio.run(a_greet('Potato')) # Hello Potato
=== A basic async program ===
import asyncio
import time
async def say_after(delay, what):
await asyncio.sleep(delay)
print(what)
async def main():
print(f"started at {time.strftime('%X')}")
await say_after(1, f"{time.strftime('%X')}: hello")
await say_after(2, f"{time.strftime('%X')}: world")
print(f"finished at {time.strftime('%X')}")
asyncio.run(main())
# started at 11:31:12
# 11:31:12: hello
# 11:31:13: world
# finished at 11:31:15
=== It's mandatory to call co-routines with await ===
import asyncio
async def nested():
return 42
async def main():
nested() # RuntimeWarning: coroutine 'nested' was never awaited. It was never called.
print(await nested()) # will print "42".
asyncio.run(main())
=== We can run several co-routines ===
import asyncio
async def nested(value):
print(value)
async def main():
tasks = [nested(i) for i in range(10)]
await asyncio.gather(*tasks)
asyncio.run(main())
=== You can make async for's and with's ===
async def func():
async with db.connect() as conn:
async for user in db.query_users():
print(user.name)
==== Operations on awaitables ====
Cancelling
[[https://docs.python.org/3/library/asyncio-task.html#shielding-from-cancellation|Shielding]]: ''asyncio.shield(async_func_call)'': Avoids the ''async_func_call'' to be cancelled. It can happen that the function that calls the shielded function is cancelled, with this the ''async_func_call'' won't stop given that case.
Set a time out with ''coroutine asyncio.wait_for''.
Get all unfinished tasks with ''asyncio.all_tasks''.
==== Tasks ====
Tasks are used to schedule co-routines.
You create tasks with the ''asyncio.create_task'' function.
import asyncio
async def nested(value):
return value
async def main():
task = asyncio.create_task(nested(3))
await task
print(task.result())
asyncio.run(main())
Interesting methods are:
* ''cancel()''
* ''cancelled()'', ''done()'', to check the status
* ''add_done_callback''
While a Task is running in the event loop, no other Tasks can run in the same thread. When a Task executes an await expression, the running Task gets suspended, and the event loop executes the next Task.
To schedule a callback from a different OS thread, the ''loop.call_soon_threadsafe()''.
==== The event loop ====
You can run co-routines thanks to the event loop.
The most basic function for running a co-routine into the event loop is ''asyncio.run''. You can give it a parameter like a ''main'' co-routine/function which will call other co-routines. This function is a high-level and high-level functions are advised to be used.
Functions to manage the event loop are:
* ''asyncio.get_running_loop()'' which returns the event loop of the current thread. If it does not exist, an exception will be raised.
* ''asyncio.get_event_loop()'' which returns the event loop of the current thread. If it does not exist, one will be crated.
* ''asyncio.set_event_loop(loop)'' sets ''loop'' as the current event loop for the current thread.
* ''asyncio.new_event_loop()''
Methods of an event loop:
* ''run_until_complete(future)''
* ''stop()''
* ''is_running()''
* ''close()''
=== Scheduling ===
''call_soon'' and ''call_soon_threadsafe'' will a sync function at the next iteration of the loop. ''call_soon_threadsafe'' must be used to schedule callbacks from another thread.
import asyncio
def prnt(value):
print(f"Hola {value}")
async def arange(count):
for i in range(count):
yield(i)
async def main():
asyncio.get_event_loop().call_soon(prnt, "hello")
prnt("hola")
async for i in arange(3):
print(i)
await asyncio.sleep(0)
asyncio.run(main(), debug=True)
# Hola hola
# 0
# Hola hello
# 1
# 2
''call_later'' and ''call_at'' allow to set when calling a non-async-function scheduled by loop iterations.
''create_task'' and ''create_future'' . There is also the ''set_task_factory'' if you want to create your own task instances.
==== Futures and executors ====
A ''Future'' represents an eventual result of an asynchronous operation.
async def set_after(fut, delay, value):
await asyncio.sleep(delay)
fut.set_result(value)
async def main():
loop = asyncio.get_running_loop()
fut = loop.create_future()
loop.create_task(set_after(fut, 1, '... world')) # We could have just used "asyncio.create_task()".
print('hello ...')
print(await fut) # Wait until *fut* has a result (1 second) and print it.
asyncio.run(main())
An instance of ''Executor'' provides methods to execute calls asynchronously. It should not be used directly, but through its concrete subclasses.
async def main():
loop = asyncio.get_running_loop()
result = await loop.run_in_executor( # 1. Returns a Future.Executor
None, blocking_io)
print('default thread pool', result)
with concurrent.futures.ThreadPoolExecutor() as pool: # 2. Run in a custom thread pool:
result = await loop.run_in_executor(
pool, blocking_io)
print('custom thread pool', result)
with concurrent.futures.ProcessPoolExecutor() as pool: # 3. Run in a custom process pool:
result = await loop.run_in_executor(
pool, cpu_bound)
print('custom process pool', result)
''ThreadPoolExecutor'' uses a pool of threads in the same way than ''ProcessPoolExecutor'' uses a pool of processes.
executor = ThreadPoolExecutor(max_workers=2)
a = executor.submit(wait_on_b)
b = executor.submit(wait_on_a)
==== Other tools ====
=== Streams ===
* [[https://docs.python.org/3/library/asyncio-stream.html]]
These allow to manage IO resources (sockets) asynchronously.
=== Synchronization ===
* [[https://docs.python.org/3/library/asyncio-sync.html]]
You can make use of the usual tools for synchronize co-routines (Lock, Event, Condition, Semaphore, BoundedSemaphore).
=== Queues ===
* [[https://docs.python.org/3/library/asyncio-queue.html]]
Asynchronous queues that can be used to distribute workload between several concurrent tasks:
import asyncio
import random
import time
async def worker(name, queue):
while True:
sleep_for = await queue.get()
await asyncio.sleep(sleep_for)
queue.task_done()
async def main():
queue = asyncio.Queue()
total_sleep_time = 0
for _ in range(20):
sleep_for = random.uniform(0.05, 1.0)
total_sleep_time += sleep_for
queue.put_nowait(sleep_for)
# Create three worker tasks to process the queue concurrently.
tasks = []
for i in range(3):
task = asyncio.create_task(worker(f'worker-{i}', queue))
tasks.append(task)
# Wait until the queue is fully processed.
started_at = time.monotonic()
await queue.join()
total_slept_for = time.monotonic() - started_at
# Cancel our worker tasks.
for task in tasks:
task.cancel()
# Wait until all worker tasks are cancelled.
await asyncio.gather(*tasks, return_exceptions=True)
print('====')
print(f'3 workers slept in parallel for {total_slept_for:.2f} seconds')
print(f'total expected sleep time: {total_sleep_time:.2f} seconds')
asyncio.run(main())
==== Notes on co-routines ====
=== Old-school co-routines ===
These are based on generators (since Python 3.5 they are not useful anymore):
@asyncio.coroutine
def old_style_coroutine():
yield from asyncio.sleep(1)
async def main():
await old_style_coroutine()
=== Debug mode ===
You can debug co-routines using the next configurations:
* Setting the PYTHONASYNCIODEBUG environment variable to 1.
* Using the -X dev Python command line option.
* Passing ''debug=True'' to ''asyncio.run()''.
* Calling ''loop.set_debug()''.
With this:
* asyncio checks for co-routines that were not awaited and logs them
* Non-threadsafe asyncio APIs raise an exception if they are called from a wrong thread.
* The execution time of the I/O selector is logged if it takes too long to perform an I/O operation.
* Callbacks taking longer than 100ms are logged.
=== Co-routines do not make code fast ===
* [[https://news.ycombinator.com/item?id=23498742|Discussion on Hacker News]]
=== functools.partial for passing arguments ===
''functools.partial'' allows to create a function that not requires arguments from one that requires them.
loop.call_soon(partial(print, "Hello", flush=True))
=== Stop co-routines when signals ===
import asyncio
import functools
import os
import signal
def ask_exit(signame, loop):
print("got signal %s: exit" % signame)
loop.stop()
async def main():
loop = asyncio.get_running_loop()
for signame in {'SIGINT', 'SIGTERM'}:
loop.add_signal_handler(
getattr(signal, signame),
functools.partial(ask_exit, signame, loop))
await asyncio.sleep(3600)
print("Event loop running for 1 hour, press Ctrl+C to interrupt.")
print(f"pid {os.getpid()}: send SIGINT or SIGTERM to exit.")
asyncio.run(main())
===== Enums =====
from enum import Enum
class Numbers(Enum):
ONE = 1
TWO = 2
number = Numbers(2) #
number1 = Numbers["ONE"] #
members = [n for n in Numbers] # [, ]
values = [n.value for n in Numbers] # [1, 2]
==== Links ====
* https://realpython.com/python-enum/
===== Python type checking =====
pi: float = 3.142
def headline(text: str, align: bool = True) -> str:
pass
The ''MyPy'' library allows to check code files.
>>> names: list = ["Guido", "Jukka", "Ivan"]
>>> version: tuple = (3, 7, 1)
>>> options: dict = {"centered": False, "capitalize": True}
>>> from typing import Dict, List, Tuple
>>> names: List[str] = ["Guido", "Jukka", "Ivan"]
>>> version: Tuple[int, int, int] = (3, 7, 1)
>>> options: Dict[str, bool] = {"centered": False, "capitalize": True}
import random
from typing import Any, Sequence
def choose(items: Sequence[Any]) -> Any:
return random.choice(items)
class Animal:
def __init__(self, name: str, birthday: date) -> None:
self.name = name
self.birthday = birthday
@classmethod
def newborn(cls, name: str) -> "Animal":
return cls(name, date.today())
def twin(self, name: str) -> "Animal":
cls = self.__class__
return cls(name, self.birthday)
def headline(text, width=80, fill_char="-"):
# type: (str, int, str) -> str
return f" {text.title()} ".center(width, fill_char)
def headline(
text, # type: str
width=80, # type: int
fill_char="-", # type: str
): # type: (...) -> str
return f" {text.title()} ".center(width, fill_char)
===== Notes =====
==== venv para crear entornos virtuales ====
$ python3 -m venv myenv
$ source myenv/bin/activate
To deactivate:
$ deactivate
==== Configure Python3 for a Python2 system ====
# Replace python2 by python3
sudo ln -s /usr/bin/python3.5 /usr/bin/python
# Fix pip3
sudo apt-get remove python3-pip; sudo apt-get install python3-pip
# Install VirtualEnvWrapper for python3
sudo pip3 install virtualenvwrapper
You can force reinstall pip:
curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
python3 get-pip.py --force-reinstall
sudo python3 -m pip uninstall pip && sudo apt install python3-pip --reinstall
sudo python -m pip uninstall pip && sudo apt install python-pip --reinstall
==== The __all__ keyword ====
It is a list of strings defining what symbols in a module will be exported when ''from import *'' is used on the module.
__all__ = ['bar', 'baz']
waz = 5
bar = 10
def baz(): return 'baz'
These symbols can then be imported like so:
from foo import *
print bar
print baz
# The following will trigger an exception, as "waz" is not exported by the module
print waz
If the ''__all__'' above is commented out, this code will then execute to completion, as the default behavior of ''import *'' is to import all symbols that do not begin with an underscore, from the given namespace.
''__all__'' affects the ''from import *'' behavior only. Members that are not mentioned in ''__all__'' are still accessible from outside the module and can be imported with ''from import ''
==== Gotchas with PIP ====
To install from downloaded packages:
pip install --no-index --find-links=/tmp/python -r requirements.txt