A Python programozási nyelv

Utasítások, vezérlési szerkezetek

Skip

Létezik üres utasítás, ennek formája:

pass

Általában akkor használják, mikor explicit kiírnak egy végrehajtási ágat, hogy az olvasó lássa, gondoltak az esetre, de nem kell abban az ágban semmit sem csinálni.

Értékadás

Mivel Pythonban minden objektum, az értékadás utasítás referenciákat rendel változókhoz.  A 2.1-es verzióban gyenge referenciák. A dokumentum készítésekor még csak egy proposal és kísérleti implementáció létezett, megtekinthető itt. A szabályai bonyolultak, majd az utasításoknál részletezem. De pl. ez legális, és helyes eredményt ad:

 
a,b = b,a  # megcseréli a-t és b-t

Egyéb példák értékadásra:

c = []; d = [] # c és d két különböző üres lista
c = d = []     # c és d ugyanazt az objektumot jelöli

Konstansok nincsenek, bár léteznek ún. immutable objektumok, melyeknek az értékét nem lehet megváltoztatni. (pl. az integerek)

Az objektumok felszabadításáról az automatikus szemétgyűjtő algoritmus gondoskodik, bár ennek minősége implementációtól függ. A Javahoz hasonlóan Pythonban is kezdeményezhető szemétgyűjtési ciklus a gc modul használatával. A 2.0-as verzióban a garbage collector már ciklikus referenciákat is kezel.

Szekvencia

