Matematikai Algoritmusok és Felfedezések I.

4. Előadás: Comprehensions, Objektum orientált programozás

2022 március 3.

List comprehension

  • Flat is better than nested.
  • Rövidités a foor loop leggyakoribb használatára, hogy gyorsan tudjunk listákat létrehozni
In [1]:
lista = []
for i in range(10):
    lista.append(2*i+1)
lista
Out[1]:
[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]

Egy soros megfelelő:

In [2]:
lista = [2*i+1 for i in range(10)]
lista
Out[2]:
[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]

Az általános formula

[<expression> for <element> in <sequence>]
In [3]:
even = [n*n for n in range(20) if n % 2 == 0]
even
Out[3]:
[0, 4, 16, 36, 64, 100, 144, 196, 256, 324]

Ami azzal ekvivalens, hogy

In [4]:
even = []
for n in range(20):
    if n % 2 == 0:
        even.append(n)
even
Out[4]:
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

egy feltételt is megadhatunk, hogy szűrjük az elemeket:

[<expression> for <element> in <sequence> if <condition>]
  • mivel itt szűrésre használjuk az if részt, nincs else ág

  • viszont a kezdeti kifejezésben használhatunk esetszétválasztást:

In [5]:
l = [1, 0, -2, 3, -1, -5, 0]

signum_l = [int(n / abs(n)) if n != 0 else 0 for n in l]
signum_l
Out[5]:
[1, 0, -1, 1, -1, -1, 0]

Ez persze nem a list comprehension extrája, hanem csak annyi, hogy ez is egy értelmes kifejezés:

In [6]:
n = -3.2 
int(n / abs(n)) if n != 0 else 0 
Out[6]:
-1

Több listán is végigfuthatunk:

In [7]:
lista1 = [1, 2, 3]
lista2 = [4, 5, 6]

[(i, j) for i in lista1 for j in lista2]
Out[7]:
[(1, 4), (1, 5), (1, 6), (2, 4), (2, 5), (2, 6), (3, 4), (3, 5), (3, 6)]

Egymásba is ágyazhatjuk őket:

In [8]:
matrix = [
    [1, 2, 3], 
    [5, 6, 7]
]

[[e*e for e in row] for row in matrix]
Out[8]:
[[1, 4, 9], [25, 36, 49]]

Set és dictionary comprehension

Minden analóg módon működik:

In [9]:
fruit_list = ["apple", "plum", "apple", "pear"]

fruits = {fruit.title() for fruit in fruit_list}

type(fruits), len(fruits), fruits
Out[9]:
(set, 3, {'Apple', 'Pear', 'Plum'})
In [10]:
word_list = ["apple", "plum", "pear"]
word_length = {word: len(word) for word in word_list}
type(word_length), len(word_length), word_length
Out[10]:
(dict, 3, {'apple': 5, 'plum': 4, 'pear': 4})

Vajon mi történik?

In [11]:
word_list = ["apple", "plum", "pear", "avocado"]
first_letters = {word[0]: word for word in word_list}
first_letters
Out[11]:
{'a': 'avocado', 'p': 'pear'}

Függvények fura viselkedése

In [12]:
def furcsafuggveny(l):
    k = []
    l = k
l = [1,2,3]
furcsafuggveny(l) 
print(l)
[1, 2, 3]
In [13]:
def furcsafuggveny2(l):
    l.append(4)
    l += [5]
    l = l + [6]
    l.append(7)
l = [1,2,3]
furcsafuggveny2(l)
print(l)
[1, 2, 3, 4, 5]
In [14]:
a=5
def foo():
    print(a)
foo()
5
In [15]:
a=5
def foo():
    a=a+1
    print(a)
foo()
---------------------------------------------------------------------------
UnboundLocalError                         Traceback (most recent call last)
<ipython-input-15-1760db591ad6> in <module>
      3     a=a+1
      4     print(a)
----> 5 foo()

<ipython-input-15-1760db591ad6> in foo()
      1 a=5
      2 def foo():
----> 3     a=a+1
      4     print(a)
      5 foo()

UnboundLocalError: local variable 'a' referenced before assignment

Namespace (névtér)

