Language
Lt :: En

Šeštoji paskaita (2004-10-11)

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 yieldui 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.


Valid XHTML 1.1! Valid CSS! Paskutiniai pakeitimai: 2012-01-08