Az utasításokat nem kell `;'-vel zárni, de a nyelv megengedi a használatukat. Az értékadás nem kifejezés, hanem utasítás.

Elágazás

Az elágazás szintaxisa:

if <kif> : <suite>
(elif < kif > : < suite >)*
[else : < suite >]

ahol a < suite >:

Nincsen többszörös elágazás.

Ciklus (for, while)

While:

while <kif> : <suite>
[else : <suite>]

A while a hagyományos elöltesztelős ciklus. Az else-ága akkor fut le, ha a ciklusfeltétel nem teljesül (tehát a ciklus elhagyása után mindig, kivéve ha break utasítással hagyjuk el a ciklust).

Kiugrási lehetőségek a ciklusból:

Nincs GOTO utasítás!

Nincs lehetőség egyszerre több ciklusból való kiugrásra.

For:

for <target_list> in <kif_list> : <suite>
[else : <suite>]

A <kif_list> kiértékelésekor egy sorozatot kell kapni. Ennek minden elemét hozzárendeli a <target_list>-hez és végrehajtja a <suite> részt. Ha a sorozat kiürült, akkor lefut az esetleges else ág.

Ha a for ciklust a hagyományos értelemben szeretnénk használni, akkor jön jól a range() függvény:

>>>range(10)
[0,1,2,3,4,5,6,7,8,9]
>>>range(5,10)
[5,6,7,8,9]
>>>range(0,10,3)
[0,3,6,9]
>>>range(-1,-10,-3)
[-1,-4,-7]

A magon belül módosíthatjuk a sorozatot, de ez csúnya hibákhoz vezethez, ugyanis egy számláló mutatja a következő feldolgozandó elemet, és egy beszúrás vagy törlés ebből a listából megkavarhatja a folyamatot. Megoldás: ideiglenes másolat készítése (pl. slice használata)

for x in a[:]:
    if x < 0 : a.remove(x)

A 2.0-es verzióban bevezették egy lista alapján  egy másik lista kiszámítására  a [ for in ] jelölést. Pl.:

print [i**2 for i in range(4)]        # [0, 1, 4, 9]

A 2.4-es Pythonban új beépített függvény a reversed(seq), ami egy sorozatot vár, és visszaad egy iterátort, ami a sorozat elemein megy végig fordított sorrendben. pl:

>>> for i in reversed(xrange(1,4)): ... print i ... 3 2 1

A reversed() jó, mert könnyebben olvasható, gyorsabb és lényegesen kevesebb memóriát használ, mint például range(1,4)[::-1] .

Figyelem! A reversed() függvény csak sorozatot kaphat bemeneti paraméterként. Ha iterátort szeretnénk megfordítani, akkor azt előbb a list() -tel listává kell konvertálni.

>>> input = open('/etc/passwd', 'r') >>> for line in reversed(list(input)): ... print line ... root:*:0:0:System Administrator:/var/root:/bin/tcsh ...

Egyszerű utasítások

Itt csak felsorolom őket, és az érdekesebbekről írok néhány szót:
assert, értékadás, pass, del, print, return, raise, break, continue, import, future, global, exec.

A legfontosabb talán az értékadás.

assignment_stmt: (target_list "=")+ expression_list
target_list:     target ("," target)* [","]
target:          identifier | "(" target_list ")" |
"[" target_list "]"
               | attributeref | subscription | slicing

Kollegám leírását szeretném bővíteni az értékadáson kívüli utasítások egy részével. Az évek alatt a Python szép fejlődést mutatott be. Most a 2.6 szabvány szerinti egyszerű utasítások egy részét mutatnám be nektek.

Az assert utasítás:

Az assert utasítás talán a legalkalmasabb arra, hogy hibát keressünk a programunkban.

assert_stmt ::= “assert” expression [”,” expression]

Az assert utasítás a következő programrészletnek felel meg:

if __debug__:
    if not expression: raise AssertionErrorm

Ha két paramétere van az assert utasításnak, akkor a következő módon működik:

if __debug__:
    if not expression1: raise AssertionError, expression2

Ezekben a programmegfeleltetésekben a __debug__ és az AssertionError beépített változók, amiket így is neveztek el. Az előző implementációkban a beépített __debug__ változó True értéke normál esetben, azonban ha fordításkor bekapcsoljuk az optimalizációs kapcsolót, akkor a beépített változó értéke False.(Parancssorban a -O kapcsoló) A jelenlegi kód generátor nem generál kódot az assert utasítás hatására, ha a fordítási időben optimalizálunk.

Megjegyezem, felesleges a forráskódba beleírni hibaüzenetet, mert ki lesz írva a veremtartalom részeként.

A __debug__ váltózónak értéket adni hiba. Az interpreter indulásakor a beépített változónak meghatározza az értékét, és hozzá is rendeli azt.

A pass utasítás:

A pass utasítás szintaktikája:

pass_stmt ::= “pass”

A pass utasítás egy null operáció, amikor kiértékelődik, semmi sem történik. Vagyis az üres utasításnak felel meg. Ez akkor hasznos, amikor az utasítás szintaktikailag elvárt, ott az utasítás számunkra felesleges.
Erre egy példa:

def f(arg):
    pass       # egy függvény, ami nem csinál semmit (még)
class C:
    pass       # egy osztály, aminek nincsenek metódusai (még)

A del utasítás:

Az utasítás szintaktikája a következő:

del_stmt ::= “del” target_list

A törlést az értékadáshoz nagyon hasonlóan, rekurzívan definiált utasítás. Ahelyett, hogy teljes részletességgel kifejteném az utasítást, inkább néhány tippet adok a használatára.

A törlésre szánt elemek listáját balról jobbra haladva rekurzív módon törli. Egy név törlésekor eltávolítja a névhez tartozó kötéseket a helyi vagy globális névtérből, attól függően, hogy a név előfordul-e global utasítással a jelenlegi kód blokkban vagy nem. Ha a név nincs semmihez sem kötve, akkor a NameError kivétel váltódik ki.

Egy név törlése a helyi névtérből hibás, ha az egy blokk szabad változójaként fordul elő.

Attribútum referencia, indexelések, vagy egy részlista törlése az elsődleges objektum műveleteivel eléggé bonyolult. Egy részlista törlése általában megfelel annak, hogy típushelyesen létrehoz helyette egy üres részt.

A print utasítás:

A print utasítás szintaktikája:

print_stmt ::= “print” ([expression (“,” expression)* [”,”]]
               |“>>” expression [(“,” expression)+ [”,”]])

A print kiértékel minden magkapott kifejezést, és az eredményül kapott objektumot a standard kimenetre írja ki.
Ha az objektum nem string akkor először a string-é konvertálás szabályait felhasználva az objektumot string-é konvertálja. Az eredményül kapott, vagy az eredeti string-et ezután írja ki. A szóközt minden objektum elé kiírja, kivéve ha a szóköz a sor elejére pozícionált.
Ez akkor történik, ha:

    1.    nincs karakter még a standard kimenetre írva
    2.    az utolsó kiírt karakter a ’\n’ vagy
    3.    az utolsó standard kimenetre történt kiírás nem a print utasítás miatt történt. (Néhány művelet üres string-eket ír ki a standard kimenetre.)

Megjegyzés: a fájl objektumként működő objektumok, de nem beépített fájl objektumok gyakran nem teljesen helyesen szimulálják a fájl objektumok viselkedésének ezt az aspektusát, tehát a legjobb ezeket az objektumokat nem használni. A ’\n’ karakter mindig a kiírás végére kerül kivéve akkor, ha a print utasítás vesszőre végződött. Ha az utasítás csak a print kulcsszót tartalmazza, vagyis ha nincs a print utasításnak paramétere, akkor csak a ’\n’ karaktert írja ki.

A standard kimenet egy fájl objektumként van definiálva, amit stdout-ra kereszteltek el, és beépítettek a sys modulba. Ha az objektum nem létezik, vagy ha az objektumnak nincs write() metódusa akkor a RuntimeError kivétel váltódik ki.

A print utasításnak van egy kiterjesztett változata. Ezt a formát néha úgy azonosítják, mint „print chevron”. Ebben a formában a >> utáni első kifejezés egy olyan ”fájl-szerű” objektumnak értékelődik ki, aminek van write() metódusa. Ezzel a kiterjesztett formával a többi kifejezést, az első paraméterben megadott fájl objektumba írhatjuk ki. Ha az első kifejezés None, akkor a kimenteti fájl objektum a sys.stdout lesz.

A return utasítás

return_stmt ::= “return” [expression_list]

A return utasítás talán az egyetlen olyan szintaktikai elem, amely függvények definíciójában mindig megtalálható, de osztálydefiníció utasítása nem lehet.

Ha egy kifejezés lista a paramétere, akkor az kiértékelődik, egyéb esetben None-t a helyettesítési érték.

A return utasítás hatása az, hogy a hívott függvényt elhagyja és egy kifejezés lista vagy a None érték lesz a függvény visszatérési értéke. Amikor a return utasítás a try blokkból az irányítást egy finally-ágnak adja át, akkor a finally-ág először lefut, és utána lép ki a függvényből.

Egy generátor függvényben a return utasítás nem kaphat kifejezés listát. Ebben a környezetben a paraméter nélküli return utasítás jelzi azt, hogy a generátor befejezte működését, ami kiváltja a StopIteration kivételt.

A yield utasítás:

yield_stmt ::= yield_expression

A yield utasítást csak akkor használjuk, amikor egy generátor függvényt definiálunk. Az uatsítást csak a függvény törzsében használjuk. A yield utasítás használata egy függvény definiálásánál elegendő ahhoz, hogy egy normál függvényből egy generátor függvényt készítsünk.

Amikor a generátor függvényt meghívjuk, akkor visszatér egy iterátorral, amit generátor iterátornak, vagy másképp generátornak nevezünk. A generátor next() hívásának hatására a függvény törzse híváskor hajtódik végre, és addig ismétlődik, amíg egy kivételt nem vált ki.

A yield utasítás végrehajtásakor, a generátor állapota fagyott lesz, és a kifejezés lista értékével tér vissza next() hívójához. A fagyott állapot azt jelenti, hogy az összes jelenlegi állapotot megtartja, beleértve a helyi változók jelenlegi értékeit, a mutatók helyeit, és a belső értékek vermét: elég információt ment el ahhoz, hogy következő next() híváskor ezeket elővegye, és a függvény pontosan lefuthasson, mintha a yield utasítás egy másik külső hívás lenne.

A Python 2.5-ös verziója óta a yield utasítás használata megengedett egy try-finally blokk try részében. Ha a generátor nem folytatódik, mielőtt befejeződne a try- blokk (úgy, hogy egy üres referencia az eredménye, vagy a szemétgyűjtögető összeszedi), a generátor iterátor close() metódusa fog meghívódni, beleértve minden még működésben lévő finally-ág lefutását.

Megjegyzés: A 2.2-es Pythonban a yield utasítás csak akkor volt megengedett, ha a generátor jellemző megengedett volt. A __future__ import utasítás használata engedélyezte ezt:

from __future__ import generators

A raise utasítás:

raise_stmt ::= “raise” [expression [”,” expression [”,” expression]]]

Ha nincs kifejezés a raise utasítás után, akkor a raise kiváltja jelenlegi lefutás alatti legutolsó aktív kivételt. Ha nem volt aktív kivétel, akkor a TypeError kivétel dobódik el, ami azt mutatja, hogy hiba történt (ha IDLE alatt futtatod, akkor a Queue.Empty kivétel váltódik ki).

Egyébként a raise kiértékelve a kifejezéseket három objektumot kap. Ha hiányzik a három kifejezés valamelyike akkor annak a helyére a kiértékelés során a None kerül. Az első két objektum a kivétel típusát és értékét határozza meg.

Ha az első objektum egy példány, akkor a kivétel típusa példány osztálya, és a példány lesz az érték, és a második objektum értéke None.

Ha az első objektum egy osztály, akkor az osztály válik a kivétel típusává. A második objektum általában a kivétel értékét határozza meg. Ha ez az osztály egy példánya, akkor példány válik a kivétel értékévé. Ha a második objektum egy pár vagy n-es, akkor úgy használjuk, mintha az osztály konstruktorának argumentum listája lenne. Ha a második objektum egy None érték, akkor egy üres argumentum listát használ, és minden más objektumot úgy kezel, mintha a konstruktor egy-egy argumentuma lenne. Vagyis készít egy példányt, amit a kivétel értékeként használ.

Ha a harmadik objektum is megtalálható, és nem None akkor egy traceback objektumnak kell lennie (ami a verem tartalmát reprezentáló objektum), és ez fogja leírni azt a helyet ahol a kivétel kiváltódott. Ha van harmadik objektum és nem traceback objektum, vagy nem None, akkor TypeError kivétel váltódik ki. A három kifejezéses formájú raise akkor hasznos, ha újra ki akarjuk váltani egy kivételt tisztán az except blokkban. De inkább ilyen esetben a kifejezés nélküli raise-t kellene alkalmazni.

A break utasítás:

break_stmt ::= “break”

A break utasítást szintaktikailag csak a for és while ciklusokban fordulhat elő, de nem lehet egy függvény vagy osztály definíciójában cikluson belüli utasítás. Befejezi a legközelebbi lezárható ciklust, elugrik az else blokkba, ha a ciklusnak van ilyen blokkja.

Ha a for ciklus break-kel fejeződik be, akkor a ciklusváltozó megtartja a befejezéskori értékét. Amikor a break átadja a vezérlést a try blokkon kívülre, és a try rendelkezik finally-ággal akkor a finally lefut, és csak utána adja át a vezérlést.

A continue utasítás

continue_stmt ::= “continue”

A continue utasítás szintaktikailag csak a for és while ciklusokban fordulhat elő, de nem lehet függvény vagy osztály definíciójában vagy finally-ág ciklusán belül. Az utasítást folytatja a következő ciklusmag lefutással a legközelebbi lezáratlan ciklust.

Amikor a continue átadja a vezérlést a try blokkon kívülre, és a try rendelkezik finally-ággal akkor a finally-ág lefut, mielőtt a következő ciklus lefutás következne.

Az import utasítás

import_stmt ::= “import” module [”as” name] ( “,” module [”as” name] )*
               | “from” relative_module “import” identifier [”as” name] ( “,” identifier [”as” name] )*
               | “from” relative_module “import” “(” identifier [”as” name] ( “,” identifier [”as” name] )* [”,”] “)”
               | “from” module “import” “*”
module ::= (identifier “.”)* identifier
relative_module ::= “.”* module
                    | “.”+ name ::= identifier

Az import utasítást két lépésben hajtja végre a fordító:

     1.    megkeresi a modult és inicializálja, ha szükséges
     2.    definiálja a nevét vagy neveit a helyi névtérnek (abban a környezetében, ahol az import utasítás található)

Az első formában (ahol nincs from) ezeket a lépéseket ismételjük minden azonosítóra a listában. Abban a formában ahol van from is ott végrehajtjuk az 1. lépést egyszer és a 2. lépést ismételjük.

Ebben a környezetben inicializálni a beépített vagy külső modulokat azt jelenti, hogy meghívunk, egy inicializáló függvényt, ami inicializálja a modulokat; a Python kód modulinicializálása azt jelenti, hogy végrehajtjuk a modul törzsét. A rendszer kezel egy modulokat tartalmazó táblázatot, ami már inicializálva van, vagy inicializálják és a modulok neveivel indexelnek. Ez a tábla sys.modules-ként érhető el. Amikor a modul nevét megtalálja a táblában az 1. lépést tudtuk le. Ha nem, akkor kezdődik a modul definíció keresése. Amikor a modult megtalálta, futtatja a modult. A modul keresésének és futtatásának folyamata implementáció és platformfüggő. Általában beépített modulokat keresi meg a megkapott név alapján, és csak ezután keresi meg a helyének a listáját, amit a sys.path-ként kapunk meg. Ha megvannak a beépített modulok, akkor a beépített inicializáló kód fut le, és ezzel az 1. lépést be is fejeztük. Ha nincs meg a fájl, amit importálni szeretnénk akkor az ImportError kivétel váltódik ki. Ha a fájlt megtaláljuk, elemeztük, kapunk egy futatható kód blokkot. Ha szintaktikai hiba van a benne, akkor a SyntaxError kivételt dobja el. Egyéb esetben, ha üres modulé a kapott név, akkor készít és beszúr egy bejegyzést a modultáblába, és ezután a kódblokk futtatható a modul környezetében. Ha kivétel történik a végrehajtás alatt, akkor az 1. lépés befejeződik.

Amikor az 1. lépés befejeződött kivételek kiváltása nélkül, akkor kezdődhet el a 2. lépés.

Az első import utasítás formában a modul neve a modul objektumait hozzáköti a helyi névtérhez, és utána következik a következő azonosító, ha van. Ha a modul nevet követi az as kulcsszó, akkor a kulcsszó utáni kifejezés lesz a modul neve, amit az adott helyen használni fog helyette. A from-ot tartalmazó forma nem köti a modulok nevét: végigmegy az azonosítók listáján összehasonlítja mindegyiket a modulban találhatókkal, és a neveket összeköti azokkal az objektumokkal, amik a helyi névtérben megtalálhatóak. Ahogy az import első formájában láttuk, az importot egy alternatív lokális névvel helyettesíthetjük, amit nevezhetünk „helyi név”-nek is. Ha az azonosító nem található meg akkor az ImportError kivétel váltódik ki. Ha az azonosítók listája helyett egy *-ot írunk, akkor az összes a modulban található publikusan definiált név össze lesz kapcsolva a helyi névtér import utasításával. A modulban definiált publikus neveket meghatározathatjuk úgy, hogy megkeressük az összes olyan változót amely __all__ névre hallgat a modul névterében. Ha definiált, akkor string-ek egy szekvenciájának kell lennie, amit a definiáltnak vagy importáltnak nevez a modul. Minden esetben elvárt azoktól a nevektől, amik az __all__-t megkapták, hogy publikusak és létezőek legyenek. Ha az __all__ nem definiált, akkor a publikus nevek halmaza tartalmazni fogja az összes nevet, ami a modul névterében megtalálható és nem ’_’ karakterrel kezdődnek. Az __all__-nak tartalmaznia kellene az egész publikus API-t. Kerülendő az, hogy véletlenül olyan elemeket exportáljunk, ami nem része az API-nak (például az olyan library modulokat, amelyek már importálva és használva vannak egy modulon belül).

A from modul import * forma csak a modul hatáskörén belül fordulhat elő. Ha import *-ot használunk egy függvényen belül, és a függvény tartalmaz, vagy a függvény egy tartalmazó blokk szabad változókkal, akkor a fordító SyntaxError kivételt fogja eldobni. Hierarchikus modul nevek: amikor a modul nevek egy vagy több pontot tartalmaznak, akkor a modul keresési módja az előzőektől eltérően zajlik. Az azonosítók sorozatát az utolsó pont felé haladva csomagként kezeli, és az utolsó azonosítót fogja keresni a csomagon belül. A csomagok általában alkönyvtárak a sys.path könyvtáron belül, aminek egy fájlja az __init__.py. A beépített függvény, amit __import__()-tal láttak el azért, hogy támogassák azokat az olyan applikációkat, amelyek eldöntik, hogy mely modulokat kell dinamikusan futtatni. Lásd Built-in Functions a Python Library referenciában részletesebben.

A future utasítás

A future utasítás egy fordítási direktíva, amellyel egy különös modult össze lehet állítani miközben szintaxist vagy szemantikát használ, ami elérhető lesz a Python egy következő értelmezésében. A future utasítás azt segíti elő, hogy enyhítse az Python következő verzióiban bemutatásra kerülő nyelvi ellentmondásokat. Ez megengedi, hogy modulként használják, az új szolgáltatásokat, mielőtt a szolgáltatás értelmezése alapvető szolgáltatássá nem válik.

future_statement ::= “from” “__future__” “import” feature [”as” name] (“,” feature [”as” name])*
                     | “from” “__future__” “import” “(” feature [”as” name] (“,” feature [”as” name])* [”,”] “)”

feature ::= identifier
name ::= identifier

A future utasításnak a modul elején kell megjelennie. Csak a következő sorok lehetnek az utasítás előtt:

    •    a modul dokumentációs kommentje (ha van ilyen),
    •    megjegyzések,
    •    üres sorok, és
    •    másik future utasítás.

A következő jellemzőket ismeri fel a Python 2.5 változata szerint: abszolút import, osztás, generátor, nested_scopes és a with utasítás. A generátor és nested_scopes a 2.3-as Python verzióban redundánsak, mert azokat mindig lehetővé teszik.

A future utasítást fordítási időben ismeri fel, és a fordító is különlegesen kezeli: az alap kódösszeállítást változásait gyakran különböző generált kódokkal implementálja. Lehet olyan eset is, hogy egy új szolgáltatás tartalmaz egy új, az előzőekkel nem egyező szintaktikai elemet (például egy új foglalt szó). Ebben az esetben a fordítónak a modult másképp kell értelmeznie. Ilyen döntéseket nem hagyhatja a fordító a futási időre.

Bármely adott értelmezés esetén a fordító tudja, hogy melyik szolgáltatás neve volt definiálva. Ha a future utasítás olyan szolgáltatást tartalmaz, ami nem volt ismert számára akkor fordítási időben keletkezett hibát fog kiváltani.

A közvetlen futási idejű szemantika akárcsak bármilyen import utasításnál: van egy általános __future __ modul, amit később fejtettek ki, és ha ezt szokásos módon importálják, akkor a future utasítás lesz végrehajtva.

Az érdekes futási idő szemantika egy speciális szolgáltatástól függ, amit a future utasítás engedélyez.

Megjegyzem, hogy nincs semmi különleges az utasításban:

import __future__ [as name]

Az előző nem egy future utasítás; ez egy szokásos import utasítás, különleges szemantikai vagy szintaktikai megszorítások nélkül.

Egy exec utasítás vagy a beépített compile() és execfile() hívások fordítják a kódot, ami egy olyan M modulé, amelyben megtalálható a future utasítás, alapértelmezésben használni fogja az új szintaktikát vagy szemantikát, amit összekapcsol a future utasítással. Ezzel tudja a compile() opcionális argumentumait kezelni a fordító a Python 2.2-es verziója óta.

A future utasítást egy parancssori fordítónak begépelve, életbe lép a többi interpreter session is.

Ha az interpretert –i kapcsolóval kezded, akkor végrehajtásra továbbadja a kapott script nevet, és ha a szkript tartalmaz future utasítást az lesz érvényben az interaktív session indulásakor a szkript végrehajtása után.

Kimaradt még néhány egyszerű utasítás, ami meghaladta ezen írás kereteit. Ezeket meghagyom az utókor számára.