A namespace a változó nevek és az objektumok közti leképezés (pont mint egy dictionary). Pl:

  • Beépített nevekhez (abs(), sorted(), int stb...) tartozik egy namespace.
  • Globális namespace: ide kerülnek a függvényeken kívül létrehozott változók.
  • Lokális namespece: minden függvény létrehoz egy saját namespacet, először abban keres.

    Különböző namespacekben szerepelhet egyező név!

In [ ]:
# Ez egy szándékosan zavaróan megírt kód. 
a=5                       # a globális namespaceben 'a' az 5-re fog mutatni
def foo(a):               # ez már egy másik `a`, ami a foo() függvény namespaceben él
    print(a+1)            # itt a foo()-hoz tartozó 'a'-ra hivatkozunk.   
    def belsofugveny(a):  # ez egy harmadik 'a' változó, ez már a belsofugveny()-hez tartozik
        print(a+5);       # itt a belsofugveny()-hez tartozó 'a'-ra hivatkozunk.  
    belsofugveny(a)       # itt a foo()-hoz tartozó 'a'-ra hivatkozunk.    
   
foo(10)
a                         # itt a globális 'a'-ra hivatkozunk.   

Scope

Minden namespacehez tartozik egy scope. A scope a kódnak az a része, ahol a neveket automatikusan abban az adott namespaceben keresi a program.

In [16]:
a=5                       #
                          #
def foo(a):                   # 
    print(a+1)                #    
                              #   
    def belsofugveny(a):          # 
        print(a+5);               #
                              #
    belsofugveny(a)           # 
                          #
foo(10)                   #  
a                         #    
11
15
Out[16]:
5

Hogyan érjük el egy másik namespaceben lévő objektumokat?

  • a python kifelé keres, először a legbelső namespaceben
  • nonlocal valnev megmondja, hogy eggyel kintebbi scopeban keressen. (Pontosabban, a "legutóbbi" nem lokális használatot keresi)
  • global valnev megmondja, hogy a globális scopeban keressen.
In [17]:
def scope_test():
    def do_local():
        spam = "local spam"

    def do_nonlocal():
        nonlocal spam
        spam = "nonlocal spam" 

    def do_global():
        global spam
        spam = "global spam"

    spam = "test spam"
    do_local()
    print("lokális értékadás után:", spam)
    do_nonlocal()
    print("nonlocal kulcsszó után:", spam)
    do_global()
    print("global kulcsszó után:", spam)

scope_test()
print("globális scopeban:", spam)
lokális értékadás után: test spam
nonlocal kulcsszó után: nonlocal spam
global kulcsszó után: nonlocal spam
globális scopeban: global spam

A másik lehetőség, hogy megadjuk, hogy melyik namespaceben kell keresni.

In [18]:
import math
math.pi           # a math modul namespaceben keresi a pi nevű változót
Out[18]:
3.141592653589793

Programozási paradigmák

Sokféle van, például:

  • Procedurális
  • Funckionális
  • Objektumorientált

Példa: Főzés vs matek vs autók

Mikor melyiket válasszuk?

Python

  • Többféle stílusban is lehet használni, a fentiek mindegyikét tudja többé kevésbé.
  • Sokszor vegyesen is használjuk.
  • Erősen támogatja az objektumorientált paradigmát. Minden objektum!
In [19]:
# minden objektum
import random
def foo():
    pass
[int,bool,foo,random] 
Out[19]:
[int,
 bool,
 <function __main__.foo()>,
 <module 'random' from 'C:\\ProgramData\\Anaconda3\\lib\\random.py'>]

Objektumorientált programozás

  • Osztályok: A programot objektumok köré szervezzük. Minden objektum tartalmaz adatokat és metódusokat, amik az adatokat kezelik.
  • Öröklődés (Inheritance): Specifikusabb objektumokat hozhatunk létre, melyek öröklik a korábbiak tulajdonságait
  • Egységbezárás (Encapsulation): Az összetartozó adatokat és metódusokat együtt kezeljük.
  • Absztrakció (Abstraction): A belső működést elrejtjük és csak a szükséges részt mutatjuk meg a felhasználónak.
  • Polimorfizmus (Polymorphism): A leszármazottak és példányok felülírhatják a metódusokat. Több osztályból is örököltethetünk egyszerre.

Objektum orientáltság Pythonban

https://docs.python.org/3/tutorial/classes.html

Osztály (class) definiálása

  • class kulcsszóval adjuk meg.
  • Példányokat tudunk létrehozni
  • Minden példánynak van egy saját namespace-e!
  • Attribútumokat tudunk kapcsolni hozzájuk melyek nem befolyásolják a többi példány attribútumait.
In [20]:
class Ember:
    pass
In [21]:
a=Ember()
print(type(a))
a.nev="Gipsz Jakab"
a.kor=24
b=Ember()
b.nev="Gipsz Jakabné"
b.kor=22
c=Ember()
c.nev="Mezga Aladár"
c.kor=23
l=[a,b,c]
for e in l:
    print(e.nev,e.kor)
<class '__main__.Ember'>
Gipsz Jakab 24
Gipsz Jakabné 22
Mezga Aladár 23
In [22]:
type(a)
Out[22]:
__main__.Ember

Init

  • __init__ függvény automatikusan meghívódik amikor a példány elkészül.
    • olyasmi mint egy konstruktor
    • nem kötelező
  • Minden metódus első paramétere maga a példány. A konvenció, hogy ezt self-nek hívjuk.
In [23]:
class Ember:
    def __init__(self,nevasd,kor):
        print("Létrejött egy ember")
        self.kor = kor
        self.nev = nevasd
 
In [24]:
a=Ember("Gipsz Jakab",24)
b=Ember("Gipsz Jakabné",22)

l=[a,b]
for e in l:
    print(e.nev,e.kor)
Létrejött egy ember
Létrejött egy ember
Gipsz Jakab 24
Gipsz Jakabné 22

Metódusok

  • Függvények az osztály definíciójában
  • Automatikusan az első argumentumuk a példány lesz.
In [25]:
class Ember:
    def __init__(self,nev,kor):
        self.kor = kor
        self.nev = nev
        
    def szuletesi_ev(self):    # egy paramétert vár
        print(2022-self.kor)
        
    def egykoru(self,other):    
        print(self.kor==other.kor)
 
    
a=Ember("Gipsz Jakab",24)
a.szuletesi_ev()                # de paraméter nélkül hívjuk meg, mivel az első paraméter maga 'a' lesz  
b=Ember("Gipsz Jakabné",22)
a.egykoru(b)
1998
False

Metódusok meghívása

Két lehetőség van:

  1. példány.metódus(param)
  2. class.metódus(példány, param)
In [26]:
a=Ember("Gipsz Jakab",24)
a.szuletesi_ev()                
Ember.szuletesi_ev(a)
1998
1998

__str__ metódus

Egy speciális metódus, amit arra használunk hogy megadjuk, hogy a print() függvény hogyan írja ki az objektumot.

In [27]:
print(a)
print(1+4j)
<__main__.Ember object at 0x000001B81C7A9D48>
(1+4j)
In [28]:
class Ember:
    def __init__(self,nev,kor,lakohely):
        self.kor = kor
        self.nev = nev
        self.lakohely = lakohely
    def __str__(self): 
        return self.nev+" egy "+str(self.kor)+" éves "+ self.lakohely + "i lakos."
    
a=Ember("Gipsz Jakab",24,"budapest")
print(a)
b=Ember("Gipsz Jakabné",22,"kecskemét")
print(b)
Gipsz Jakab egy 24 éves budapesti lakos.
Gipsz Jakabné egy 22 éves kecskeméti lakos.

Osztály attribútumok

  • Olyan attribútum, amin az ossztály összes tagja osztozik.
In [29]:
class Ember:
    letszam = 42

A példányokon és a class objektumon keresztül is elérjük

In [30]:
a = Ember()
a.letszam
Out[30]:
42
In [31]:
Ember.letszam
Out[31]:
42

Megváltoztatni a classon keresztül lehet

In [32]:
a1 = Ember()
a2 = Ember()

print(a1.letszam,a2.letszam)
a1.letszam = 43
a1.letszam, a2.letszam
42 42
Out[32]:
(43, 42)

A példányokon keresztül viszont nem

In [33]:
a1 = Ember()
a2 = Ember()

a1.letszam = 11
a1.letszam , a2.letszam
Out[33]:
(11, 42)

Azért, mert ez egy új attribútumot hoz létre a példány namespacében.

In [34]:
a1.letszam
Out[34]:
11

Öröklődés

In [35]:
class Ember:
    pass
 
class Matematikus(Ember):
    pass

e = Ember()  
m = Matematikus()
print(isinstance(e, Matematikus))
print(isinstance(m, Ember))
print(issubclass(Ember, Matematikus))
print(issubclass(Matematikus,Ember))
False
True
False
True

Ha nem írunk semmilyen osztályt, automatikusan az object osztály a szülő osztály:

In [36]:
o=object()
In [37]:
class A: pass
class B(object): pass

print(issubclass(A, object))
print(issubclass(B, object))
True
True

Metódus öröklődés

A metódusok öröklődnek, de felülírhatóak.

In [38]:
class A(object):
    def foo(self):
        print("A.foo függvény")
        
    def bar(self):
        print("A.bar függvény")
        
class B(A):
    def foo(self):
        print("B.foo függvény")
        
b = B()
b.foo()
b.bar()
B.foo függvény
A.bar függvény

Attribútum öröklődés

Mivel az adat attribútumok bárhol létrehozhatóak, csak akkor "öröklődnek", ha a szülő osztályban lévő kód meghívódik.

In [39]:
class A(object):
    
    def foo(self):
        self.value = 42
        
class B(A):
    pass

b = B()
print('b',b.__dict__)            # a __dict__ kiírja az összes attribútumot 
a = A()
print('a',a.__dict__)

a.foo()
print('a',a.__dict__)
print('b',b.__dict__)

b.foo()
print('b',b.__dict__)
b {}
a {}
a {'value': 42}
b {}
b {'value': 42}
In [40]:
dir(a)
Out[40]:
['__class__',
 '__delattr__',
 '__dict__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__gt__',
 '__hash__',
 '__init__',
 '__init_subclass__',
 '__le__',
 '__lt__',
 '__module__',
 '__ne__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 '__weakref__',
 'foo',
 'value']
In [41]:
a.__dict__
Out[41]:
{'value': 42}

A szülő osztály konstruktora

  • meghívódik az szülő osztály __init__ függvénye. Viszont mivel az __init__ nem egy szokásos konstruktor, így nem hívódik meg automatikusan a szülő osztály init függvénye, ha felülírja a gyerek osztály.
In [42]:
class A(object):
    def __init__(self):
        print("A.__init__ called")
        
class B(A):
    #pass
    def __init__(self):
        print("B.__init__ called")
        
class C(A):
    pass
        
b = B()
print("c létrehozása")
c = C()
B.__init__ called
c létrehozása
A.__init__ called

A szülő osztály metódusai kétféleképpen is elérhetőek

  1. a már tanult módon, az osztály nevével
  2. vagy pedig a super függvény segítségével
In [43]:
class A(object):
    def __init__(self):
        print("A.__init__ ")
        
        
class B(A):
    def __init__(self):
        A.__init__(self)
        print("B.__init__ ")
        
class C(A):
    def __init__(self):
        super().__init__()
        print("C.__init__ ")
        
print("B")
b = B()
print("C")
c = C()
B
A.__init__ 
B.__init__ 
C
A.__init__ 
C.__init__ 

1. Példa: polinomok

In [44]:
class Polinom:
    
    def __init__(self, lista):
        self.ehlista=lista 
       
    def __str__(self):
        szoveg=""
        for i,eh in enumerate(self.ehlista):
            szoveg=szoveg+str(eh)+"x^"+str(i)+"+"
        szoveg=szoveg.rstrip("+")    
        return szoveg
    
    def deri(self):
        l=[]
        for i,eh in enumerate(self.ehlista):
            if i==0:
                pass
            else:
                l.append(i*eh)
        return Polinom(l)
In [45]:
a=Polinom([1,2,4,5])
print(a)
print(a.deri().deri())
1x^0+2x^1+4x^2+5x^3
8x^0+30x^1
In [46]:
class Masodfoku(Polinom):
    def egyikgyok(self):
        a=self.ehlista[2]
        b=self.ehlista[1]
        c=self.ehlista[0]
        return (-b+(b**2-4*a*c)**(1/2))/(2*a) 
In [47]:
p=Masodfoku([2,3,1])
print(p)
p.egyikgyok()
print(p.deri())
2x^0+3x^1+1x^2
3x^0+2x^1
In [ ]:
 
In [48]:
# for ciklusok nélkül is meg tudjuk oldani :)
class Polinom:
    
    def __init__(self, lista):
        self.ehlista=lista
       
    def __str__(self):
        szoveg="".join([str(eh)+"x^"+str(i)+"+" for i,eh in enumerate(self.ehlista)][::-1])
        szoveg=szoveg.rstrip("+")    
        return szoveg
    
    def deri(self): 
        l=[i*eh for i,eh in enumerate(self.ehlista) if i!=0]
        return Polinom(l)
    
    def beh(self,x):
        valasz=sum([eh*x**i for i,eh in enumerate(self.ehlista)])
        return valasz
     
    def __add__(self, other):
        
        l=max(len(self.ehlista),len(other.ehlista))
        bovitett_eh1=self.ehlista + [0]*(l - len(self.ehlista))
        bovitett_eh2=other.ehlista + [0]*(l - len(other.ehlista))
            
        uj_ehlista=[bovitett_eh1[i]+bovitett_eh2[i] for i in range(l) ]
        
        return Polinom( uj_ehlista )
        
class Masodfoku(Polinom):
    def egyikgyok(self):
        a=self.ehlista[2]
        b=self.ehlista[1]
        c=self.ehlista[0]
        return (-b+(b**2-4*a*c)**(1/2))/(2*a) 
    
p=Polinom([1,2])
print(p,p.beh(5))
print(p.deri())

m=Masodfoku([1,2,7])
print(m.beh(m.egyikgyok()))
print(p+p)
print(Polinom([1,2,3])+Polinom([2,1]))
2x^1+1x^0 11
2x^0
(2.220446049250313e-16+1.1102230246251565e-16j)
4x^1+2x^0
3x^2+3x^1+3x^0

2. Példa: Sárkányok

sarkany

In [49]:
class sarkany:
    def __init__(self, nev):
        self.nev=nev
In [50]:
class repulo_sarkany(sarkany):
    def __init__(self, nev, szarnyfesztav):
        super().__init__(nev)
        self.szarnyfesztav=szarnyfesztav
    
    def tamadas(self):
        print("A", self.nev, "nevű sárkány a levegőből rád vetette magát", )
        
    def repules(self):
        print("A", self.nev, "nevű sárkány repül", )
        
In [51]:
smaug=repulo_sarkany("Smaug",12)
smaug.repules()
smaug.tamadas()
smaug.szarnyfesztav
A Smaug nevű sárkány repül
A Smaug nevű sárkány a levegőből rád vetette magát
Out[51]:
12
In [52]:
class tuzokado_sarkany(sarkany):
    def __init__(self, nev):
        super().__init__(nev)
    
    def tamadas(self):
        print("A", self.nev, "nevű sárkány szénné égetett", )
        
    def tuzokadas(self):
        print("A", self.nev, "nevű sárkány a tüzet okád", )
    
In [53]:
susu=tuzokado_sarkany("Süsü")
susu.tuzokadas()
susu.tamadas()
A Süsü nevű sárkány a tüzet okád
A Süsü nevű sárkány szénné égetett

Többszörös öröklődés

  • mindegyik osztály metódusai öröklődnek
In [54]:
class repulo_tuzokado_sarkany(repulo_sarkany,tuzokado_sarkany):
     def __init__(self, nev, szarnyfesztav):
        self.nev=nev  
        self.szarnyfesztav=szarnyfesztav
    
     def tamadas(self):
        tuzokado_sarkany.tamadas(self)
     

Vajon mi lesz az eredmény?

In [55]:
viserion=repulo_tuzokado_sarkany("Viserion",25)
viserion.repules()
viserion.tuzokadas()
viserion.tamadas()
A Viserion nevű sárkány repül
A Viserion nevű sárkány a tüzet okád
A Viserion nevű sárkány szénné égetett

Azt, hogy melyik hívódik meg, az MRO (Method Resolution Order) határozza meg. Ez egy sorrend az osztályokon, és egy metódus hívásnál addig megy a sorrendben még meg nem találja valamelyik osztályban a metódust. Nem pontosan így működik, de ökölszabálynak jó, hogy felfelé mélységi kersést végez, balról jobbra sorrendben.