Jól kihagytam az érettségis blogbejegyzést. Nem is keresek kifogásokat, lusta voltam. Ellenben már készülőben van (konkrétan több mint a fele magvan) az ezt követő, tehát a 11. része a tutorialnak.
Modulok. Library-k. Függvénykönyvtárak.
Egyre hosszabb kódokat írogatunk és látható, hogy csak nem kéne egyetlen fájlba írni a komplett programot, mert átláthatatlanná válik a munkánk. A modulok ezt a problémát oldják meg: kirakhatjuk a forráskód egyes részeit különböző fájlokba, és ezekből a fájlokból akkor és ott töltünk be egy részt (konkrétabban: valami objektumot, függvényt, vagy csak egyszerű konstans értékeket) amikor arra szükség van.
Ahogy modulokra bontjuk a programunkat úgy a modulok hordozhatóvá is válnak: összegyűjtünk egy csokor objektumot, azok saját modulba kerülnek, és ha egy másik programban ugyan ezekre (vagy csak néhány) az objektumokra lenne szükség, akkor egyszerűen csak oda másoljuk a modult az új programhoz, és már használhatóak azok a kódok, amiket régen megírtunk.
Látható hogy így megoldható a hordozhatóság, és a kód újrahasznosítás problémája. De nem csak a saját magunknak írhatunk modulokat, hanem mások is írhatnak nekünk, és ezek tetszőlegesen terjeszthetőek. A modulok használata a szoftverfejlesztés szerver része, ugyanis rengeteg olyan modul érhető el, amik olyan funkciókat valósítanak meg, amit ha magunknak kéne implementálnunk akkor az igen sokáig tartana (de az is lehet, hogy egyáltalán nem vagyunk elég képzettek hogy kidolgozzuk a részleteket).
Tehát modulokat azért használunk, hogy a saját programunkat szétbontsuk, és azért, hogy új funkciókat érhessünk el a Pythonban, úgy, hogy azokat más programozók helyettünk már megírták.
A standard library
A Pythonra szokták azt is mondani, hogy batteries included (az elemeket tartalmazza), azaz nem csak a nyers szintaxist meg az interpretert kapjuk, hanem egy halom nagyon hasznos modul is beépítve részét képezi a Pythonnak. A beépített modulokat a standard library (stdlib) kifejezéssel szokták illetni.
Oké de milyen modulok állnak rendelkezésre? A The Python Standard Library reference oldalán egy igen hosszú listát láthatunk. Ezek közül egy pár, ami érdekesebb lehet:
datetime
- Dátum- és idő kezelő függvényekdecimal
- Tetszőleges pontosságú tizedes törtekio
- Műveletek be- és kimeneti adatokkalmath
- Matematikai műveletekmultiprocessing
ésthreading
- Több szálon / processzor magon futó programok fejlesztéséhezrandom
- Véletlenszerű értékek generálásáhozsqlite3
- SQLite SQL adatbázis eléréséhezsubprocess
- Programok indítása és vezérlése Python forráskódbóltempfile
- Átmeneti fájlok és könyvtárak létrehozásáhoztime
- Aktuális idő vagy program futásának szüneteltetésetraceback
- Python traceback-ek elérésestr
-ként vagy formázásukurllib
- Adatok letöltése az internetrőlunittest
- Tesztek fejlesztéséhezzipfile
-.zip
kiterjesztésű fájlok írása, olvasása
Tudom hogy ezek a jellemzések igen rövidek. Általában 1-1 modulról 1-2 teljes bejegyzést lehetne mesélni. Valószínűleg ahogy véget ér ez a tutorial elkezdem a standard library részletes bemutatását. De ez még a jövő zenéje.
Ez most csak 14 modul amit így gyorsan összeszedtem, és viszonylag könnyen megfoghatónak találtam, csak a jéghegy csúcsa. Aki a Python telepítés című postom alapján telepítette a Python értelmezőjét, az installálta a pip
nevű programot is, ami egy úgynevezett csomagkezelő. Ez a program képes az interneten fellelhető csomagok (modulok) telepítésére, amikből több tízezer található. De erre majd később térünk vissza.
Egy csomagban (package) több modul található, ezért ezek direkt különböző fogalmak.
Modulok használata
A modul az importálás után válik használhatóvá. Ez kétféleképp lehetséges, az import
utasítással például így:
import random
E sor után a random
modul elérhető lesz a forráskódban, aminek a tartalmára a random
névvel hivatkozhatunk. A modulban található értékek (konstansok, függvények, osztályok) hasonlóan érhetőek el, mint ha azok a random
objektum adattagjai lennének, azaz a .
operátorral, például így:
import random
number = random.randint(0, 10)
A fenti sorral a
random.randint()
egy darab 0 és 10 közötti véletlenszerű számot generál, amitnumber
néven tárolunk el.
Előfordulhat hogy nem akarjuk a teljes modult elérhetővé tenni, mert nem akarunk olyan sokat gépelni, vagy mert a modul betöltése túl sokáig tartana. Erre az esetre használhatjuk a from ... import ...
kifejezést:
from random import randint
number = randint(0, 10)
Az 1. sorban álló kifejezés már-már egy komplett angol mondat: a random modulból importáld a randintet. Látható, hogy ilyenkor többé nem random.randint()
-ként érjük el az importált metódust. A random
név a forráskódban továbbra se lesz definiálva.
Az importálások általában a forráskód elejére kerülnek, néhány speciális eset kivételével, például a körkörös függőség (circular dependency) esetén.
Importált objektum átnevezése
Ez egy igen rövid történt, ezért leírom. Az as
kulcsszóval át tudjuk nevezni az importált modult vagy objektumokat. Ez nagyon jól jön akkor, ha amúgy névütközés alakulna ki a forráskódban, vagy az importált objektum neve túl hosszú, vagy simán csak nem tetszik.
import random as rndm
from random import randint as random_integer
Gondoltam egy számra
Oké, lássuk ezt a gyakorlatban az ide vágó klasszikus feladattal: írjunk egy olyan játékot, hogy a számítógép gondol egy tetszőleges 0 <= x <= 100
számra és a játékosnak ki kell találnia hogy mire gondolt a gép. Ha a kipróbált szám kisebb a gondoltnál, akkor írjuk ki hogy “kisebb”, ha nagyobb akkor értelem szerűen azt hogy “nagyobb”. Addig fusson a játék amíg nincs találat.
Lássuk a kódot!
import random
secret = random.randint(0, 100)
while True:
player_number = int(input('Your guess: '))
if player_number < secret:
print('Your guess is too low')
elif player_number > secret:
print('Your guess is too high')
else:
print('You guessed my number!')
break
Igazából az érdekes részeket le is tudjuk az első 3 sorban: az 1. sorban importáljuk a random
modult, a 3. sorban pedig előállítok egy véletlenszerű számot 0 és 100 között és eltárolom a secret
változóban.
Ez után csak a szokásos dolgok jönnek: indítunk egy vételen ciklust, mivel nem tudjuk hogy mikor ér majd véget a játék. Bekérjük a kipróbált számot amit player_number
néven tárolunk el. Ez után jön pár ellenőrzés: ha kisebb a játékos száma akkor kiírjuk hogy túl kicsi a szám; ha nagyobb a játékosé akkor kiírjuk hogy túl nagy; amúgy meg nyerés van. Pont ahogy a feladat leírásban volt.
Na akkor futtassunk egy menetet:
Your guess: 50
Your guess is too low
Your guess: 75
Your guess is too high
Your guess: 62
Your guess is too low
Your guess: 68
Your guess is too high
Your guess: 65
Your guess is too low
Your guess: 66
Your guess is too low
Your guess: 67
You guessed my number!
Tök fasza nem?
A programot megszakítani a
CTRL+C
billentyűkombinációval lehet (szokás szerint).
A kitalálás során fejben a bináris keresés algoritmusát alkalmaztam.
Dátum és óra
Írjunk egy egyszerű kis scriptet ami a számítógépen beállított dátumot és időt mutatja élőben, tehát minden másodpercben frissüljön:
import time
while True:
print(time.strftime('%Y-%m-%d %H:%M:%S'), end='\r')
time.sleep(1)
A forráskód futását a sleep()
függvénnyel tudjuk megállítani, ami a time
modulban található. Itt található többek közt az strftime()
függvény is, amivel formázhatjuk az idő megjelenését.
Importáltuk tehát a time
modult, és indul is a végtelen ciklus. Itt a print()
-en belül meghívjuk a time.strftime()
függvényt. Az strftime()
-nak adott '%Y-%m-%d %H:%M:%S'
paraméter szerint az időt úgy formázzuk, hogy az ehhez hasonlóan jelenjen meg: 2016-09-25 11:56:12
.
Most nem mennék bele, hogy melyik hieroglifa mit jelent, de az
strftime()
leírásában megtalálható minden.
A print()
-ben még beállítjuk az end='\r'
kwarggal, hogy a sor végét lezáró karakter legyen a carriage return (erről itt lehet olvasni). Így tehát kiíródik a dátum és idő, majd a kurzor visszakerül a sor elejére, és a következő kiírás felülírja a meglevőt. Így az óra egy helyben marad.
Az utolsó sorban a time.sleep(1)
-el 1 másodpercig áll a program. Itt tetszőleges törtet is megadhatunk (így kevesebb időt is várakozhatunk).
Megoldás másik modullal
A datetime
modul is dátum és idő funkciókat tartalmaz. Ebben a modulban van egy olyan függvény, ami egyből szépen formázva adja vissza az időt. Próbáljuk ki ezt:
import datetime
import time
while True:
print(datetime.datetime.now(), end='\r')
time.sleep(1)
Importáltuk a datetime
modutl, és a print()
-be beírtuk a datetime.datetime.now()
hívást. Ha futtatjuk a programot akkor egy kicsit pontosabb időt látunk, mert látszódnak a milliszekundumok, de ez most elhanyagolható. Valami ilyesmi jelenik meg: 2016-09-25 12:05:49.251479
Ez a fenti egy érdekes példa, amit nem is említettem külön: a modulok tartalmazhatnak modulokat, ezzel nincs semmi gond, csupán még egy .
-ot kell kifejezésbe tenni.
Az is látszik a példában, hogy 2 km kódot kell írni a pontos időhöz. Írjuk át így a kódot:
import time
from datetime import datetime
while True:
print(datetime.now(), end='\r')
time.sleep(1)
A
datetime
csomag tartalmazza az azonos nevűdatetime
modult, de ott vannak még például atime
,date
vagytimedelta
modulok is a csomagban.
Még két példa
A fenti példákból úgy tűnhet mintha csak függvények lennének elérhetőek a modulokból, de ez nem így van. Például ha a pi értéke elég pontosan elérhető a math
modulból:
import math
print(math.pi)
Látszik, ez csak egy egyszerű tört.
Egy kicsit szeméylesbb példával zárnám ezt a blokkot. Anno azért kezdtem el komolyabban programozni, hogy tudjak írni egy olyan programot, amivel leelőzhetem a barátaimat egy hülye Facebookos játékban. Ehhez az kellett hogy a programom tudjon kommunikálni a távoli szerverrel, szóval hogy elérhessem az internetet. Az urllib
segítségével ez megoldható.
Ez a kis script letölt egy weboldalt, ami az IP-címünket tartalmazza:
import urllib.request
response = urllib.request.urlopen('https://httpbin.org/ip')
print(response.read())
A kimeneten valami ehhez hasonló jelenik meg: b'{\n "origin": "10.0.0.1"\n}\n'
. Ez amúgy ránézésre majdnem olyan mintha Python kód lenne, de valójából JSON objektumot látunk. A json
modullal átalakíthatjuk egy dict
-é ezt az eredményt:
import json
import urllib.request
response = urllib.request.urlopen('https://httpbin.org/ip')
my_ip = json.loads(response.read().decode())['origin']
print(my_ip)
Nem minden szerver válaszol ilyen szépen formázva, de a példa kedvéért kerestem egyet.
Honnan tudom hogy mi van a modulban?
Kérdés hogy mégis honnan a picsából kéne tudnom, hogy mi-merre van? Sehonnan. Senki se tudja magától, senki se tudja mit-hogy szoktak a programozók elnevezni. Így válik nyilvánvalóvá, hogy nem is magát a Pythont (szintaxist, kifejezéseket) tart sokáig megtanulni, hanem azt, hogy melyik csomagban és modulban mi van; azt hogy hívják, és hogyan lehet elérni. Általában elmondható, hogy a csomagok ismeretének elsajátítása tart a legtovább a programozói karrier építése során.
A tanulás során érdemes mindig kéznél tartani a Python Standard Library oldalát, illetve olyan fejlesztői környezetet (IDE) beszerezni, ami ki tudja egészíteni a parancsainkat, így nem kell mindenre pontosan emlékezni.
Igazából a Python nyelvtől teljesen független, hogy az ember milyen fejlesztői környezetet használ, ezért nem is foglalkoztam ezzel a kérdéssel eddig, és most se szánnék rá időt. Rövidre fogva, én a JetBrains PyCharm szoftverét használom évek óta.
Hogyan kell modult írni?
Azt hittem már hogy sose érkezünk meg ehhez a ponthoz. Modult szerencsére pofon egyszerű írni: másoljuk össze a kódjainkat egy különálló .py
fájlba, és a fájl neve lesz a modul. Ilyen egyszerű.
Korábban a számkitatlálós példában írtam az int(input())
kifejezést, ezzel alakítottam át a játékos által beírt szöveget int
-é. Ezt a kis kódocskát átalakítjuk egy függvénnyé, és kirakjuk a typed_inputs.py
nevű fájlba.
# typed_inputs.py
def int_input(prompt):
return int(input(prompt))
Mondjuk azt, hogy a
typed_inputs
modulban olyan szöveg beolvasó függvényeket gyűjtünk, amik megfelelő típusúvá alakítják a felhasználótól érkező szöveget. Így azint_input()
-tól azt várjuk, hogy egyint
-el tér vissza.
Ez után egy másik fájlból, amit most hívjunk program.py
-nak, importáljuk a modult így:
# program.py
import typed_inputs
number = typed_inputs.int_input('Enter a number: ')
És kész is: az int
-es inputunk mostantól független életet élhet.
Hogyan kell csomagot írni?
A csomag sem egy nagy mutatvány, ezért is nem írtam róla sokat idáig. Annyit kell róluk tudni, hogy több modult fognak össze. Roppant könnyű létrehozni egy sajátot: tegyük a moduljainkat egy könyvtárba, és ebben a könyvtárban készítsünk egy __init__.py
elnevezésű fájlt is. A könyvtár neve lesz a csomag neve.
Véleményem szerint csak azért nem szükséges csomagot készíteni hogy legyen olyan is. Akkor érdemes csomagot írni ha van több különböző modulunk amik logikailag jól összetartoznak. A modulok nagyon jól elvannak magukban is a hétköznapokban.
Tegyük fel hogy mindenféle típus-specifikus modulokat írogattunk, úgyhogy ezeket belerakjuk a typed
nevű csomagba. A következő könyvtárstruktúrát alakítsuk ki:
program.py
typed/
__init__.py
inputs.py
“Magyarul”: legyen egy
program.py
fájlunk és mellette egytyped
nevű könyvtár. Ebben a könyvtárban legyen egy__init__.py
és egyinputs.py
nevű fájl.
Ezért a példáért nem akartam új forráskódot kitalálni, szóval az inputs.py
tartalma egyezzen meg a feljebb bemutatott typed_inputs.py
tartalmával. Ha ez megvan, akkor a program.py
így módosul:
# program.py
from typed import inputs
number = inputs.int_input('Enter a number: ')
A lényeg a 3. sorban látható: a typed
csomagból importáljuk az inputs
modult. Ez a forma pont úgy néz ki, mint amikor egy modulból egy objektumot importáltunk. Akkor most hogyan tudjuk megkülönböztetni hogy csomagból vagy modulból történt az importálás? Szokás szerint nem tudjuk. De igazából érdekel ez akárkit is? Nem.
Egyébként ha már itt tartunk, még mindig lehetséges a typed
csomag inputs
moduljának egyetlen objektumát importálni ha ezt írjuk:
from typed.inputs import int_input
Viszont meglepő lehet, de ez nem fog működni:
# program.py
import typed
number = typed.inputs.int_input('Enter a number: ')
Mire való az __init__.py
?
A csomagban helyet foglaló __init__.py
, mint ahogy egyébként bármi más is, nem dísznek került oda a könyvtárba. Persze állhat üresen is, a csomag akkor is működni fog, de írhatunk ide mindenféle inicializáló kódokat. Ez azt jelenti, hogy amikor a csomagból importálunk valamit, akkor az __init__.py
fájl tartalma automatikusan le fog futni!
Írjuk be ezt a typed/__init__.py
fájlba:
# __init__.py
print('*typed package is in action*')
Írjuk vissza a program.py
-t a működő verzióra és futtassuk így. Valami ilyesmi fog megjelenni a konzolban:
*typed package is in action*
Enter a number:
A *typed package is in action* szöveg varázslatosan megjelent abban a pillanatban, hogy az importálásra sorára került a program futása.
Remélem érezhető hogy ide akármilyen kódot írhatunk, ami segíthet például beállítani a csomag saját lelki világában valamit még mielőtt az őt használó program futhatna. Egy konkrét esetet mutatnék: írjuk be ezt az __init__.py
-ba:
# __init__.py
from typed import inputs
Így a program.py
-ban ez már lehetséges lesz:
# program.py
import typed
number = typed.inputs.int_input('Enter a number: ')
De ennyi trükközés egyenlőre elég lesz. Majd még visszatérünk erre.
A modul névtere
A névterekről volt már szó. Akkor azt mondtam, hogy minden Python fájl 1-1 névtér. Így, hogy már több fájlunk is van, ez végre értelmet is nyert.
Az egyszerűség kedvéért térjünk vissza erre a struktúrára:
program.py
typed_inputs.py
Ahogy a program.py
elején megtörténik az importálás a typed_inputs.py
tartalma lefut, a saját izolált névterében. Ebből a névtérből egy nevet (függvényt, konstanst, objektumot) az import
utasítással tudtunk elérni. Más módszer viszont nincs az átjárásra.
A modulokat nem úgy kell elképelni, hogy azok összeállnak egyetlen nagy szöveggé amikor elindul a program, hanem mindegyik modul önálló és az importálás hatására kelnek életre. Éppen ezért azok a modulok amik soha se voltak importálva nem is futnak le sose; viszont ha fut az importálás, akkor minden sor végrehajtódik mint bármelyik szabványos Python program esetén.
Egyébként az izoláció nem azt jelenti, hogy csak olvashatóak az importált modulok. Tehát lehetséges a futó modulban található objektumokat módosítani, törölni. Ekkor magában a forráskódban nem történik változás, de a futó program máshogy működhet. De erről majd a második rész szól.
Zárás
Most hogy végre tudunk a modulok és csomagok létezéséről én végre színesebb példákat és ti izgalmasabb programokat írhatok. Alig várom hogy végre valami érdekes szoftvert írjunk. Addig is az objektumorientált programozással folytatjuk (már majdnem kész az 1. rész!)
Ez a bejegyzés a Python tutorialom egyik része. Az összes rész listája itt fellelhető.
-slp
Nincsenek megjegyzések:
Megjegyzés küldése