Języki skryptowe Python

Wykład 13

Polimorfizm


  • współdzielenie interfejsu przez różne typy
class Kot:
    def glos(self):
        print("Miau")

class Pies:
    def glos(self):
        print("Hau")

class Krowa:
    def glos(self):
        print("Muu") 
for zwierze in [Kot(), Pies(), Krowa()]:
    zwierze.glos()  # za każdym razem inny typ
Miau
Hau
Muu

Ryby głosu nie mają


class Ryba:
    pass  # brak definicji glos
for zwierze in [Kot(), Pies(), Krowa(), Ryba()]:
    zwierze.glos()  # Ryba nie ma zdefiniowanej metody glos
Miau
Hau
Muu
...
AttributeError: 'Ryba' object has no attribute 'glos'

Wymuszanie interfejsu


class Zwierze:
    def glos(self):
        pass
    
class Kot(Zwierze):
    def glos(self):  # nadpisuje Zwierze.glos
        print("Miau")
        
class Ryba(Zwierze):
    pass
for zwierze in [Kot(), Ryba()]:
    zwierze.glos()  # Ryba siedzi cicho
Miau

Większe wymuszanie interfejsu


class Zwierze:
    def glos(self):
        raise NotImplementedError("Każde zwiesze musi mieć głos.")
    
class Kot(Zwierze):
    def glos(self):  # nadpisuje Zwierze.glos
        print("Miau")
        
class Ryba(Zwierze):
    pass
for zwierze in [Kot(), Ryba()]:
    zwierze.glos() # Ryba zwróci błąd
Miau
...
NotImplementedError: Każde zwiesze musi mieć głos.

Przykład - Wielokąt


class Wielokat:
    
    def __init__(self, *boki):
        self.boki = boki       # krotka
        
    def obwod(self):           # suma długości
        return sum(self.boki)  # boków
        
    def pole(self): raise NotImplementedError
w = Wielokat(1, 2, 3, 4, 5)    # pięciokąt

w.obwod()
15

Przykład - Trójkąt


class Trojkat(Wielokat):
    
    def __init__(self, *boki):
        super().__init__(*boki)    # utwórz wielokąt
        
    def pole(self):
        pole = p = self.obwod()/2  # obwod zdefiniowane w Wielokat
        
        for bok in self.boki:      # wzór Herona
            pole *= (p - bok)      # p(p - a)(p - b)(p - c)
            
        return pole**0.5 # pierwiastek
t = Trojkat(3, 4, 5)

t.obwod()  # z Wielokat
12
t.pole()  # z Trójkat
6.0

Przykład - Trójkąt Równoboczny


class TrojkatR(Trojkat):
    
    def __init__(self, bok):
        super().__init__(bok, bok, bok)  # Trójkąt -> Wielokąt
        
    def pole(self):
        return (self.boki[0]**2)*(3**0.5)/4
t = TrojkatR(3)  # (3, 3, 3)

t.pole()  # TrojkatR.pole()
3.8971143170299736
t = Trojkat(3, 3, 3)

t.pole()  # Trojkat.pole() 
3.897114317029974

Wielokrotne dziedziczenie


  • klasa może posiadać kilka klas bazowych
  • posiada wtedy wszystkie dane i metody
  • a co jeśli się powtarzają?

Przykład - amifibia


class Samochod:
    
    def jedz(self):
        print("Jadę...")
        
class Lodz:
    
    def plyn(self):
        print("Płynę...")
        
class Amfibia(Samochod, Lodz): pass
amfibia = Amfibia()

amfibia.jedz()  # Samochod
amfibia.plyn()  # Lodz
Jadę...
Płynę...

Przykład - amifibia v2


class Samochod:
    def run(self):
        print("Jadę...")
        
class Lodz:
    def run(self):
        print("Płynę...")
        
class Amfibia1(Samochod, Lodz): pass
class Amfibia2(Lodz, Samochod): pass
amifibia1 = Amfibia1()
amifibia1.run()
Jadę...
amifibia2 = Amfibia2()
amifibia2.run()
Płynę...

Generatory


  • funkcja, która zachowuje się jak iterator (czyli można np. wykorzystać ją w pętli)
  • zamiast tworzyć całą listę obiektów, które będą przechowywane w pamięci
  • oszczędza czas i pamięć

Dygresja: range


%timeit -n1 range(1000000)  # tworzy obiekt range
1 loop, best of 3: 1.68 µs per loop
# kolejne elementy range
# muszą zostać zapisane w pamięci
%timeit -n1 list(range(1000000))
1 loop, best of 3: 53.3 ms per loop

Ciąg geometryczny


def geometryczny(a1, q, n):
    """Generuje n wyrazów ciągu geometrycznego."""
    
    ciag = [a1]
    
    for _ in range(n-1):         # n-1 bo pierwszy już jest
        ciag.append(ciag[-1]*q)  # następny = poprzedni * iloraz
        
    return ciag
ciag = geometryczny(1, 3, 10)

print(ciag)
[1, 3, 9, 27, 81, 243, 729, 2187, 6561, 19683]

Ciąg geometryczny - generator


