Od zera do mistrza Big Data i uczenia maszynowego w Pythonie. Część 5. Funkcje

wpis w: migawka, Python | 0

Dotarliśmy do tego fragmentu kursu, gdzie znamy podstawowe typy zmiennych prostych i sekwencyjnych. Umiemy robić jawne konwersje, rozumiemy zmienne logiczne, potrafimy wykorzystywać warunki logiczne i sterować programem wykorzystując pętle.

Przyszedł czas by pochylić się nad jednym z ważniejszych elementów , czyli podprogramami zwanymi funkcjami.

Funkcja to fragment kodu , który może być wielokrotnie wykorzystywany w różnych miejscach naszego programu.

W języku Python składnia definicji funkcji jest następująca:

Skupimy się nad:

  • parametrami formalnymi
    zmiennymi aktualnymi
    zmiennymi lokalnymi
    zmiennymi globalnymi.

Ważne będzie zrozumienie działania przestrzeni nazw, słów kluczowych global, nonlocal.

Funkcje mogą mieć nieznaną liczbę argumentów. Mogą być budowane rekurencyjnie. Poznamy sposoby przekazywania argumentów  i sprawdzanie ich poprawności.Nauczymy się korzystać z wartości domyślnych i parametrów specjalnych.
Poznamy również wyrażenia lambda, które pozwalają nam na tworzenie mini-funkcji.

Dobrą praktyką jest umieszczanie na początku funkcji jej dokumentacji. Ten docstring powinien zawierać listę argumentów i znaczenie i opis działania.

Kod notatnika Jupiter został jak zwykle umieszczony na Githubie

Podgląd:

https://nbviewer.jupyter.org/github/djkormo/PythonForML/blob/master/intro/Kurs_4_funkcje.ipynb

Uruchomienie:

https://mybinder.org/v2/gh/djkormo/PythonForML/master?filepath=intro/Kurs_4_funkcje.ipynb

Po uruchomieniu kontenera należy wyczyścić informacje wyjściowe za pomocą menu Kernel -> Restart & Clear Output.

Reenredowane wtyczką WP:

Funcje

def nazwa( parametry ):

'''dokumentacja''' #opcjonalne

logika

return [wyrażenie] # opcjonalne

In [1]:
# nasza pierwsza funkcja.
# Wyswietlnie wartości parametru. 
# Funkcja nie zwraca jawnej wartości

def drukuj(napis):
    print(napis)
    
# wykonanie funkcji

drukuj('Litwo, Ojczyzno moja')    
Litwo, Ojczyzno moja
In [2]:
#Jeśli nie zwracamy wartości, to niejawnie zwracana jest wartość None.

print("Nie zwracam wartosci") is None
Nie zwracam wartosci
Out[2]:
True
In [3]:
# Wywolanie instrukcji help w przypadku, gdy nasza funkcja nie  zostałą opisana w docstring

help(drukuj)
Help on function drukuj in module __main__:

drukuj(napis)

In [4]:
# dodajemy dokumentację do funkcji

def drukuj(napis):
    '''Funkcja print drukuje podany w parametrze napis'''
    print(napis)
    
In [5]:
# Wywolanie instrukcji help w przypadku, gdy nasza funkcja   została opisana w docstring

help(drukuj)
Help on function drukuj in module __main__:

drukuj(napis)
    Funkcja print drukuje podany w parametrze napis

In [6]:
# pusta funkcja

def nicnierobie():
   pass # pusta instrukcja, to nie jest komentarz

# wywołanie 
nicnierobie()


# w wersji z dokumentacją

def nicnierobie():
   '''Funkcja leniwa. Nic nie robię i dobrze mi z tym.''' 
   pass

#wywołanie 
nicnierobie()
# pomoc 
help(nicnierobie)
# typu None
help(nicnierobie) is None
Help on function nicnierobie in module __main__:

nicnierobie()
    Funkcja leniwa. Nic nie robię i dobrze mi z tym.

Help on function nicnierobie in module __main__:

nicnierobie()
    Funkcja leniwa. Nic nie robię i dobrze mi z tym.

Out[6]:
True
In [7]:
# funkcja z parametrem zwrotnym

def sklejnapisy(napis1,napis2):
    '''Funkcja skleja dwa napisy podane w parametrach funkcji'''
    napis3=napis1+napis2 # zmienna lokalna
    return napis3 # slowo k

# pomoc systemowa 

help(sklejnapisy)

# wynik działania funkcji

print(sklejnapisy('Nad rzęczką ','opodal krzaczka.'))

