2013-02-13 14 views
12

Wiele iteratorów "funkcjonuje" w module __builtin__ jest w rzeczywistości implementowanych jako typy, nawet jeśli dokumentacja mówi o nich jako o "funkcjach". Weźmy na przykład enumerate. Dokumentacja mówi, że jest to odpowiednik:Dlaczego niektóre "funkcje" wbudowane w python faktycznie są typami?

def enumerate(sequence, start=0): 
    n = start 
    for elem in sequence: 
     yield n, elem 
     n += 1 

Co jest dokładnie takie, jak bym to wdrożył, oczywiście. Jednak przeprowadziłem następujący test z poprzednią definicją i otrzymałem:

>>> x = enumerate(range(10)) 
>>> x 
<generator object enumerate at 0x01ED9F08> 

Tego właśnie oczekuję. Jednak w przypadku korzystania z wersji __builtin__, otrzymuję to:

>>> x = enumerate(range(10)) 
>>> x 
<enumerate object at 0x01EE9EE0> 

Z tego wnoszę, że jest ona definiowana jako

class enumerate: 
    def __init__(self, sequence, start=0): 
     # .... 

    def __iter__(self): 
     # ... 

zamiast w formie standardowy pokazy dokumentacji. Teraz mogę zrozumieć, jak to działa, i jak jest to równoważne z standardową formą, co chcę wiedzieć, co jest powodem, aby to zrobić w ten sposób. Czy to jest bardziej wydajne w ten sposób? Czy ma to coś wspólnego z tymi funkcjami wdrażanymi w C (nie wiem, czy są, ale podejrzewam, że tak)?

Używam Python 2.7.2, na wypadek gdyby różnica była ważna.

Z góry dziękuję.

+1

Czy to jest problem? Funkcje i klasy są po prostu obiektami wywoływalnymi ... – JBernardo

+0

@JBernardo To nie jest problem w prawie wszystkich okolicznościach (a kiedy tak jest, prawdopodobnie powinieneś po prostu naprawić hack, który się psuje). Ale nadal jest interesująco. – delnan

+4

Nie, oczywiście, że nie. To tylko kwestia akademicka. Chcę poznać uzasadnienie ich wdrożenia, kiedy wdrażanie generatorów jest tak proste. Być może pozwoli mi to trochę wniknąć w kwestię: czy powinienem to robić w ten sposób dla własnych generatorów? –

Odpowiedz

10

Tak, ma to związek z tym, że wbudowane generalnie są realizowane w C. Bardzo często kod C wprowadza nowe typy zamiast zwykłych funkcji, jak w przypadku enumerate. Pisanie ich w C zapewnia lepszą kontrolę nad nimi, a często pewne ulepszenia wydajności, , a ponieważ nie ma prawdziwej wady, jest to naturalny wybór.

Weź pod uwagę, że do pisania równowartość:

def enumerate(sequence, start=0): 
    n = start 
    for elem in sequence: 
     yield n, elem 
     n += 1 

w C, to znaczy nowej instancji generatora, należy utworzyć obiekt kodu, który zawiera aktualne kodu bajtowego. Nie jest to niemożliwe, ale nie jest to łatwiejsze niż napisanie nowego typu, który po prostu implementuje __iter__ i __next__ wywoływanie C-API Pythona plus inne zalety posiadania innego typu.

Tak, w przypadku enumerate i reversed jest to po prostu dlatego, że zapewnia lepszą wydajność i jest łatwiejsza w utrzymaniu.