def gen_geometryczny(a1, q, n):
    """Generuje n wyrazów ciągu geometrycznego."""
    
    for _ in range(n):
        yield a1 # zwróć obecną wartość a1
        a1 *= q  # i czekaj na kolejną iterację

Generator a lista


ciag1 = geometryczny(1, 3, 10)      # zwraca listę 
ciag2 = gen_geometryczny(1, 3, 10)  # zwraca generator
print(ciag1)
[1, 3, 9, 27, 81, 243, 729, 2187, 6561, 19683]
print(ciag2)
<generator object gen_geometryczny at 0x7f9bac1ec678>

Generator jak iterator


next(ciag2)  # pierwszy element
1
next(ciag2)  # kolejny element itd
3
# a najczęściej w pętli
for i in gen_geometryczny(1, 3, 10):
    print(i, end=', ')
1, 3, 9, 27, 81, 243, 729, 2187, 6561, 19683, 

Czas


%timeit -n3 geometryczny(1, 3, 10000)
3 loops, best of 3: 12.8 ms per loop
%timeit -n3 gen_geometryczny(1, 3, 10000)
3 loops, best of 3: 978 ns per loop

Czas dokładniej


def test_lista():
    """Pętla po liście"""
    for element in geometryczny(1, 3, 10000):
        pass
    
def test_generator():
    """Pętla po generatorze"""
    for element in gen_geometryczny(1, 3, 10000):
        pass
%timeit -n3 test_lista()
3 loops, best of 3: 13.6 ms per loop
%timeit -n3 test_generator()
3 loops, best of 3: 12.1 ms per loop

Lista czy generator


  • generator będzie z reguły szybszy
  • generator może być nieskończony
  • jednak nie można wykonywać operacji na "elementach", np. sortowania
  • lista może być efektywniejsza, jeśli planujemy więcej pętli

"Wyczerpanie" generatora


ciag = gen_geometryczny(1, 3, 20)  # generuje 20 wyrazów
for i in range(10):   # wydrukuj 10 pierwszych
    print(next(ciag), end=' ')
1 3 9 27 81 243 729 2187 6561 19683 
for element in ciag:  # nie zaczyna od pierwszego!
    print(element, end=' ')
59049 177147 531441 1594323 4782969 14348907 43046721 129140163 387420489 1162261467 

Wielekrotne użycie


def lista_loop(n=10):
    """Wykonuje n pętli po liście"""
    ciag = geometryczny(1, 3, 10000)
    
    for _ in range(n):
        for element in ciag:
            pass
    
def gen_loop(n=10):
    """Wykonuje n pętli po generatorze"""
    for _ in range(n):
        for element in gen_geometryczny(1, 3, 10000):
            pass
%timeit -n3 lista_loop()
3 loops, best of 3: 14.2 ms per loop
%timeit -n3 gen_loop()
3 loops, best of 3: 77.4 ms per loop

Powtórka: listy składane (list comprehensions)


# chcemy stworzyć listę zawierającą
# kwadraty wszystkich cyfr

kwadraty = []

for x in range(10):
    kwadraty.append(x**2)
    
print(kwadraty)
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
# a korzystając z listy skladanej

kwadraty = [x**2 for x in range(10)]

print(kwadraty)
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

Append vs lista składana


def create_list(n=100000):
    """Tworzy listę kwadratów pierwszych n liczb naturalnych"""
    
    kwadraty = []
    
    for x in range(n):
        kwadraty.append(x**2)
        
    return kwadraty

def list_comprehension(n=100000):
    """Tworzy listę kwadratów pierwszych n liczb naturalnych"""
    
    return [x**2 for x in range(n)]

Czas


%timeit -n3 x = create_list()  # lista tworzona przez append
3 loops, best of 3: 48.3 ms per loop
%timeit -n3 y = list_comprehension()  # lista składana
3 loops, best of 3: 42.5 ms per loop
create_list() == list_comprehension()  # wynik ten sam
True

Przykład


def force(m, a):
    """Zwraca wartość siły [N].
    
    Liczy siłę jaką należy zadziałać na ciało
    o masie m [kg], aby nadać mu przyspieszenie a [m/s^2].
    """
    return m*a

wagi = [10, 20, 30, 40, 50]       # kg
przyspieszenia = [1, 2, 3, 4, 5]  # m/s^2
# stwórz tablicę z wartościami sił 

sily = [force(m, a) for m, a in zip(wagi, przyspieszenia)]

print(sily)
[10, 40, 90, 160, 250]

Generator "jak lista składana"


# lista składana
list_comprehension = [x**2 for x in range(10)]

# generator expression
generator = (x**2 for x in range(10))
for x in generator:
    print(x, end=' ')
0 1 4 9 16 25 36 49 64 81 
# lub prościej

for x in (x**2 for x in range(10)):
    print(x, end=' ')
0 1 4 9 16 25 36 49 64 81 

Uwagi na koniec


  • unikaj tworzenia list przez append, jeśli można wykorzystać list comprehension
  • unikaj tworzenia list jeśli jest to zbędne (użyj generatorów)
  • unikaj generator function na rzecz generator expression
  • jest też tuple comprehension:
krotka = *(x**2 for x in range(10)),
print(krotka)
(0, 1, 4, 9, 16, 25, 36, 49, 64, 81)