Pytanie Jak len (generator ()) [duplicate]


To pytanie już zawiera odpowiedź:

Generatory Pythona są bardzo przydatne. Mają przewagę nad funkcjami, które zwracają listy. Jednak możesz len(list_returning_function()). Czy istnieje sposób len(generator_function())?

AKTUALIZACJA:
Oczywiście len(list(generator_function())) pracowałbym.....
Próbuję użyć generatora, który utworzyłem w nowym generatorze, który tworzę. W ramach obliczeń w nowym generatorze musi znać długość starego. Chciałbym jednak zachować oba z nich z tymi samymi właściwościami, co generator, w szczególności - nie przechowywać całej listy w pamięci, ponieważ może to być bardzo długie.

UPDATE 2:
Załóż generator wie jest to docelowa długość nawet od pierwszego kroku. Ponadto, nie ma powodu, aby utrzymać len() składnia. Przykład - jeśli funkcje w Pythonie są obiektami, czy nie mógłbym przypisać długości do zmiennej tego obiektu, która byłaby dostępna dla nowego generatora?


102
2017-09-18 10:16


pochodzenie


Masz na myśli unikanie tego, co oczywiste len(list(generator_function())) ? - 6502
Jeśli ty naprawdę Potrzebuję długości, generatory to niewłaściwe podejście. Ale często nie potrzebujesz tego. itertools potrafi zdziałać cuda, a innym razem długość wyjściową można dokładnie przewidzieć na podstawie danych wejściowych.
tak, mam na myśli unikanie oczywistych len(list(generator_function())) - Jonathan
Wyjaśnij dlaczego "w ramach obliczeń w nowym generatorze musi znać długość starego", to jest zło ​​i prawdopodobnie możemy to wyeliminować. itertools ma do tego mnóstwo konstrukcji. - smci
na przykład stary generator wytwarza pewną funkcję losową, a nowy generator wykonuje obliczenia, które zależą od bieżącego czasu i długości wektora. Nie rozumiem, jak ten użytek byłby zły. Zaufaj mi, że mam taką potrzebę i że jest to rozsądne pod względem architektonicznym w moim systemie. - Jonathan


Odpowiedzi:


Generatory nie mają długości, w końcu nie są kolekcjami.

Generatory są działa ze stanem wewnętrznym (i fantazyjna składnia). Możesz je wielokrotnie wywoływać, aby uzyskać ciąg wartości, dzięki czemu możesz ich użyć w pętli. Ale nie zawierają żadnych elementów, więc zapytanie o długość generatora przypomina zapytanie o długość funkcji.

jeśli funkcje w Pythonie są obiektami, nie mogę przypisać długości do a   zmienna tego obiektu, która byłaby dostępna dla nowego generatora?

Funkcje są obiektami, ale nie można przypisać do nich nowych atrybutów. Powodem jest prawdopodobnie utrzymanie tak podstawowego obiektu jak najbardziej efektywnego.

Możesz jednak po prostu wrócić (generator, length) pary z twoich funkcji lub owinąć generator w prosty obiekt taki jak ten:

class GeneratorLen(object):
    def __init__(self, gen, length):
        self.gen = gen
        self.length = length

    def __len__(self): 
        return self.length

    def __iter__(self):
        return self.gen

g = some_generator()
h = GeneratorLen(g, 1)
print len(h), list(h)

50
2017-09-18 10:35



Nie, to dość oczywiste, że to jest znaczące, aby mówić o długości wielu generatorów, ponieważ wiele generatorów zwraca skończoną liczbę elementów. Twój argument, że jest bez znaczenia, dowodzi zbyt wiele; jeśli ją zaakceptujemy, to za pomocą tej samej logiki nie ma sensu przekształcać wyjścia generatora na listę ... i jeszcze list(generator()) działa i jest wbudowany w język. - Mark Amery
@MarkAmery Część tego, co sprawia, że ​​generatory są tak elastyczne, że one nie rób tego musi dostarczyć __len__ metoda (lub podobna do Java hasNext i removelub ...). Co by to było itertools.count powrót? W Pythonie nie ma liczby całkowitej "nieskończoności". A co z generatorami nie wiem kiedy skończą? Aby napisać skuteczny __len__ metoda dla Hipotezy Goldbacha generator, najpierw musisz odpowiedzieć na jedno z największych otwartych pytań w matematyce. - Kevin J. Chase
@ Kevin J. Mogę równie dobrze zapytać "Co by to było sum(itertools.count()) powrót?", jeszcze sum może brać generatory. Istnieje oczywisty możliwy sposób wdrożenia len() na arbitralnych iterabelach: niech iteruje i policzy ile jest elementów. Twierdzę, że byłoby to nieprzydatne (znając długość zużytego generatora, którego elementy odrzucono, nie będzie użyteczne w większości przypadków), ale fakt, że będzie on w pętli na nieskończonych generatorach otwarcie nie jest argumentem, który wydaje ci się, że tak jest sum() i list()mają takie samo zachowanie. - Mark Amery
... i "zwrócić długość zużytego generatora, którego elementy zostały odrzucone" jest czasem przydatne: jako najbardziej zewnętrzny użytkownik łańcucha generatora, chcesz tylko zgłosić liczbę elementów, na których działają generatory wewnętrzne (np. ile zapisów bazy danych zostało zapisanych). - Jonathan Hartley
Odpowiedź @Rocha Oxymorona jest dokładnie tym, czego szukałem. - Jonathan Hartley


Konwersja do list to sugerowane w innych odpowiedziach jest najlepszym sposobem, jeśli nadal chcesz przetwarzać elementy generatora, ale ma jedną wadę: wykorzystuje pamięć O (n). Możesz liczyć elementy w generatorze, nie korzystając z dużej ilości pamięci za pomocą:

sum(1 for x in generator)

Oczywiście należy pamiętać, że może to być wolniejsze niż len(list(generator)) we wspólnych implementacjach Pythona, a jeśli generatory są wystarczająco długie, aby skomplikować pamięć, operacja zajmie trochę czasu. Mimo to osobiście wolę to rozwiązanie, ponieważ opisuje ono, co chcę uzyskać, i nie daje mi niczego dodatkowego, co nie jest wymagane (np. Lista wszystkich elementów).

Posłuchaj także porady delnana: Jeśli odrzucasz wyjście generatora, istnieje duże prawdopodobieństwo, że istnieje sposób na obliczenie liczby elementów bez uruchamiania go lub zliczanie ich w inny sposób.


157
2017-09-18 10:50



To jest najlepsza odpowiedź imo. Byłoby jednak nieco bardziej pythonic do zapisu: suma (1 dla _ w generatorze) - RussellStewart
Jako noob Pythona prawdopodobnie brakuje mi czegoś oczywistego, ale jaki jest sens używania tego podejścia, skoro nie można już używać generatora do generowania rzeczywistych wartości, ponieważ używałeś go już do zliczania wartości (i generatory są jednorazowe AFAIU)? - sschuberth
@sschuberth: Masz rację. Jeśli potrzebujesz zarówno długości, jak i wartości (i nie kontrolujesz pochodzenia generatora), najlepszym rozwiązaniem jest zamienić go na listę. - evertheylen
Po prostu pięknie. Jest to bardzo pragmatyczne i sprytne! - Emil Ingerslev
Znalazłem to w źródle scikit-learn :) github.com/scikit-learn/scikit-learn/blob/a24c8b46/sklearn/... - victor


Załóżmy, że mamy generator:

def gen():
    for i in range(10):
        yield i

Możemy owinąć generator, wraz ze znaną długością, w obiekt:

import itertools
class LenGen(object):
    def __init__(self,gen,length):
        self.gen=gen
        self.length=length
    def __call__(self):
        return itertools.islice(self.gen(),self.length)
    def __len__(self):
        return self.length

lgen=LenGen(gen,10)

Przypadki LenGen same generatory, ponieważ wywoływanie ich zwraca iterator.

Teraz możemy użyć lgen generator w miejsce geni dostęp len(lgen) także:

def new_gen():
    for i in lgen():
        yield float(i)/len(lgen)

for i in new_gen():
    print(i)

16
2017-09-18 10:36



rozwiązałeś to, ale z klasą. Ja ... nie spodziewałem się tego :) Czy jest jakaś korzyść w próbie zachowania projektu jako funkcji? - Jonathan
@ Jonathan: Moją pierwszą próbą było dołączenie atrybutu do obiektu generatora, gen(). Jednak w przeciwieństwie do funkcji Python nie pozwala na dołączanie dodatkowych atrybutów do obiektów generatora. Z powodu tego ograniczenia poszedłem z klasą. - unutbu


Możesz użyć len(list(generator_function()). Jednak to pochłania generator, ale tylko w ten sposób można dowiedzieć się, ile elementów jest generowanych. Więc możesz chcieć zapisać listę gdzieś, jeśli chcesz również użyć przedmiotów.

a = list(generator_function())
print(len(a))
print(a[0])

9
2017-09-18 10:18





Możesz len(list(generator)) ale prawdopodobnie możesz zrobić coś bardziej efektywnego, jeśli naprawdę chcesz odrzucić wyniki.


6
2017-09-18 10:18





Możesz użyć send jako hack:

def counter():
    length = 10
    i = 0
    while i < length:
        val = (yield i)
        if val == 'length':
            yield length
        i += 1

it = counter()
print(it.next())
#0
print(it.next())
#1
print(it.send('length'))
#10
print(it.next())
#2
print(it.next())
#3

6
2017-10-12 22:12



Ten kod nie działa dla mnie (python 3.6). Jeśli zrobię it.next() dostaję AttributeError: 'generator' object has no attribute 'next'. next(it) działa. - bli


Możesz łączyć zalety generatorów z pewnością len(), tworząc własny obiekt iterowalny:

class MyIterable(object):
    def __init__(self, n):
        self.n = n

    def __len__(self):
        return self.n

    def __iter__(self):
        self._gen = self._generator()
        return self

    def _generator(self):
        # Put your generator code here
        i = 0
        while i < self.n:
            yield i
            i += 1

    def next(self):
        return next(self._gen)

mi = MyIterable(100)
print len(mi)
for i in mi:
    print i,

Jest to w zasadzie prosta implementacja xrange, który zwraca obiekt, który możesz wziąć, ale nie tworzy jawnej listy.


4
2017-09-18 12:16



rozwiązałeś to, ale dzięki class. Ja ... nie spodziewałem się tego :) Czy jest jakaś korzyść w próbie zachowania projektu jako funkcji? - Jonathan
Daje to mały błąd: tworzony iterator uruchamia się ponownie za każdym razem, gdy dzwonisz iter na tym lub na oryginalnej iteracji. Będzie miało mniej zaskakujące zachowanie, jeśli zmienisz nazwę _generator do __iter__ i usuń next. Twój iterator nie będzie miał długości, ale to nie jest problem, ponieważ iteracja będzie. (Kolejną poprawką jest zadzwonić self._generator podczas __init__ i nie podczas __iter__.) - Rosh Oxymoron


Możesz użyć reduce.

W przypadku Python 3:

>>> import functools
>>> def gen():
...     yield 1
...     yield 2
...     yield 3
...
>>> functools.reduce(lambda x,y: x + 1, gen(), 0)

W Pythonie 2, reduce znajduje się w globalnej przestrzeni nazw, więc importowanie jest niepotrzebne.


4
2017-09-28 05:30