# inny sposob wywołania tej funcji

print(sklejnapisy(napis1='Nad rzęczką ',napis2='opodal krzaczka.'))
Help on function sklejnapisy in module __main__:

sklejnapisy(napis1, napis2)
    Funkcja skleja dwa napisy podane w parametrach funkcji

Nad rzęczką opodal krzaczka.
Nad rzęczką opodal krzaczka.
In [8]:
# jeżeli potrzebujemy wyswietlenia wielu wartości output z jednej komorki
from IPython.core.interactiveshell import InteractiveShell
InteractiveShell.ast_node_interactivity = "all"
In [9]:
# Wywolanie argumentów danej funkcji  może być zarówno bez podowania nazwy argumentu, lub w postaci nazwa=wartosc

# Dla powyższej funcji mamy różne sposoby  wywołania

# tylko wartosci argumentow

sklejnapisy('Nad rzęczką ','opodal krzaczka.')

# pierwszy argument przez wartosc, drugi przez postać nazwa=wartosc

sklejnapisy('Nad rzęczką ',napis2='opodal krzaczka.')

# pierwszy argument przez nazwa=wartosc, drugi przez postać nazwa=wartosc

sklejnapisy(napis1='Nad rzęczką ',napis2='opodal krzaczka.')

# pierwszy argument przez nazwa=wartosc, drugi przez postać nazwa=wartosc, ale z przestawieniem kolejnosci

sklejnapisy(napis2='opodal krzaczka.',napis1='Nad rzęczką ')
Out[9]:
'Nad rzęczką opodal krzaczka.'
Out[9]:
'Nad rzęczką opodal krzaczka.'
Out[9]:
'Nad rzęczką opodal krzaczka.'
Out[9]:
'Nad rzęczką opodal krzaczka.'
In [11]:
# parametry funkcji mogą mieć zdefiniowaną wartość domyślną

def sklejnapisy3(napis1,napis2,napis3='...'):
    '''Funkcja skleja trzy napisy podane w parametrach funkcji'''
    napis4=napis1+napis2+napis3 # zmienna lokalna
    return napis4

# jeśli nie podamy wartości trzeciego parametru

print(sklejnapisy3('Nad rzeczką ','opodal krzaczka'))

print(sklejnapisy3(napis1='Nad rzeczką ',napis2='opodal krzaczka'))

# z podaniem tej wartości 

print(sklejnapisy3('Nad rzeczką ','opodal krzaczka','!!!'))

print(sklejnapisy3(napis1='Nad rzeczką ',napis2='opodal krzaczka',napis3='!!!'))
Nad rzeczką opodal krzaczka...
Nad rzeczką opodal krzaczka...
Nad rzeczką opodal krzaczka!!!
Nad rzeczką opodal krzaczka!!!
In [12]:
# jesli nie podamy patametru, ktory nie ma wartosci domyslnej ?
print(sklejnapisy3(napis1='Nad rzeczką ',napis3='!!!')) # TypeError: sklejnapisy() missing 1 required positional argument: 'napis2'

# Bedzie zwrócony błąd  TypeError: sklejnapisy3() missing 1 required positional argument: 'napis2'
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-12-72ebcfe66dac> in <module>()
      1 # jesli nie podamy patametru, ktory nie ma wartosci domyslnej ?
----> 2 print(sklejnapisy3(napis1='Nad rzeczką ',napis3='!!!')) # TypeError: sklejnapisy() missing 1 required positional argument: 'napis2'
      3 
      4 # Bedzie zwrócony błąd  TypeError: sklejnapisy3() missing 1 required positional argument: 'napis2'

TypeError: sklejnapisy3() missing 1 required positional argument: 'napis2'
In [13]:
# metadane funkcji

# https://docs.python.org/3/reference/datamodel.html

print ('Nazwa: ',sklejnapisy3.__name__)           # nazwa funkcji
print ('Opis: ',sklejnapisy3.__doc__ )           # dokumentacja (docstring)
print ('Wartosci domyslne:',sklejnapisy3.__defaults__ )  # wartości domyślne 
Nazwa:  sklejnapisy3
Opis:  Funkcja skleja trzy napisy podane w parametrach funkcji
Wartosci domyslne: ('...',)
In [14]:
# zen of Python
import this
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!

Klika słów o zasięgu zmienych

In [15]:
#http://www.diveintopython.net/html_processing/locals_and_globals.html

# Zmienne utworzone wewnątrz funkcji mają lokalny zasięg.
# Funkcja locals() zwraca słownik par klucz-wartość

