TI/Programowanie dla Fizyków Medycznych:Funkcje

Z Brain-wiki

Funkcje

Przy programowaniu często zdarza się, że pewne czynności chcemy wykonywać wiele razy, warto wtedy zorganizować nasz kod w taki sposób, aby było w nim możliwie mało powtórzeń kodu. Jest to istotne w przypadku gdy chcemy wprowadzać zmiany, jeśli nawet drobna zmiana będzie wymagała identycznych poprawek w kilku miejscach to z dużym prawdopodobieństwem w jednym miejscu poprawki dokonamy a w innym nie i nasz kod będzie bardzo trudny w utrzymaniu. Kolejnym powodem dla którego warto dobrze zorganizować kod jest jego czytelność - w czasie pisania programu oczywiście jesteśmy w stanie posuwać projekt dalej nawet jeśli nie jest on zbyt czytelnie napisany, ale gdy z kodem zacznie pracować ktoś inny, albo sami wrócimy do niego po jakimś czasie odnalezienie się w źle zorganizowanym kodzie nie będzie proste. Python dostarcza trzech mechanizmów pozwalających organizować i wielokrotnie wykorzystywać fragmenty kodu - funkcje, dekoratory i klasy, czyli programowanie zorientowane obiektowo. W tym rozdziale zajmiemy się funkcjami.

Poznaliśmy już sporo wbudowanych w Pythona funkcji takich jak abs(), all(), any(), dict(), tuple(), set(), sum(), min(), max(), enumerate(), range() czy xrange(), teraz zajmiemy się tworzeniem własnych funkcji.

Funkcje tworzy się za pomocą słowa kluczowego def, po nim następuje nazwa funkcji, lista argumentów w nawiasie i dwukropek, a poniżej ciało funkcji będące zwykłymi instrukcjami Pythona poprzedzonymi pojedynczym wcięciem, na przykład funkcja wypisująca potęgę zadanej wartości może wyglądać następująco:

def f(x):
    print x ** 2

i jej wywołanie:

>>> f(10)
100

Lista argumentów może być pusta:

>>> def f():
	print 'Ala'

	
>>> f()
Ala

argumenty mogą mieć wartości domyślne i wtedy jeśli przy wywołaniu funkcji nie zostaną podane ich wartości to przyjmowane są wartości domyślne, obowiązuje jednak zasada, że na argumenty z wartościami domyślnymi występują na końcu listy argumentów w definicji funkcji:

>>> def f(x, y = 'Ala'):
	print x, y

	
>>> f('kot')
kot Ala
>>> f('kot', 'Basia')
kot Basia
>>> def f(x = 'kot', y):
	print x, y
	
SyntaxError: non-default argument follows default argument

Pracując z funkcjami mającymi argumenty z wartościami domyślnymi warto uświadomić sobie, że wartość domyślna jest tworzona nie przy każdym wywołaniu funkcji, ale w czasie definiowania funkcji i później ta wartość jest w miarę potrzeb wykorzystywana, jest to istotne w przypadku, gdy wartości domyślne są obiektami modyfikowalnymi:

>>> def f(x, lista = []):
	lista.append(x)
	print lista

	
>>> f(1)
[1]
>>> f(2)
[1, 2]
>>> f(3, [])
[3]
>>> f(4)
[1, 2, 4]

W powyższym przykładnie w pamięci trzymana jest jedna domyślna lista i jest ona przypisywana na drugi argument jeśli nie został on podany w wywołaniu funkcji, ale ta lista jest modyfikowana przez funkcję, zatem, wbrew temu co wydaje się naturalne, jeśli nie podamy drugiego argumentu to nie koniecznie będzie on pustą listą. Należy również pamiętać, że argumenty do funkcji przekazywane są przez referencje a nie przez wartości (co jest ważne gdy pracujemy z obiektami modyfikowalnymi):

>>> def f(x, lista):
	lista.append(x)

	
>>> A = []
>>> f(1, A)
>>> A
[1]

Przy wywołaniu funkcji jej argumenty można podawać pozycyjnie, czyli pierwsza wartość w wywołaniu jest podstawiana pod pierwszy argument z definicji, ale można także podawać argumenty po nazwach z definicji, zwiększa to czytelność kodu, pozwala nie dbać o kolejność argumentów i w przypadku, gdy mamy kilka argumentów z wartościami domyślnymi pozwala zastępować tylko wybrane z nich. Należy pamiętać, że po podaniu pierwszego argumentu przez nazwę wszystkie pozostałe muszą również być przekazane przez nazwę:

>>> def f(x = 1, y = 2, z = 3):
	print 'pierwszy : ', x, ' drugi : ', y, ' trzeci : ', z

	