Inne zalety to:

  • Możesz dodać metody do typu (np chain.from_iterable.). Można to zrobić nawet za pomocą funkcji, ale najpierw musisz je zdefiniować, a następnie ręcznie ustawić atrybuty, które nie wyglądają tak czysto.
  • Możesz nam isinstance na iterables. Może to pozwolić na pewne optymalizacje (na przykład, jeśli wiesz, że isinstance(iterable, itertools.repeat), możesz być w stanie zoptymalizować kod, ponieważ wiesz, które wartości zostaną uzyskane.

Edit: Właśnie w celu wyjaśnienia, co mam na myśli:

w C, czyli nowej instancji generatora, należy utworzyć kod obiekt, który zawiera aktualne kodu bajtowego.

Patrząc na Objects/genobject.c jedyną funkcją, aby utworzyć instancję PyGen_Type jest PyGen_New którego podpis jest:

PyObject * 
PyGen_New(PyFrameObject *f) 

Teraz, patrząc na Objects/frameobject.c widzimy, że aby stworzyć PyFrameObject ty koniecznością połączenia PyFrame_New, z tym podpisem:

PyFrameObject * 
PyFrame_New(PyThreadState *tstate, PyCodeObject *code, PyObject *globals, 
      PyObject *locals) 

Jak widać, wymaga instancji a PyCodeObject. PyCodeObject s W jaki sposób interpreter python reprezentuje kod bajtowy wewnętrznie (np. PyCodeObject może reprezentować kod bajtowy funkcji), więc: tak, aby utworzyć instancję PyGen_Type z C, należy ręcznie utworzyć kod bajtowy, a tworzenie go nie jest takie proste PyCodeObject s od PyCode_New ma ten podpis:

PyCodeObject * 
PyCode_New(int argcount, int kwonlyargcount, 
      int nlocals, int stacksize, int flags, 
      PyObject *code, PyObject *consts, PyObject *names, 
      PyObject *varnames, PyObject *freevars, PyObject *cellvars, 
      PyObject *filename, PyObject *name, int firstlineno, 
      PyObject *lnotab) 

uwaga jak to zawiera argumenty takie jak firstlineno, filename które są oczywiście miało być uzyskane przez źródła Pythona, a nie z innym kodem C. Oczywiście możesz go utworzyć w C, ale nie jestem wcale pewien, czy wymagałoby to mniej znaków niż pisanie prostego nowego typu.

+0

Czy musisz? Duża liczba funkcji jest napisana w języku C i naprawdę wątpię, że nie można emulować generatorów w języku C. Czy to ładne, czy użyteczne, to zupełnie inne pytanie ;-) – delnan

+4

Dlaczego funkcje napisane w C muszą być nowymi typami? – martineau

+0

@martineau Prawdopodobnie uogólniłem zbyt wiele. Chciałem powiedzieć, że stworzenie nowego generatora w C oznacza ręczne utworzenie kodu bajtowego dla funkcji, która jest przesadą i nie jest tak "wygodna". Pisanie nowego typu za pomocą metod '__iter__' i' __next__' jest dość łatwe i zapewnia więcej korzyści. – Bakuriu

2

Tak, są one realizowane w C. Będą korzystać z API C dla iteratorów (PEP 234), w którym iteratory są zdefiniowane poprzez tworzenie nowych typów, które mają gniazdo tp_iternext.

Funkcje tworzone za pomocą składni funkcji generatora (yield) są funkcjami "magicznymi", które zwracają specjalny obiekt generatora. Są to instancje types.GeneratorType, których nie można ręcznie utworzyć. Jeśli inna biblioteka korzystająca z interfejsu API C definiuje własny typ iteratora, nie będzie instancją GeneratorType, ale nadal będzie implementować protokół iteratora C API.

Dlatego typ enumerate jest odmiennym rodzajem, który różni się od GeneratorType i można go używać jak każdego innego typu, z isinstance i podobnymi (chociaż nie powinno).


przeciwieństwie odpowiedź Bakuriu, w enumerate nie jest generator, więc nie ma kodu bajtowego/ramek.

$ grep -i 'frame\|gen' Objects/enumobject.c 
    PyObject_GenericGetAttr,  /* tp_getattro */ 
    PyType_GenericAlloc,   /* tp_alloc */ 
    PyObject_GenericGetAttr,  /* tp_getattro */ 
    PyType_GenericAlloc,   /* tp_alloc */ 

Zamiast sposób tworzenia nowego enumobject jest z funkcją enum_new, którego podpis nie wykorzystuje ramkę

static PyObject * 
enum_new(PyTypeObject *type, PyObject *args, PyObject *kwds) 

Funkcja ta jest umieszczona wewnątrz tp_new szczelinie PyEnum_Type (struct typu PyTypeObject).Tutaj widzimy również, że slot tp_iternext jest zajęty przez funkcję enum_next, która zawiera prosty kod C, który pobiera następny element iteratora, który wylicza, a następnie zwraca PyObject (krotkę).