def loc(arg):
    x = 1
    print ('Locals() : ',locals())
    
# x lokalny ma wartość 7 tzpu calkowitego 

# x lokalny ma wartość 'Lokalne jest piekne' typu napis
loc('Lokalne jest piekne') 
Locals() :  {'arg': 'Lokalne jest piekne', 'x': 1}
In [16]:
 #http://www.diveintopython.net/html_processing/locals_and_globals.html
    
def lokalny(arg):
     x = 1
     # wyswietlamy slownik zmiennych lokalnych
     # zauwazmy, ze pojawia sié klucz o nazwie orgumentu funkcji 
     print (locals() )  
    
     # zmieniamy wartosc slownika o nazwie x   
     locals()["x"] = 2 
     # wyswietlamy slownik zmiennych lokalnych 
     print (locals() ) 
     # wyswietlamy zawartosc zmiennej x
     print ("Zmienna x= ",x)

# nasza zmienna         
z = 7
print ("Zmienna z= ",z)

lokalny(3)
# zmieniany wartosc slownika o nazwie z
globals()["z"] = 8    
print ("Zmienna z= ",z)

# To co musimy zapamietac, to informacja o tym, ze locals jest tylko do odczytu 
# Inaczej jest z odpowiednikiem globals, o czym bedzie pozniej 
Zmienna z=  7
{'arg': 3, 'x': 1}
{'arg': 3, 'x': 1}
Zmienna x=  1
Zmienna z=  8

Fragment z dokumentacji w Wikipedii

Kiedy pewna linia kodu pyta się o wartość zmiennej x, Python przeszuka wszystkie przestrzenie nazw, aby ją znaleźć, w poniższym porządku:

  1. lokalna przestrzeń nazw -- określona dla bieżącej funkcji lub metody pewnej klasy. Jeśli funkcja definiuje jakąś lokalną zmienną x, Python wykorzysta ją i zakończy szukanie.
  2. przestrzeni nazw, w której dana funkcja została zagnieżdżona i przestrzeniach nazw, które znajdują się wyżej w "zagnieżdżonej" hierarchii.
  3. globalna przestrzeń nazw -- określona dla bieżącego modułu. Jeśli moduł definiuje zmienną lub klasę o nazwie x, Python wykorzysta ją i zakończy szukanie.
  4. wbudowana przestrzeń nazw -- globalna dla wszystkich modułów. Ponieważ jest to ostatnia deska ratunku, Python przyjmie, że x jest nazwą wbudowanej funkcji lub zmiennej.
In [17]:
def funkcja_lokalna():
    x = 1
    
# Zmienna x jest dostépna tylko wewnátrz funkcji    
print (x) #NameError: name 'x' is not defined
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-17-6ae2f3f26784> in <module>()
      3 
      4 # Zmienna x jest dostépna tylko wewnátrz funkcji
----> 5 print (x) #NameError: name 'x' is not defined

NameError: name 'x' is not defined
In [20]:
# jesli chcemy skorzystac ze zmiennej utworzonej poza cialem funkcji
#  Co jesli taka zmienna nie zostala utworzona wczesniej ?


def funkcja_globalna_nieutworzona():
    global x
    x = 10
  
#print ('Zmienna x= ',x)  #NameError: global name 'x' is not defined
funkcja_globalna_nieutworzona()  
print ('Zmienna x= ',x)
Zmienna x=  10
In [21]:
# Wykorzystujemy istniejaca zmienna w funkcji
def funkcja_globalna_utworzona():
    global x
    x =x+1
# tworzymy zmienna x     
x=10    
print ('Zmienna x= ',x) 
funkcja_globalna_utworzona()
print ('Zmienna x= ',x) 
Zmienna x=  10
Zmienna x=  11
In [22]:
# jak wygláda struktura locals ?
def funkcja_globalna_utworzona():
    global x
    x =x+1
    print (locals()) # struktura jest pusta , brakuje zmienny lokalnych
    
x=10    
print ('Zmienna x= ',x) 
funkcja_globalna_utworzona()
print ('Zmienna x= ',x)    
Zmienna x=  10
{}
Zmienna x=  11
In [23]:
# Do jej pory rozmawialismy o zmiennych lokalnyh i globalnych.

# Jak sobie z tym poradzic, jesli korzystamy z funkcji w funkcji ?

def funkcja_zewnetrzna():
    def funkcja_wewnetrzna():
        nonlocal x
        x = 5
    funkcja_wewnetrzna()

funkcja_zewnetrzna()

