Kihagyás

3. gyakorlat

A gyakorlat anyaga

Kivételek ismétlése

A kivételkezeléssel kapcsolatos tananyag ismétlése a Szkriptnyelvek tanyagból itt.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
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
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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:

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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
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.

1
2
3
print(add_numbers.__name__)
print("--- Help ---")
print(help(add_numbers))
1
2
3
4
5
inner
--- Help ---
Help on function inner in module __main__:

    inner(*args, **kwargs)

Ezt viszonylag könnyű megjavítani, a functools modul wraps dekorátora pont erre való:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#!/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
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
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
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
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).
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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 objektum close() 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

Feladatok

1. 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:

1
2
3
with swallow_exceptions([ZeroDivisionError]):
    1 / 0
Nullaval valo osztas lenyelve

A feladat megoldásához ne használd a contextlib.suppress metódust!

2. 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:

1
2
import datetime
print(datetime.datetime.now())
1
2
3
4
5
6
7
@debug
def division(n):
    return n / 0

@debug
def another_division(n):
    return n / 2
1
another_division(10)
1
2
another_division parameterek:  (10,) {}
visszateresi ertek: 5
1
division(10)
1
2
3
4
5
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

3. YOLO Cache

A feladat egy olyan context manager/dekorátor elkészítése, amely csökkenti a CPU terhelését, mégpedig cache-elés 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 cace-ben tárolt értékkel térjen vissza, hiszen nem érünk rá örökké!

Pluszpontért: Egy 'timeout' paraméter a cache-hez, hogy meddig maradjanak benne a tárolt értékek, ha nincsenek használva.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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()

4. 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. Csak az első függvényhasználatkor szóljon.

5. 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 elemei.

1
2
    for i in flatten(range(3), range(3, 5)):
        print(i)

Ennek kimenete:

1
0 1 2 3 4

Kapcsolódó linkek


Utolsó frissítés: 2021-02-24 17:24:53