3. gyakorlat¶
A gyakorlat anyaga¶
Kivételek ismétlése¶
A kivételkezeléssel kapcsolatos tananyag ismétlése a Szkriptnyelvek tanyagból itt.
class RantottaException(Exception):
def __init__(self, hany_tojas):
super(RantottaException, self).__init__()
self.hany_tojas = hany_tojas
def rantotta_keszit(tojas):
if tojas < 1:
raise RantottaException(tojas)
else:
print('Nyami, kesz a rantotta %d tojasbol' % tojas)
def problems(a, b=0):
if isinstance(a, int) and isinstance(b, int):
return a / b
else:
raise Exception(a)
def main():
print(problems(8, 2))
# print(problems(10))
# print(problems('Nagy baj van!'))
try:
problems(10)
except ZeroDivisionError as zde:
print('Nullaval nem osztunk, ejnye!')
# raise # Exception('Nullaval osztottunk!')
except Exception as exc:
print('Exception details:', exc)
finally:
print('Vege a veszelyes resznek! Huh!')
tojasok = input('Hany tojasbol sutnel rantottat?')
try:
rantotta_keszit(int(tojasok))
except RantottaException as re:
print('%d tojasbol nem lehet rantottat sutni!' % re.hany_tojas)
if __name__ == '__main__':
main()
Context managerek¶
- Ismétlés
- Jó és helytelen kontextuskezelők
- Saját context manager írása
class File(object):
def __init__(self, filename, mode):
print('File context manager: __init__')
self.filename = filename
self.mode = mode
def __enter__(self):
print('File context manager: __enter__')
self.open_file = open(self.filename, self.mode)
return self.open_file
def __exit__(self, exception_type, exception_value, traceback):
print('File context manager: __exit__')
self.open_file.close()
class WrongContextManager(object):
def __init__(self, *args):
print('Wrong context manager: __init__')
def __enter__(self):
print('Wrong context manager: __enter__')
if __name__ == '__main__':
ctx_mgr = File('foo.txt', 'w')
print("--")
with ctx_mgr as infile:
infile.write('foo')
print("--")
with File('foo.txt', 'w') as infile:
infile.write('foo')
# with WrongContextManager('Won\'t work') as wont_work:
# print(wont_work)
További olvasnivaló a témában:
- https://docs.python.org/3/reference/datamodel.html#with-statement-context-managers
- https://docs.python.org/3/library/contextlib.html
- https://alysivji.github.io/managing-resources-with-context-managers-pythonic.html
- http://preshing.com/20110920/the-python-with-statement-by-example/
- https://docs.python.org/3/library/contextlib.html
- PEP 343 -- a "with" utasítás PEP-je: https://www.python.org/dev/peps/pep-0343/
Dekorátorok¶
Ismerős a Szkriptnyelvek előadásról, ott már láttuk a használatát (egy egyszerű példán keresztül). Mivel ez egy nagyon hasznos tudás lehet, érdemes kicsit jobban is megbarátkozni vele. Az alábbiakban lépésről lépésre bonyolítjuk az egyszerű dekorátorunkat.
A dekorátorok gyakorlatilag megvalósítják a Decorator tervezési mintát, Python nyelven. A dekorátor tervezési minta az OOP tervezési mintái közé tartozik. Meglévő objektum funkcionalitását bővíthetjük vele, mégpedig úgy, hogy az objektum nem tud róla, hogy bővítve van.
A legegyszerűbb dekorátor valahogy így néz ki:
def first_decorator(func):
def inner(x, y):
print("< Before function call")
func(x, y)
print("< After function call")
return inner
@first_decorator
def foo(x, y):
print("The parameters were: ", x, y)
def foo2(x, y):
print("The parameters were: ", x, y)
# Dekoralt funkcio meghivasa
foo("First run", 100)
method = first_decorator(foo2)
method("Second run", 120)
A fenti megoldással az a probléma, hogy a paraméterek kötöttek, csak két darab lehet. Ezt könnyedén kiküszöbölhetjük a korábbról ismert tetszőleges paraméteres megoldással.
def first_decorator_keep_params(func):
def inner(*args, **kwargs):
print("< Before function call")
func(*args, **kwargs)
print("< After function call")
return inner
# @first_decorator # Nem mukodik
@first_decorator_keep_params
def add_numbers(*args):
"""
Adds infinite number of numbers
:param args: the numbers
:return: the sum of the numbers
"""
print(sum(args))
Ezek a megoldások elég jók, de nem 100 százalékosak, hiszen az eredeti függvények elveszítik az indentitásukat. Nézzük meg, hogy mi a függvény neve, vagy pedig a segítség meghívásával mi történik.
Ezt viszonylag könnyű megjavítani, a functools
modul wraps
dekorátora pont erre való:
import functools
def first_decorator_keep_params_indentity(func):
@functools.wraps(func)
def inner(*args, **kwargs):
print("< Before function call")
func(*args, **kwargs)
print("< After function call")
return inner
@first_decorator_keep_params_indentity
def add_numbers2(*args):
"""
Adds infinite number of numbers
:param args: the numbers
:return: the sum of the numbers
"""
print(sum(args))
add_numbers2(2, 3, 4, 5)
print(add_numbers2.__name__)
print("--- Help ---")
print(help(add_numbers2))
Dekorátorok paraméterekkel¶
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from functools import wraps
def debug(func):
"""Debug decorator
Segitsegevel lehet debug infokat kiirni a hivott metodusokrol
:param func: a metodus amit szeretnenk dekoralni
"""
def decorated(*args, **kwargs):
print("Called method", func.__name__)
return func(*args, **kwargs)
return decorated
def return_multiplier(*nums):
def _outer_wrapper(wrapped_function):
@wraps(wrapped_function)
def _wrapper(*args, **kwargs):
result = wrapped_function(*args, **kwargs)
for num in nums:
result = result * num
return result
return _wrapper
return _outer_wrapper
@debug
@return_multiplier(2, 3)
def func4():
return 'Na'
print(func4())
Generátorok¶
- yield vs return
def kiskutyak():
for i in range(101):
yield i+1
yield 'Elfogytunk.. :('
def main():
kiskutyak_generator = kiskutyak()
for kiskutya in kiskutyak_generator:
print(str(kiskutya) + '. kiskutya')
# ajjaj
print('next', next(kiskutyak_generator))
if __name__ == '__main__':
main()
Context managerek (contextlib)¶
- Context managerek készítése dekorátorral
from contextlib import contextmanager
import time
@contextmanager
def timed():
start_time = time.time()
yield
end_time = time.time()
print("Total execution time: {}".format(end_time - start_time))
def main():
with timed():
for i in range(100000):
i = i * 6
time.sleep(2)
if __name__ == '__main__':
main()
- ContextDecorator gyerekosztály készítése (context manager és dekorátor is egyben).
import time
from contextlib import ContextDecorator
class timed(ContextDecorator):
def __init__(self, method_name):
self.start = None
self.end = None
self.method_name = method_name
def __enter__(self):
self.start = time.time()
print("Starting at {}".format(self.start))
return self
def __exit__(self, exc_type, value, traceback):
self.end = time.time()
total = self.end - self.start
print("Method '{}' ended at {} (total: {})".format(self.method_name,
self.end, total))
def main():
with timed('for-range+sleep'):
for i in range(100000):
i = i * 6
time.sleep(2)
complex_compute()
@timed('complex_compute')
def complex_compute():
time.sleep(1)
if __name__ == '__main__':
main()
Egyébb hasznos dolgok a contextlibből¶
A contextlib
dokumentációja: https://docs.python.org/3/library/contextlib.html
contextlib.closing(thing)
: Bezárja a megnyitott dolgot (gyakorlatilag a működés végén meghívja az objektumclose()
metódusát.contextlib.suppress(*exceptions)
: Adott típusú kivételeket elnyelő kontextuskezelő.contextlib.redirect_stdout(new_target)
: Átirányítja az alapértelmezett kimenetet.contextlib.redirect_stderr(new_target)
: Átirányítja az alapértelmezett hibakimenetet.contextlib.AbstractContextManager
: Egy általános, üresen megvalósított kontextuskezelő váz (az__exit__
metódus absztrakt).contextlib.nullcontext
: Üres kontextuskezelő.-
contextlib.contextmanager
-
contextlib.AbstractAsyncContextManager
contextlib.asynccontextmanager
Összefoglaló a tanultakról itt
Órai feladatok¶
Megjegyzés
Az órai feladatok alatt lévő feladat(sor)ok azok a feladat(sor)ok, amelyek megoldása beleszámít a zh javításért megoldandó feladatok közé. Ezen feladat(sor)okból 6 darab megoldása szükséges ahhoz, hogy egy zh javíthatóvá váljon. Ha külön nem jelezzük, a teljes feladatsor megoldása esetén jár a zh javításért szerezhető pont.
Info
A teljes feladatsor megoldása szükséges a zh javításért járó pont megszerzéséért.
Swallow exceptions¶
A feladat egy olyan context manager/dekorátor elkészítése, ami elnyeli a megadott típusú kivételeket, például:
A kód végrehajtása esetén nem történik semmi, esetleg egy kiírás, például: ZeroDivisionError kivetel lenyelve
.
Amennyiben ez nem egyértelmű, ez csak egy példa, tetszőleges Exception leszármazott osztályra működnie kell a megírt kódnak.
Amennyiben más típusú kivétel érkezik, természetes ne történjen elnyelés, a hiba kerüljön továbbdobásra.
A feladat megoldásához ne használd a contextlib.suppress
metódust!
Better debug¶
A feladat egy dekorátor elkészítése, ami rendes debug hibaüzenetet ír ki: időpont, meghívott metódus neve, argumentumok, visszatérési érték, esetleg kivétel.
Tipp a dátumkezeléshez
A dátumkezeléshez használhatod a datetime
modult, az alábbi módon:
import datetime
print(datetime.datetime.now())
@debug
def division(n):
return n / 0
@debug
def another_division(n):
return n / 2
another_division(10)
Ennek kimenete:
Egy másik példa:
Ennek kimenete:
division parameterek: (10,) {}
raised: integer division or modulo by zero
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: integer division or modulo by zero
YOLO Cache¶
A feladat egy olyan dekorátor elkészítése, amely megfelelő cache-elést biztosít a dekorált függvény számára segítségével. Ehhez tárolja el a beérkező paramétereket, és a függvény által kiszámolt értéket. Ha legközelebb érkeznek olyan paraméterek, melyek már korábban voltak, és rögzítettük a visszatérési értékét, akkor ne hívja meg a függvényt, hanem a cache-ben tárolt értékkel térjen vissza, hiszen nem érünk rá örökké!
from contextlib import contextmanager
import time
@contextmanager
def timed():
start_time = time.time()
yield
end_time = time.time()
print("Total execution time: {}".format(end_time-start_time))
def cached(func):
pass
@cached
def factorial(n):
r = 1
i = 2
while i <= n:
r *= i
i += 1
return r
def main():
with timed():
for i in range(5000):
factorial(3000 + (i % 50))
if __name__ == '__main__':
main()
Deprecated dekorátor¶
A feladat egy olyan context manager/dekorátor elkészítése, ami figyelmezteti a felhasználót, hogy elavult függvényt használt. Fontos! Csak az első függvényhasználatkor szóljon, ha egymás után többször hívjuk meg, akkor az első alkalom után ne jelezzen.
Ennek kimenete:
flatten generátor¶
A feladat egy olyan generátor függvény írása, amelynek bejárható paramétereket adva transzparensen visszaadja az egymást követő bejárható dolgok elemeit.
Ennek kimenete:
Házi feladat¶
A YOLO Cache bővítése egy 'timeout' paraméter a cache-hez, hogy meddig maradjanak benne a tárolt értékek, ha nincsenek használva.