# wykonanie powyzszego kodu koñczy sie bledem  no binding for nonlocal 'x' found

# funkcja_wewnetrzna ma zdefiniowana zmienna x jako zmienna nielokalna
# to oznacza, ze oczekuje istnienia takiej funkcji w przestrzeni nazw , ktorej jest zanurzona
# czyli w funkcji funkcja_zewnetrzna()
  File "<ipython-input-23-66605e0f4d74>", line 7
    nonlocal x
    ^
SyntaxError: no binding for nonlocal 'x' found
In [24]:
# W funkcji wewnétrznej difiniujemy zmienna nielokalna x.
# W funkji wewnétrznej tworzymy zmienna lokalna x
# Na zewnátrz obu funkcji jest zdefiniowana zmienna x.

def funkcja_zewnetrzna():
    def funkcja_wewnetrzna():
        nonlocal x
        x = 5
        print('funkcja_wewnetrzna()-> Zmienna x= ',x)
    funkcja_wewnetrzna()
    x =10
    print('funkcja_zewnetrzna()-> Zmienna x= ',x)
    
# zmienna globalna x
x=-1
print('Zmienna x= ',x)
funkcja_zewnetrzna()
print('Zmienna x= ',x)
Zmienna x=  -1
funkcja_wewnetrzna()-> Zmienna x=  5
funkcja_zewnetrzna()-> Zmienna x=  10
Zmienna x=  -1
In [25]:
# W ponizszym przypadku zmienna globalna x nie jest zmieniana, gdyæ 
# w funkcja_zewnetrzna() , gdyz korzysta ze zmiennej lokalnej x
# w funkcja_wewnetrzna(), gdyz korzysta ze zmiennej nielokalnej x , czyli z lokalnej x funkcja_zewnetrzna()

# Ten przyklad proponuje sobie spokojnie pouklada© w glowie

def funkcja_zewnetrzna():
    def funkcja_wewnetrzna():
        nonlocal x
        x = 1
        print("funkcja_wewnetrzna()-> Zmienna x=", x)  
    x = 3
    print("funkcja_zewnetrzna()-> Zmienna x=", x)
    funkcja_wewnetrzna()
    
    
print("Globalnie() -> Zmienna x= ", x)    
x = 2    
funkcja_zewnetrzna()
print("Globalnie() -> Zmienna x= ", x)
Globalnie() -> Zmienna x=  -1
funkcja_zewnetrzna()-> Zmienna x= 3
funkcja_wewnetrzna()-> Zmienna x= 1
Globalnie() -> Zmienna x=  2
In [26]:
# Ten przyklad zostawiam dla chetnych.

# Kiedy zmienna x przyjmuje wartosc 50 ?

def funkcja_zewnetrzna():
    def funkcja_wewnetrzna():
        def funkcja_wewnetrzna2():
            nonlocal x
            x = 100
            print("funkcja_wewnetrzna2()-> Zmienna x=", x)
        x = 50
        funkcja_wewnetrzna2()
        print("funkcja_wewnetrzna()-> Zmienna x=", x)
    x = 10
    funkcja_wewnetrzna()
    print("funkcja_zewnetrzna()-> Zmienna x=", x)

x = 1
print("Globalnie()-> Zmienna x=", x)
funkcja_zewnetrzna()
print("Globalnie()-> Zmienna x=", x)
Globalnie()-> Zmienna x= 1
funkcja_wewnetrzna2()-> Zmienna x= 100
funkcja_wewnetrzna()-> Zmienna x= 100
funkcja_zewnetrzna()-> Zmienna x= 10
Globalnie()-> Zmienna x= 1
In [27]:
#### Rekurencja i iteracja na przykladzie ciagu Fibonacciego

def fibo_rekurencja(n):
    '''Funkcja fibo_rekurencja(n) wyznacza wartosc n elementu ciagu Fibonaciego w wersji rekurencyjnej
    '''
    if n<=1:
      return 1
    else:
      return fibo_rekurencja(n-1) + fibo_rekurencja(n-2)

def fibo_iteracja(n):
    '''Funkcja fibo_iteracja(n) wyznacza wartosc n elementu ciagu Fibonaciego w wersji rekurencyjnej
    '''
    x,y=1,0
    for i in range(n):
      x,y=x+y,x
    return x    


help(fibo_iteracja)
print ('Iteracja....')
for n in range(10):
  print('Fibo_iteracja dla n= ',n,' = ',fibo_iteracja(n)) 

