PHP, C #, Python i prawdopodobnie kilka innych języków ma yield
słowo kluczowe, które służy do tworzenia funkcji generatora.
W PHP: http://php.net/manual/en/language.generators.syntax.php
W języku Python: https://www.pythoncentral.io/python-generators-and-yield-keyword/
W języku C #: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/ke words / yield
Obawiam się, że jako funkcja / funkcja językowa yield
łamie niektóre konwencje. Jednym z nich jest „pewność”. Jest to metoda, która zwraca inny wynik za każdym razem, gdy ją wywołujesz. Za pomocą zwykłej funkcji innej niż generator można ją wywołać, a jeśli otrzyma to samo wejście, zwróci to samo wyjście. Z wydajnością zwraca inną moc wyjściową, w zależności od jej stanu wewnętrznego. Dlatego jeśli losowo wywołujesz funkcję generującą, nie znając jej poprzedniego stanu, nie możesz oczekiwać, że zwróci określony wynik.
Jak taka funkcja pasuje do paradygmatu językowego? Czy to faktycznie łamie jakieś konwencje? Czy warto mieć tę funkcję i korzystać z niej? (aby podać przykład tego, co dobre, a co złe, goto
było kiedyś cechą wielu języków i nadal jest, ale jest uważane za szkodliwe i jako takie zostało wyeliminowane z niektórych języków, takich jak Java). Czy kompilatory / tłumacze języka programowania muszą zerwać z konwencjami, aby zaimplementować taką funkcję, na przykład, czy język musi implementować wielowątkowość, aby ta funkcja działała, czy może to być zrobione bez technologii wątkowania?
yield
jest zasadniczo silnikiem stanu. Nie ma za każdym razem zwracać tego samego wyniku. Co to będzie zrobić z absolutną pewnością jest za każdym razem jest ona wywoływana powrócić następny element w przeliczalny. Wątki nie są wymagane; potrzebujesz zamknięcia (mniej więcej), aby utrzymać obecny stan.yield
słowa kluczowego takiego jak Python. Ma metodę statycznąstd::this_thread::yield()
, ale to nie jest słowo kluczowe. Więcthis_thread
wstawiłby do niego prawie każde wywołanie, dzięki czemu byłoby dość oczywiste, że jest to funkcja biblioteczna tylko do generowania wątków, a nie funkcja językowa dotycząca generowania przepływu kontroli w ogóle.Odpowiedzi:
Najpierw zastrzeżenia - C # to język, który znam najlepiej, i chociaż jego język
yield
wydaje się bardzo podobny do innych językówyield
, mogą istnieć subtelne różnice, których nie znam.Bzdury. Czy naprawdę oczekujesz
Random.Next
lubConsole.ReadLine
zwracasz ten sam wynik za każdym razem, gdy do nich zadzwonisz? Co powiesz na połączenia Rest? Poświadczenie? Odebrać przedmiot z kolekcji? Istnieją różnego rodzaju (dobre, użyteczne) funkcje, które są nieczyste.Tak,
yield
gra bardzo źletry/catch/finally
i jest niedozwolony ( https://blogs.msdn.microsoft.com/ericlippert/2009/07/16/iterator-blocks-part-three-why-no-yield-in-finally/ dla więcej informacji).Z pewnością warto mieć tę funkcję. Rzeczy takie jak LINQ w C # są naprawdę fajne - leniwe ocenianie kolekcji zapewnia dużą korzyść w zakresie wydajności i
yield
pozwala na wykonanie tego rodzaju czynności w ułamku kodu z ułamkiem błędów, które zrobiłby iterator ręcznie walcowany.To powiedziawszy, nie ma mnóstwo zastosowań
yield
poza przetwarzaniem kolekcji w stylu LINQ. Użyłem go do przetwarzania sprawdzania poprawności, generowania harmonogramu, randomizacji i kilku innych rzeczy, ale spodziewam się, że większość programistów nigdy go nie wykorzystała (lub niewłaściwie go wykorzystała).Nie dokładnie. Kompilator generuje iterator maszyny stanów, który śledzi, gdzie się zatrzymał, aby mógł się tam ponownie uruchomić przy następnym wywołaniu. Proces generowania kodu działa podobnie do stylu przekazywania kontynuacji, w którym kod po
yield
jest wciągany do własnego bloku (a jeśli ma jakiśyield
s, inny podblok itd.). Jest to dobrze znane podejście stosowane częściej w programowaniu funkcjonalnym, a także pojawia się w kompilacji async / czekaj w języku C #.Nie jest potrzebne wątkowanie, ale wymaga ono innego podejścia do generowania kodu w większości kompilatorów i ma pewien konflikt z innymi funkcjami językowymi.
Podsumowując,
yield
jest to funkcja o stosunkowo niskim wpływie, która naprawdę pomaga przy określonym podziale problemów.źródło
yield
słowo kluczowe jest podobne do coroutines, tak, czy coś innego? Jeśli tak, chciałbym mieć jeden w C! Mogę wymyślić przynajmniej kilka porządnych części kodu, które byłyby o wiele łatwiejsze do napisania przy użyciu takiej funkcji językowej.async
/await
do języka ktoś go zaimplementowałyield
.Chciałbym odpowiedzieć na to pytanie z perspektywy Pythona, zdecydowanie , to świetny pomysł .
Zacznę od omówienia kilku pytań i założeń w twoim pytaniu, a następnie wykażę wszechobecność generatorów i ich nieuzasadnioną przydatność w Pythonie później.
To nieprawda. Metody na obiektach można traktować jako same funkcje z własnym stanem wewnętrznym. W Pythonie, ponieważ wszystko jest obiektem, możesz faktycznie pobrać metodę z obiektu i ominąć tę metodę (która jest powiązana z obiektem, z którego pochodzi, więc pamięta swój stan).
Inne przykłady obejmują celowo losowe funkcje, a także metody wprowadzania danych, takie jak sieć, system plików i terminal.
Jeśli paradygmat języka obsługuje takie funkcje, jak funkcje pierwszej klasy, a generatory obsługują inne funkcje języka, takie jak protokół Iterable, to bez problemu się dopasowują.
Nie. Ponieważ jest on upieczony w języku, konwencje są zbudowane wokół i obejmują (lub wymagają!) Korzystanie z generatorów.
Podobnie jak w przypadku każdej innej funkcji, kompilator musi być po prostu zaprojektowany do obsługi tej funkcji. W przypadku Pythona funkcje są już obiektami ze stanem (takie jak domyślne argumenty i adnotacje funkcji).
Ciekawostka: domyślna implementacja Pythona w ogóle nie obsługuje wątków. Posiada globalną blokadę interpretera (GIL), więc nic nie działa równolegle, chyba że uruchomisz drugi proces, aby uruchomić inną instancję Pythona.
Uwaga: przykłady znajdują się w Pythonie 3
Ponad wydajność
Chociaż
yield
słowa kluczowego można użyć w dowolnej funkcji, aby zamienić go w generator, nie jest to jedyny sposób, aby go utworzyć. Python oferuje Generatory Expressions, potężny sposób na wyraźne wyrażenie generatora w kategoriach innego iterowalnego (w tym innych generatorów)Jak widać, składnia jest nie tylko czysta i czytelna, ale także wbudowane funkcje, takie jak
sum
generatory akceptacji.Z
Sprawdź propozycję rozszerzenia Python dla instrukcji With . Jest bardzo różny, niż można się spodziewać po stwierdzeniu With w innych językach. Przy niewielkiej pomocy ze standardowej biblioteki generatory Pythona działają pięknie jako menedżery kontekstów.
Oczywiście drukowanie rzeczy jest najbardziej nudną rzeczą, jaką możesz tutaj zrobić, ale pokazuje widoczne rezultaty. Bardziej interesujące opcje obejmują automatyczne zarządzanie zasobami (otwieranie i zamykanie plików / strumieni / połączeń sieciowych), blokowanie współbieżności, tymczasowe zawijanie lub zastępowanie funkcji oraz dekompresowanie, a następnie ponowne kompresowanie danych. Jeśli wywoływanie funkcji jest jak wstrzykiwanie kodu do kodu, wówczas z instrukcjami jest jak zawijanie części kodu w inny kod. Niezależnie od tego, jak go używasz, jest to solidny przykład łatwego przechwytywania struktury języka. Generatory oparte na wydajności nie są jedynym sposobem tworzenia menedżerów kontekstu, ale z pewnością są wygodne.
Częściowe wyczerpanie
Pętle w Pythonie działają w ciekawy sposób. Mają następujący format:
Po pierwsze, wywołane
<iterable>
przeze mnie wyrażenie jest oceniane w celu uzyskania iterowalnego obiektu. Po drugie, iterable go wywołało__iter__
, a wynikowy iterator jest przechowywany za kulisami. Następnie__next__
wywoływany jest iterator w celu uzyskania wartości powiązania z wprowadzoną nazwą<name>
. Ten krok powtarza się, aż wezwanie do__next__
rzutu aStopIteration
. Wyjątek jest połykany przez pętlę for i od tego momentu wykonywanie jest kontynuowane.Wracając do generatorów: gdy wywołujesz
__iter__
generator, po prostu sam się zwraca.Oznacza to, że możesz oddzielić iterację od czegoś od tego, co chcesz z tym zrobić, i zmienić to zachowanie w połowie. Poniżej zauważ, jak ten sam generator jest używany w dwóch pętlach, a w drugim zaczyna działać od miejsca, w którym przerwał od pierwszego.
Leniwa ocena
Jedną z wad generatorów w porównaniu z listami jest jedyna rzecz, do której można uzyskać dostęp w generatorze, to następna rzecz, która z niego wychodzi. Nie możesz cofnąć się i jak w przypadku poprzedniego wyniku lub przejść do następnego bez przechodzenia przez wyniki pośrednie. Zaletą tego jest to, że generator nie może zająć prawie żadnej pamięci w porównaniu do swojej równoważnej listy.
Generatory mogą być również leniwie powiązane.
Pierwszy, drugi i trzeci wiersz po prostu definiują generator, ale nie wykonują żadnej prawdziwej pracy. Gdy wywoływany jest ostatni wiersz, suma prosi o kolumnę numeryczną o wartość, kolumna numeryczna potrzebuje wartości z ostatniej kolumny, ostatnia kolumna prosi o wartość z pliku dziennika, który następnie odczytuje wiersz z pliku. Stos ten rozwija się, dopóki suma nie otrzyma pierwszej liczby całkowitej. Następnie proces powtórzy się dla drugiej linii. W tym momencie suma ma dwie liczby całkowite i dodaje je do siebie. Zauważ, że trzeci wiersz nie został jeszcze odczytany z pliku. Sum następnie żąda wartości z kolumny liczbowej (całkowicie nieświadomy reszty łańcucha) i dodaje je, aż kolumna liczbowa się wyczerpie.
Naprawdę interesującą częścią jest to, że wiersze są czytane, konsumowane i odrzucane indywidualnie. W żadnym momencie cały plik w pamięci nie jest naraz. Co się stanie, jeśli ten plik dziennika to, powiedzmy, terabajt? Po prostu działa, ponieważ czyta tylko jedną linię na raz.
Wniosek
To nie jest pełny przegląd wszystkich zastosowań generatorów w Pythonie. W szczególności pominąłem nieskończone generatory, maszyny stanów, przekazując wartości z powrotem i ich związek z korupinami.
Uważam, że wystarczy wykazać, że możesz mieć generatory jako czysto zintegrowaną, przydatną funkcję językową.
źródło
Jeśli jesteś przyzwyczajony do klasycznych języków OOP, generatorów i
yield
może wydawać się denerwujący, ponieważ stan zmienny jest przechwytywany na poziomie funkcji, a nie na poziomie obiektu.Jednak kwestia „pewności” to czerwony śledź. Zwykle nazywa się to przezroczystością referencyjną i zasadniczo oznacza, że funkcja zawsze zwraca ten sam wynik dla tych samych argumentów. Po osiągnięciu stanu zmiennego tracisz przejrzystość referencyjną. W OOP obiekty często mają stan zmienny, co oznacza, że wynik wywołania metody nie zależy tylko od argumentów, ale także od stanu wewnętrznego obiektu.
Pytanie brzmi, gdzie uchwycić stan zmienny. W klasycznym OOP stan mutable istnieje na poziomie obiektu. Ale jeśli język obsługuje zamknięcia, możesz mieć stan zmienny na poziomie funkcji. Na przykład w JavaScript:
Krótko mówiąc,
yield
jest naturalny w języku, który obsługuje zamykanie, ale byłby nie na miejscu w języku takim jak starsza wersja Java, gdzie stan zmienny istnieje tylko na poziomie obiektu.źródło
Moim zdaniem nie jest to dobra funkcja. Jest to zła cecha, przede wszystkim dlatego, że trzeba jej bardzo ostrożnie uczyć, a wszyscy źle ją uczą. Ludzie używają słowa „generator”, równoważąc funkcję generatora z obiektem generatora. Pytanie brzmi: kto lub co faktycznie wykonuje plony?
To nie jest tylko moja opinia. Nawet Guido w biuletynie PEP, w którym o tym rządzi, przyznaje, że funkcja generatora nie jest generatorem, ale „fabryką generatorów”.
To trochę ważne, nie sądzisz? Ale czytając 99% dokumentacji, można odnieść wrażenie, że funkcja generatora jest faktycznym generatorem i zwykle ignorują fakt, że potrzebujesz również obiektu generatora.
Guido rozważał zamianę „def” na „gen” dla tych funkcji i powiedział Nie. Ale twierdzę, że i tak by to nie wystarczyło. Naprawdę powinno to być:
źródło