>>> f()
pierwszy :  1  drugi :  2  trzeci :  3
>>> f(4, 5, 6)
pierwszy :  4  drugi :  5  trzeci :  6
>>> f(z = 4, x = 5, y = 6)
pierwszy :  5  drugi :  6  trzeci :  4
>>> f(y = 9)
pierwszy :  1  drugi :  9  trzeci :  3
>>> f(x = 1, 2)
SyntaxError: non-keyword arg after keyword arg

Można także zdefiniować funkcje przyjmujące dowolną liczbę argumentów - umieszczenie na liście argumentów w definicji nazwy poprzedzonej * (na przykład *args) sprawia, że wszystkie nadmiarowe argumenty przekazane pozycyjnie zostaną wpisane w krotkę args. Na liścia argumentów zmienna z * musi znajdować się po wszystkich argumentach, które muszą być przekazane i może występować tylko raz. Podobnie podając na liście argumentów zmienną poprzedzoną ** (na przykład **kwargs) wszystkie nadmiarowe argumenty przekazane po nazwie zostaną umieszczone w słowniku kwargs, ta zmienna podobnie jak *args może wystąpić tylko raz i musi być ostatnią zmienną na liście argumentów:

>>> def f(x = 1, *args):
	print 'pierwszy : ', x, 'pozostałe : ', args

	
>>> f()
pierwszy :  1 pozostałe :  ()
>>> f(1)
pierwszy :  1 pozostałe :  ()
>>> f(1, 2)
pierwszy :  1 pozostałe :  (2,)
>>> f(1, 2, 3)
pierwszy :  1 pozostałe :  (2, 3)
>>> def f(x = 1, *args, **kwargs):
	print 'pierwszy : ', x, 'pozostałe pozycyjne : ', args, 'pozostałe po nazwie : ', kwargs

	
>>> f()
pierwszy :  1 pozostałe pozycyjne :  () pozostałe po nazwie :  {}
>>> f(1)
pierwszy :  1 pozostałe pozycyjne :  () pozostałe po nazwie :  {}
>>> f(1, 2)
pierwszy :  1 pozostałe pozycyjne :  (2,) pozostałe po nazwie :  {}
>>> f(1, 2, 3)
pierwszy :  1 pozostałe pozycyjne :  (2, 3) pozostałe po nazwie :  {}
>>> f(1, 2, 3, Ala = 'kot')
pierwszy :  1 pozostałe pozycyjne :  (2, 3) pozostałe po nazwie :  {'Ala': 'kot'}
>>> f(1, 2, 3, Ala = 'kot', Basia = 'pies')
pierwszy :  1 pozostałe pozycyjne :  (2, 3) pozostałe po nazwie :  {'Basia': 'pies', 'Ala': 'kot'}
>>> def f(x = 1, *args, y, **kwargs):
	print 'pierwszy : ', x, 'pozostałe pozycyjne : ', args, 'pozostałe po nazwie : ', kwargs
	
SyntaxError: invalid syntax
>>> def f(x = 1, *args, **kwargs, y):
	print 'pierwszy : ', x, 'pozostałe pozycyjne : ', args, 'pozostałe po nazwie : ', kwargs
	
SyntaxError: invalid syntax
>>> def f(x = 1, *args, *args2):
	print 'pierwszy : ', x, 'pozostałe pozycyjne : ', args, 'pozostałe po nazwie : ', kwargs
	
SyntaxError: invalid syntax
>>> def f(x = 1, **kwargs, **kwargs2):
	print 'pierwszy : ', x, 'pozostałe pozycyjne : ', args, 'pozostałe po nazwie : ', kwargs
	
SyntaxError: invalid syntax
>>> def f(x = 1, **kwargs, *args):
	print 'pierwszy : ', x, 'pozostałe pozycyjne : ', args, 'pozostałe po nazwie : ', kwargs
	
SyntaxError: invalid syntax

Jak widać pierwszy argument w wywołaniu funkcji jest zawsze przekazywany na pierwszy argument z listy w definicji nawet jeśli posiada on wartość domyślną, niemożliwe jest również dwukrotne zdefiniowanie argumentu - pozycyjnie i przez nazwę:

>>> def f(x):
	print x

	
>>> f(0, x = 1)

Traceback (most recent call last):
  File "<pyshell#132>", line 1, in <module>
    f(0, x = 1)
TypeError: f() got multiple values for keyword argument 'x'

Innym ciekawym zastosowaniem * jest rozpakowywanie sekwencji, a ** rozpakowywanie słowników, elementy sekwencji są podawane jako kolejne argumenty pozycyjne w wywołaniu funkcji, z kolei elementy słownika jako argumenty podawane przez nazwę:

>>> def f(x, y, z):
	print 'x :', x, 'y :', y, 'z :', z

	
