2013-05-05 11 views
6

To pytanie sprawia, że ​​wyciągam włosy.W jaki sposób Generatory Python wiedzą, kto dzwoni?

jeśli robię:

def mygen(): 
    for i in range(100): 
     yield i 

i nazywają to od tysiąca wątków, w jaki sposób generator wie co wysłać następny dla każdego wątku? Za każdym razem, gdy to nazywam, generator zapisuje tabelę z licznikiem i referencją osoby dzwoniącej lub coś w tym stylu?

To dziwne.

Proszę, wyjaśnij mi zdanie na ten temat.

Odpowiedz

6

mygen nie musi niczego pamiętać. Każde wywołanie do mygen() zwraca niezależną iterację. Z drugiej strony, iterables mają stan: Za każdym razem, gdy wywoływana jest next(), przeskakuje ono do właściwego miejsca w kodzie generatora - po napotkaniu yield sterowanie jest przekazywane z powrotem do wywołującego. Rzeczywista implementacja jest raczej nieporządna, ale w zasadzie można sobie wyobrazić, że taki iterator przechowuje zmienne lokalne, kod bajtowy i bieżącą pozycję w kodzie bajtowym (wskaźnik instrukcji a.k.a.). W wątkach nie ma nic szczególnego.

+0

Taa, nici służyły tylko do zilustrowania problemu. Biorąc pod uwagę, że generatory mogą dawać zły wygląd współbieżności (lub coś więcej czarnej magii niż to), aby pytać początkujących. –

+0

@PatrickBassut: Możesz symulować [coroutines] (https://en.wikipedia.org/wiki/Coroutine) z nimi, a dzięki coroutines możesz tworzyć [zielone wątki] (https://en.wikipedia.org/wiki/Green_threads). – icktoofay

2

Funkcja taka jak ta, po wywołaniu, zwróci obiekt generatora. Jeśli masz osobne wątki wywołujące next() na tym samym obiekcie generatora, będą się one wzajemnie zakłócać. Oznacza to, że 5 wątków wywołujących next() 10 razy każdy otrzyma 50 różnych wydajności.

Jeśli dwa wątki tworzą generator, wywołując mygen() w wątku, będą miały oddzielne obiekty generatora.

Generator jest obiektem, a jego stan będzie przechowywany w pamięci, dlatego dwa wątki, z których każdy tworzy mygen(), będą odnosić się do oddzielnych obiektów. Nie będzie różnił się od dwóch wątków tworzących obiekt od class, każdy będzie miał inny obiekt, mimo że klasa jest taka sama.

jeśli przychodzisz do tego z tła C, jest to nie to samo, co funkcja ze zmiennymi static. Stan jest utrzymywany w obiekcie, a nie statycznie w zmiennych zawartych w funkcji.

+0

Nie odpowiedziałeś na moje pytanie. Nie chcę wiedzieć, co się stanie, ale jak to się stanie. Czy generator zachowuje pewną wartość w pamięci, gdy wątek tworzy generator? –

+4

@PatrickBassut obiekt generatora ma stan tak jak każdy inny obiekt, więc tak, jego stan zostanie zapisany w pamięci. –

1

To może być bardziej przejrzyste, jeśli spojrzeć na to w ten sposób. Zamiast:

for i in mygen(): 
    . . . 

użytku:

gen_obj = mygen() 
for i in gen_obj: 
    . . . 

wtedy widać, że mygen() jest wywoływana tylko raz, i to tworzy nowy obiekt, a to, że przedmiot, który pobiera powtórzyć. Można utworzyć dwie sekwencje w tym samym wątku, jeśli chcesz:

gen1 = mygen() 
gen2 = mygen() 
print(gen1.__next__(), gen2.__next__(), gen1.__next__(), gen2.__next__()) 

Zostanie wydrukowany 0, 0, 1, 1.

Można dostęp do tego samego iterator z dwóch wątków, jeśli chcesz, po prostu zapisać obiekt generatora w globalnym:

global_gen = mygen() 

gwintu 1:

for i in global_gen: 
    . . . 

gwintu 2:

for i in global_gen: 
    . . . 

Prawdopodobnie spowodowałoby to wszelkiego rodzaju spustoszenie. :-)

+0

Generatory są trochę dziwne. Możesz przypisać je do zmiennych, ale w momencie, w którym faktycznie je używasz, zaczyna generować wartości w pewien sposób "na żądanie". Jeśli przypisałeś lokalizację pamięci danej funkcji, byłoby to całkowicie zrozumiałe. Ale o ile wiem, nie robisz tego tam (tak naprawdę wywołujesz funkcję w gen_obj = mygen()). Łał! –

+1

Po przejściu do następnego poziomu poważnego Python-Fu, można przekazywać wskaźniki do powiązanych __next __() metod jako wywołania GUI. :-) –

+1

W większości przypadków, instrukcja "def" tworzy pojedynczy obiekt funkcyjny z twojego kodu, który jest wykonywany, gdy wywołujesz funkcję po nazwie. Jeśli kod zawiera "yield", def tworzy dwa * obiekty funkcji: ten, który zostanie wywołany, gdy wywołasz nazwę funkcji, nie ma wcale twojego kodu; po prostu zwraca obiekt generatora. Obiekt funkcji, który tworzy z twojego kodu, jest wywoływany przez atrybut __next__ tego obiektu generatora, a ta funkcja zachowuje swój stan w obiekcie generatora i wie, jak go zapisać i przywrócić między wywołaniami. –