This page is only available in Lithuanian
Darbas su gijomis
Jei reikia ką nors padaryti fone, naudokite threading modulį.
import threading import time import sys def some_function(): for i in range(3) time.sleep(1) print "1" thread = threading.Thread(target=some_function) thread.start() sys.exit()
Pagrindinė programos gija paleidžia foninę giją ir išeina. Gina laukia tris sekundes ir kas sekundę spausdina skaičių į ekraną. Pati programa baigia darbą, kai baigia darbą visos gijos (išskyrus „demoniškąsias“). Jei norite, kad gija netrukdytų programos darbo pabaigai, prieš ją paleisdami pasakykite apie tai:
... thread.setDaemon(True) thread.start() ...
Jei nenorite perdavinėti funkcijų Thread klasei, galite apsirašyti savo klasę:
import time import threading class MyThread(threading.Thread): def __init__(self): threading.Thread.__init__(self) self.setDaemon(True) def run(self): for i in range(3) time.sleep(1) print "1" thread = MyThread() thread.start() time.sleep(2)
Gijos metodas join
leidžia vienai gijai palaukti, kol kita gija
baigs darbą.
Sudėtingesnis pavyzdys: paleiskime procesą ir perskaitykime jo standartinį išvedimą (stdout) bei standartinį klaidų kanalą (stderr) atskirose gijose. Jei bandytume tai daryti vienoje gijoje, iškiltų rizika užsiblokuoti (deadlock), jei užsipildytų ne tas operacinės sistemos buferis.
import os import threading class Reader(threading.Thread): def __init__(self, pipe): threading.Thread.__init__(self) self.pipe = pipe self.start() def run(self): self.data = self.pipe.read() self.pipe.close() def run_program(command_line): """Rus a program and return (standard_output, standard_error).""" child_stdin, child_stdout, child_stderr = os.popen3(command_line) child_stdin.close() reader = Reader(child_stderr) stdout_data = child_stdout.read() child_stdout.close() reader.join() stderr_data = reader.data return (stdout_data, stderr_data)
Koordinacija tarp gijų
Modulyje threading yra keli tradiciniai primityvai kontroliuoti, kad gijos neužliptų viena kitai ant kojų -- Lock, RLock (recursive lock), Semaphore, Event, Condition objektai. Dažniausiai gera idėja yra jų vengti ir pasinaudoti aukštesnio lygio Queue moduliu ir perdavinėti specializuotas užduotis gijoms-darbininkėms.
Parašyti programą be klaidų yra labai sudėtinga, jei naudojamos gijos, tad geriau jų vengti.
Efektyvumas
Pythonas negali labai efektyviai išnaudoti daugelio gijų, nes Pythono interpretatorius naudoja vieną globalų užraktą (lock). Vieno procesoriaus sistemose tai nesvarbu, bet daugiaprocesorinėse sistemose nesitikėkite pagreitėjimo, jei gijos vykdo tik pliką Python kodą.
Šis apribojimas negalioja išoriniams moduliams, rašytiems C kalba, arba darbui su operacine sistema (failų skaitymui, tinklo paketų laukimui ir t.t.). Šioms užduotims gijos labai tinka.
Iteratoriai
Jūs jau žinote, kad Pythonas leidžia prasukti for
ciklą
per visas failo eilutes:
f = file('filename.txt') # arba open('filename.txt') -- tai tas pats for line in f: print line
Kaip tai veikia? Kaip rašyti savo klases, per kurias galima būtų iteruoti?
Python leidžia iteruoti per visus objektus, kurie turi metodą
__iter__
(bei objektus, turinčius metodą __getitem__
,
bet tai specialus ir siauras atvejis). Šis metodas turi grąžinti
iteratorių -- objektą, kuris turi funkciją next
ir
kurio __iter__
metodas grąžina jį patį.
class AddressBook: def __init__(self): self._data = [('Jonas', 'jonas@example.com'), ('Petras', 'petras@example.org'), ('Maryte', 'maryte@example.net')] def __iter__(self): return AddressBookIterator(self) class AddressBookIterator: def __init__(self, abook): self.abook = abook self.index = 0 def next(self): if self.index >= len(self.abook._data): raise StopIteration item = self.abook._data[self.index] self.index += 1 return item
Funkcija next
paeiliui grąžina sekos elementus, o kai jie
pasibaigia, iškelia StopIteration išskirtinę situaciją.
Kam reikia atskiros klasės? Ogi tam, kad galima būtų per tą pačią adresų knygelę iteruoti lygiagrečiai.
Generatoriai
Rankutėmis iteratorius rašinėti nepatogu. Laimei, Pythonas turi generatorius. Generatorius -- tai funkcija, grąžinanti iteratorių:
class AddressBook: def __init__(self): self._data = [('Jonas', 'jonas@example.com'), ('Petras', 'petras@example.org'), ('Maryte', 'maryte@example.net')] def __iter__(self): for item in self._data: yield item
Generatorius nuo paprastos funkcijos (ar metodo) skiriasi tuo, kad jo kūne
naudojamas bazinis žodis yield
. Generatoriaus iškvietimas grąžina
iteratorių. Kviečiant to iteratoriaus next
metodą vykdomas
funkcijos kodas iki kito yield
iškvietimo ir grąžinama
yield
ui paduota reikšmė. Jei funkcija baigiasi nepasiekusi
yield
, next
iškelia StopIteration.
Kitas pavyzdys:
def fibonacci(): """Generuoja Fibonačio skaičius.""" a, b = 1, 1 while True: yield a a, b = b, a+b fib = fibonacci() # generatoriaus iškvietimas duoda iteratorių for n in range(6): print n, fib.next() # galime rankomis kviesti funkciją next print fib2 = fibonacci() # galime lygiagrečiai iteruoti kelis kartus for n, f in zip(range(8), fib2): # naudoju zip, kad ištraukčiau pirmus 8 print n, f print # senasis iteratorius atsimena, kur jis buvo print 6, fib.next()
Šiame pavyzdyje parodytas generatorius yra begalinis.
Standartinė Python funkcija enumerate
taip pat yra
generatorius. Ją galima būtų užrašyti taip:
def enumerate(seq): n = 0 for item in seq: yield n, item n += 1
Galima būtų parašyti ir funkciją, kuri ima sąrašą ir grąžina sąrašą, bet generatoriaus privalumas -- sutaupoma atminties, nėra ilgų tarpinių sąrašų, galima dirbti su begalinėmis sekomis.
Dar vienas standartinėje bibliotekoje esantis generatoriaus pavyzdys -- funkcija os.walk (palyginkite su os.path.walk).
Modulyje itertools yra neblogas rinkinukas įvairių iteratorių bei generatorių, kuriuos galima tarpusavyje kombinuoti.