>>> A = [1, 2, 3]
>>> f(*A)
x : 1 y : 2 z : 3
>>> B = {'x' : 1, 'y' : 2, 'z' : 3}
>>> f(**B)
x : 1 y : 2 z : 3
>>> A = (1, 2)
>>> B = {'z' : 3}
>>> f(*A, **B)
x : 1 y : 2 z : 3

Funkcje mogą zwracać pewne wartości dzięki wykorzystaniu słowa kluczowego return po którym podawana jest wartość, którą funkcja ma zwrócić i następuje wyjście z funkcji. Funkcje, które nic nie zwracają (nie ma w nich polecenia return) zwracają wartość None. Wartości zwracane przez funkcje mogą być przypisane na zmienne. Polecenie return można również stosować bez parametru (wtedy zwracana jest wartość None) do kończenia wykonania funkcji:

>>> def f():
	print 'funkcja bez return'

	
>>> a = f()
funkcja bez return
>>> print a
None
>>> def f():
	print 'przed return'
	return 5
	print 'po return'


>>> a = f()
przed return
>>> a
5
>>> def f():
	print 'przed return'
	return
	print 'po return'

	
>>> a = f()
przed return
>>> print a
None

Funkcje można traktować jak normalne zmienne, przekazywać je do innych funkcji, zwracać je przez return i definiować wewnątrz innych funkcji:

>>> def potegowanie(n):
	def f(x):
		return x ** n
	return f

>>> doKwadratu = potegowanie(2)
>>> doSzescianu = potegowanie(3)
>>> doKwadratu(5)
25
>>> doSzescianu(5)
125
>>> def wykonajDwaRazy(f):
	f()
	f()

	
>>> def f():
	print 'Ala'

	
>>> wykonajDwaRazy(f)
Ala
Ala

Funkcje można tworzyć także przy pomocy słowa kluczowego lambda po którym występuje liczba parametrów, dwukropek i wartość zwracana przez funkcję:

>>> f = lambda x, y, z: x + y + z
>>> f(1, 2, 3)
6

Python pozwala w prosty sposób pisać dokumentację do napisanych przez nas funkcji przez umieszczenie w pierwszej linii ciała funkcji napisu, którego pierwsza linia jest krótkim opisem funkcji, po niej następuje pusta linia i szczegółowy opis, na przykład:

>>> def f():
	"""Opis ogólny

	Opis szczegółowy
	"""
	pass

>>> help(f)
Help on function f in module __main__:

f()
    Opis ogólny
    
    Opis szczegółowy

>>> print f.__doc__
Opis ogólny

	Opis szczegółowy

Zadanie 1

Napisz funkcje przyjmującą jako parametr napis i liczbę całkowitą i szyfrującą napis za pomocą szyfru Cezara z przesunięciem równym danej liczbie.

Zadanie 2

Napisz funkcję przyjmującą jako parametr liczbę całkowitą x i zwracającą jej rozkład na czynniki w postaci listy [(p1,w1), (p2,w2), ... , (pn, wn)] takiej, że x = p1 ** w1 + p2 ** w2 + ... + pn ** wn

Zadanie 3

Napisz funkcję zwracającą silnię zadanej jako parametr liczby całkowitej, zrób to przy pomocy pętli i rekurencji.

Zadanie 4

Napisz funkcję zwracającą listę liczb pierwszych nie większych od zadanej liczby całkowitej.

Zadanie 5

Napisz cztery różne funkcje sortujące daną listę przy pomocy następujących algorytmów:

  • sortowanie przez wstawianie
  • sortowanie przez wybór
  • sortowanie bąbelkowe
  • sortowanie metodą QuickSort

Następnie napisz osobną funkcję, która posłuży przetestowaniu powyższych algorytmów sortujących, przyjmując jako parametr nazwę funkcji sortującej.

Zadanie 6

Napisz funkcję przyjmującą napis składający się ze znaków '(', ')', '[', ']', '{' i '}' i sprawdzający czy napis jest poprawnym wyrażeniem nawiasowym (na przykład: '(){[]{([])}}' jest poprawne, a '[{]}' nie jest)

Zadanie 7

Pod adresem http://www.scientificstyleandformat.org/Tools/SSF-Citation-Quick-Guide.html można znaleźć wskazówki odnośnie formułowania cytowania w bibliografii artykułów z czasopism naukowych. Napisz funkcję, która w postaci parametrów otrzyma potrzebne informacje i na ich podstawie sformułuje tekst gotowy do umieszczenia w bibliografii. Niektóre parametry (np. autor) powinny być obowiązkowe, niektóre (np. tom) mogą nie wystąpić. Funkcja powinna dawać odpowiednie informacje przy podaniu jej niewłaściwych parametrów, jak również potrafić zaakceptować niektóre parametry w dwóch formach, np. jednego autora w formie string "Kowalski A", a dwóch w formie krotki stringów ("Nowak J", "Kowalski B").


"Programowanie dla Fizyków Medycznych"