Przeprowadzka, PyEnum_Type jest następnie umieszczana w module wbudowanym (Python/bltinmodule.c) pod nazwą enumerate, dzięki czemu jest publicznie dostępna.

Nie potrzebny kod bajtowy. Pure C. O wiele bardziej wydajny niż jakikolwiek czysty python lub implementacja generatortype.

+0

I ** never ** stwierdził, że 'wyliczanie' wymaga" kodu bajtowego "lub obiektów ramek. Stwierdziłem, że stworzenie nowej instancji 'GeneratorType' wymaga tego, a więc * mogłoby *" wyliczyć ", gdyby zostało zaimplementowane jako funkcja zwracająca instancję' GeneratorType'. – Bakuriu

+1

@Bakuriu I oskarżyłem cię o powiedzenie czegoś takiego. Twoja odpowiedź jest poświęcona zdefiniowaniu generatora w C. Ale nikt tego nie robi, definiujemy niestandardowe typy iteratorów w C. – forivall

+0

Gdybym chciał zamienić wyliczenie na funkcję w C, użyłbym 'PyCFunction_New *' i zwróciłby niestandardowy obiekt, powiedzmy, PyEnum_Type. Brawo dla typowania kaczek: nie musimy się przejmować, że nie jest to instancja typu "GeneratorType". – forivall

1

Wywołanie enumerate wymaga zwrócenia iteratora. Iterator to obiekt o określonym API. Najłatwiejszym sposobem implementacji klasy z określonym interfejsem API jest, ogólnie rzecz biorąc, zaimplementowanie jej jako klasy.

Powodem, dla którego napisano "typ" zamiast "klasa", jest specyficzny dla Pythona 2, ponieważ klasy wbudowane nazywane były "typami" w Pythonie 2, jako pozostałą część Pythona posiadającego oba typy i klasy przed Pythonem 2.2. W Pythonie 2.3 klasy i typy zostały ujednolicone. A w Pythonie 3 to dlatego mówi klasa:

>>> enumerate 
<class 'enumerate'> 

To czyni go bardziej zrozumiałym, że kwestia „Dlaczego niektóre typy builtins zamiast funkcji” ma bardzo niewiele wspólnego z ich realizowany w C. Są typy/klasy, ponieważ był to najlepszy sposób implementacji funkcjonalności. To takie proste.

Teraz gdybyśmy zamiast interpretować jako pytanie „Dlaczego enumerate typ/klasa zamiast generator” (co jest zupełnie inna kwestia), to odpowiedź jest oczywiście inna. Odpowiedź jest taka, że ​​generatory są skrótami Pythona do tworzenia iteratorów z funkcji Pythona. Nie są one przeznaczone do użytku z C. Są również mniej przydatne do generowania generatorów poza funkcjami poza metodami klasy, tak jakby chcieć utworzyć obiekt iteracyjny z metody klasy, którą trzeba również przekazać w kontekście obiektu, ale z funkcją, której nie potrzebujesz. Tak więc jest to głównie korzyść, że masz mniej "rusztowania" kodu.

+0

Nie widzę, jak różnica w python3/python2 ma * cokolwiek * do czynienia z pytaniem OP (ponieważ wspomniał tylko o python2.7). Również "Są typy/klasy, ponieważ był to najlepszy sposób na wdrożenie funkcjonalności" jest dość oczywiste, inaczej oznaczałoby to, że deweloperzy Pythona lubią tracić czas na robienie rzeczy w trudny sposób, bez żadnej przewagi. Pytanie OP jest bardziej szczegółowe. – Bakuriu

+0

@Bakuriu: Chodzi o to, że Python 2 nazywający go "typami" skłonił ludzi do myślenia, że ​​ma to coś wspólnego z ich implementacją w C, co wynika z dwóch pozostałych odpowiedzi. To jest źle**. Nie ma to nic wspólnego z ich implementacją w C. Jest to widoczne w Pythonie 3, gdzie nie są to już typy, ale klasy. –

+0

@Bakuriu Ja wyjaśniłem pytanie. –

Powiązane problemy