In [1]:
# setup
from IPython.core.interactiveshell import InteractiveShell
InteractiveShell.ast_node_interactivity = "all"

Własne funkcje

Funkcja to wydzielony, zorganizowany fragment kodu przeznaczony do wielokrotnego użycia, któremu nadano nazwę. Funkcje wprowadzają do aplikacji większą modułowość (kod podzielony jest na fragmenty przeznaczone do wykonania jakiegoś zadania) oraz zwiększają stopień ponownego użycia kodu. Funkcje mogą składać się z pojedyńczego wyrażenia lub też z sekwencji wyrażeń, które mają być po sobie wykonane.

W ramach języka Python można wyróżnić kilka grup funkcji:

  • funkcje wbudowane, np. type(), max(), te funkcje są dostępne w momencie uruchomienia programu, jednak ich lista jest ograniczona. Pełna lista tych funkcji dostępna jest w dokumentacji języka [https://docs.python.org/3/library/functions.html].
  • funkcje w modułach biblioteki standardowej, dostarczane wraz z wersją języka i wraz z nią aktualizowane.
  • zewnętrznych tworzonych przez niezależnych programistów, w tej grupie znajdują zarówno pojedyncze rozwiązania tworzone przez niezależnych developerów jak również potężne narzędzia takie jak numpy czy matplotlib.

Dodatkowo, jak każdy język programowania, daje możliwość tworzenia własnych funkcji. W tej części zajmiemy przede wszystkim własnymi funkcjami. Stosowanie funkcji wbudowanych zostało wspomniane w poprzednich częściach kursu natomiast bibliotece standardowej zostanie poświęcony osobny moduł.

Definiowanie funkcji

Definicja funkcji składa się z nagłówka funkcji - nazwy, argumentów oraz ciała funkcji - sekwencji wyrażeń jakie mają w ramach funkcji być wykonane. Po zdefiniowaniu funkcji, wystarczy, że wywołamy funkcję podając jej nazwę oraz wymagane argumenty.

Ogólna składnia funkcji to:

def nazwa_funkcji(argument1, argument2=0, `[arguments]`): 
    '''opis funkcji (docstring)

    Keyword arguments:
    argument1 -- parameter string (requited)
    argument2 -- wartość float (optional) -- (default 0.0)
    '''
    #ciało funkcji
    blok instrukcji do wykonania
    return <wyrazenie>

nagłówek funkcji - zawiera:

  • słowo kluczowe def wskazujące, że mamy doczynienia z funkcją.
  • nazwę funkcji
  • argumenty funkcji rozdzielone przecinkiem i podane w nawiasach. Funkcja może posiadać od 0 do dowolnej liczby argumentów.
  • dwukropek oznaczający zakończenie nagłówka

Kolejne linie muszą zaczynać się od wcięcia. Tak jak w przypadku instrukcji warunkowych oraz pętli stosujemy 4 spacje jako wcięcie.

Druga linia (opcjonalna) zawiera opis funkcji. Choć nie jest to wymagane (tj. bez tej części funkcja zadziała), każda funkcja powinna być dobrze udokumentowana. Opis funkcji musi być zawarty pomiędzy trzema apostrofami, tzw. Triple quote. Trzecia i kolejne linie to ciało funkcji zawierają blok instrukcji, który ma zostać wykonany w ramach funkcji (tzw. ciało funkcji, ang. function body). return zamyka funkcję oraz wskazuje co funkcja zwróci jako wynik.

Nie można zdefiniować w jednym module/skrypcie dwóch funkcji o tej samej nazwie. Jeśli tak zrobimy, druga funkcja nadpisze pierwszą.

Nazewnictwo funkcji powinno być spójne – preferuje się słowa rozdzielone podkreślnikiem, np. moja_funkcja.

Jako przykład zdefiniujmy funkcję obliczającą pole prostokąta. Wynik funkcji można też przypisać do zmiennej. W podstawowej wersji pythona nie określamy typów argumentów ani typu zwracanego wyniku.

In [2]:
def pole_prostakata(bok_a, bok_b):
    '''
    Funkcja wylicza pole prostokata na podstawie podanego boku a oraz 	boku b
    Keyword arguments:
    bok_a float -- bok krótszy (requited)
    bok_b float -- bok dłuższy (requited)
    '''
    pole = bok_a * bok_b
    return pole

print(pole_prostakata(5,4))
20

Funckja wbudowana help() może być także stosowana z funkcjami zdefiniowanymi przez użytkownika. Wywołując help() z nazwą funkcji jako argumentem, poznamy argumenty funkcji oraz informację zapisaną w docstring.

In [3]:
help(pole_prostakata)
Help on function pole_prostakata in module __main__:

pole_prostakata(bok_a, bok_b)
    Funkcja wylicza pole prostokata na podstawie podanego boku a oraz   boku b
    Keyword arguments:
    bok_a float -- bok krótszy (requited)
    bok_b float -- bok dłuższy (requited)

Argumenty funkcji

W ramach funkcji można definiować dowolną liczbę argumentów. Sposób definiowania argumentów dzieli się na cztery grupy:

  • argumenty pozycyjne, wymagane - są to argumenty, które nie mają przypisanej wartości domyślnej, znajdują się na początku listy argumentów
  • argumenty opcjonalne, znajdują się na liście za pozycyjnymi, posiadają wartość domyślną
  • lista argumentów *args - dowolnej długości lista nie nazwanych argumentów
  • słownik argumentów **kvargs - dowolnej długości lista nazwanych argumentów, ale nie zdefiniowanych w funkcji, muszą się znajdować za argumentami opcjonalnymi

* args stosujemy w sytuacji, gdy do funkcji chcemy przekazać niekoreśloną listę argumentów tego samego typu, natomiast **kvargs jeżeli chcemy przekazać nieokreśloną listę argumentów o znanej nazwie, z których jedynie nieliczne będą wykorzystane. Stosuje się w wypadku definiowania listy pól w tabeli albo do przekazywania argumentów do innych funkcji wywołwywanych wewnątrz funkcji.

Jeżeli podajemy argumenty zgodnie z kolejnością ich definiowania w nagłówku nazwy argumentów można pominąć, natomiast jeżeli zmieniamy ich kolejność lub nie podajemy wszystkich opcjonalnych, należy podawać ich nazwy. Nazwy należy podawać jeżeli stosujemy **kvargs.

In [4]:
def pozycyjne(A,B):
    return A+B

def opcjonalne(A,B=1.0):
    return A/B

pozycyjne(3,4)
opcjonalne(5)
opcjonalne(B=2.,A=4) # zmiana kolejności
Out[4]:
7
Out[4]:
5.0
Out[4]:
2.0

lista argumentów i słownik argumentów to zagadnienie nieco bardziej skomplikowane. Najlepiej omówić je na przykładzie.

Listę argumentów stosuje się jeżeli chcemy przekazać do funkcji nieznaną przed wykonaieniem listę argumentów o nieokreślonej liczbie. W praktyce lista argumentów przekazywana jest jako krotka a nie lista. Klasycznym przykładem będzie tu opracowana funkcja suma(), która ma na celu obliczyć sumę wprowadzonych liczb:

In [5]:
def suma(*A):
    wynik = 0;
    for a in A:
        wynik +=a
    return wynik

suma(1,2,4,5,6,7)
suma(1)
Out[5]:
25
Out[5]:
1

Co zrobić jeżeli do takiej funkcji chcemy przekazać istniejącą już listę? W takiej sytucji należy zastosować operator * określony jako operator rozwijania listy/krotki (ang. expand tuple). Zamienia on listę jako w zbiór niezależnych literałów. Jeżeli lista zostanie przekazana bez gwiazdki, zostanie zgłoszony błąd.

In [6]:
lista = [1,2,3,4,5,6,7]
suma(*lista)
Out[6]:
28

W przypadku rozwijanego słownika, każdy utworzony argument funkcji będzie pozycją słownika o kluczu zdefiniowanym przez argument. Dostęp do wartości uzyskamy przez wywołanie słownika z odpowiednim kluczem. Jeżeli funkcja będzie zawierać odwołania do słownika z kluczem, który nie został przekazany fukcja zgłosi błąd.

In [7]:
def slownik(A,**B): #2
    return A + B['c'] +B['d'] # funkcja zna zmienne o tej nazwie
In [8]:
slownik(1,c=5,d=2)
Out[8]:
8

Z tego powodu rozwijalne słowniki należy stosować w sytuacji przekazywania atrybutów, dla których zostały zdefiniowane wartości domyślne. W praktyce to rozwiązanie stosuje się, gdy nasza funkcja wywołuje inną funkcję, dla której możemy (ale nie musimy) zmienić domyślne argumenty. Należy się jednak upewnić, czy takie argumenty przekazano.

Funkcje a zasięg zmiennych (scope)

W języku python każda zmienna raz zainicjownana funkcjonuje do mementu aż zakończy się blok, w ramach którego została zainicjowana lub zostanie ręcznie usunięta operatorem del.

W przypadku zmiennych inicjowanych w głównym ciele skryptu ale też w obrębie pętli i warunków zmienna istnieje aż do zakończenia działania skryptu, natomiast zmienne inicjowane w ciele funkcji tylko do momentu zakończenia działania tej funkcji. Co więcej zmienna inicjowna w obrębie fukcji może mieć taką samą nazwę jak zmienna zainicjowana poza funckcją

In [9]:
a = 3
print(a) #3

def funkcja(b):
    print(b) #5
    #print a zgłosi błąd, zmienna niezainicjowana
    a = 6
    print(a) #6

funkcja(5)
print(a) #ponownie 3
    
3
5
6
3

Zasięg zmiennych nie dotyczy obiektów złożonych. Jeżeli do funkcji zostanie przekazany obiekt złożony, modyfikowalny (lista, słownik) i w obrębie funkcji zostanie on zmodyfikowany, to zostanie zmodyfikowany również poza funkcją. Nazywa się to przekazywaniem argumentów przez referencję:

In [10]:
lista = [1,2,3,4]
lista

def modyfikuj(lista):
    lista.append(10)

modyfikuj(lista)
lista
Out[10]:
[1, 2, 3, 4]
Out[10]:
[1, 2, 3, 4, 10]