help(fibo_rekurencja)
print ('Rekurencja....')
for n in range(10):
  print('Fibo_rekurencja dla n= ',n,' = ',fibo_rekurencja(n)) 
    
Help on function fibo_iteracja in module __main__:

fibo_iteracja(n)
    Funkcja fibo_iteracja(n) wyznacza wartosc n elementu ciagu Fibonaciego w wersji rekurencyjnej

Iteracja....
Fibo_iteracja dla n=  0  =  1
Fibo_iteracja dla n=  1  =  1
Fibo_iteracja dla n=  2  =  2
Fibo_iteracja dla n=  3  =  3
Fibo_iteracja dla n=  4  =  5
Fibo_iteracja dla n=  5  =  8
Fibo_iteracja dla n=  6  =  13
Fibo_iteracja dla n=  7  =  21
Fibo_iteracja dla n=  8  =  34
Fibo_iteracja dla n=  9  =  55
Help on function fibo_rekurencja in module __main__:

fibo_rekurencja(n)
    Funkcja fibo_rekurencja(n) wyznacza wartosc n elementu ciagu Fibonaciego w wersji rekurencyjnej

Rekurencja....
Fibo_rekurencja dla n=  0  =  1
Fibo_rekurencja dla n=  1  =  1
Fibo_rekurencja dla n=  2  =  2
Fibo_rekurencja dla n=  3  =  3
Fibo_rekurencja dla n=  4  =  5
Fibo_rekurencja dla n=  5  =  8
Fibo_rekurencja dla n=  6  =  13
Fibo_rekurencja dla n=  7  =  21
Fibo_rekurencja dla n=  8  =  34
Fibo_rekurencja dla n=  9  =  55

Operator lambda

In [28]:
# Wyrazenia Lanbda, czyli funkjce inline lub finkcje atomowe
# skladania 
# lambda argumenty: instrukcja_gererujaca_wynik

# funkcja dodawania
dodawanie = lambda x,y : x+y
print('1+2 =',dodawanie(1,2))
1+2 = 3
In [29]:
# Warto poczytac https://www.afternerd.com/blog/python-lambdas/

# kwadrat liczby

kwadrat = lambda x: x * x
print('3*3 = ',kwadrat(3))

# mnozenie dwoch liczb

mnozenie = lambda x,y: x * y
print('3*4 = ',mnozenie(3,4))

# zawsze prawda, w wyrazenie bez argumentow na wejsciu

prawda= lambda :True
print ('Zawsze prawda = ',prawda())
3*3 =  9
3*4 =  12
Zawsze prawda =  True
In [30]:
# operacja list i map

lista_wejsciowa = [1, 2, 3, 4]
lista_wyjsciowa=list(map(lambda x: x**2, lista_wejsciowa))

print ('lista_wyjsciowa=',lista_wyjsciowa)
lista_wyjsciowa= [1, 4, 9, 16]
In [31]:
# operator lambda w wersji rekurencyjnej

silnia = lambda x: x * silnia(x-1) if x != 0 else 1
print('Silnia z 6 =',silnia(6))
Silnia z 6 = 720

Obsluga wyjatkow

In [32]:
x = [1,2]
# probujemy siagnac po nieistniejacy element
print(x[2]) #IndexError: list index out of range
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-32-10465f1c6562> in <module>()
      1 x = [1,2]
      2 # probujemy siagnac po nieistniejacy element
----> 3 print(x[2]) #IndexError: list index out of range

IndexError: list index out of range
In [33]:
try:
  x = [1,2]     
  print(x[2])
except Exception as e:
    print ('Nie widzimy bledu')
Nie widzimy bledu
In [35]:
try:
  x = [1,2]     
  print(x[2])
except Exception as e:
    print ('Cos sie wydarzylo: ',e) #wyswietlamy blad
Cos sie wydarzylo:  list index out of range
In [36]:
# Mozemy wykorzystac predefiniowane rodzaje wyjatkow
try:
  x = [1,2]     
  print(x[2])
except ZeroDivisionError as e0:
    print ('Cos sie wydarzylo: ',e0) #blad nie zostanie wyswietlony
except Exception as e:
    print ('Cos sie wydarzylo: ',e) #wyswietlamy blad        
Cos sie wydarzylo:  list index out of range
In [37]:
try:
  1/0
except ZeroDivisionError as e0:
  print ('Cos sie wydarzylo: ',e0) #blad  zostanie wyswietlony
except Exception as e:
  print ('Cos sie wydarzylo: ',e) # blad nie zostanie wyswietlamy
Cos sie wydarzylo:  division by zero

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *