2015-01-28 33 views
19

Spędziłem trochę czasu na badaniu collections.namedtuple module kilka tygodni temu. Moduł wykorzystuje funkcję fabryczną, która zapełnia dane dynamiczne (nazwa nowej klasy namedtuple i nazwy atrybutów klas) w bardzo dużym ciągu. Następnie exec jest wykonywany z ciągiem (który reprezentuje kod) jako argumentem, a nowa klasa jest zwracana.Dlaczego moduł namedtuple nie używa metaclass do tworzenia obiektów klasy nt?

Czy ktoś wie, dlaczego zostało to zrobione w ten sposób, gdy istnieje konkretne narzędzie dla tego rodzaju rzeczy łatwo dostępne, tj. Metaklasu? Nie próbowałem zrobić to sama, ale wydaje się, że wszystko, co dzieje się w module namedtuple mogły być łatwo realizowane za pomocą namedtuple metaklasa, tak:

class namedtuple(type): 

itp itd

Odpowiedz

19

Nie są pewne wskazówki w issue 3974. Autor zaproponował nowy sposób tworzenia nazwanych krotki, który został odrzucony z następującymi uwagami:

Wydaje się, że korzyści z oryginalnej wersji jest to, że szybciej, dzięki hardcoding metod krytycznych. - Antoine Pitrou

Nie ma nic unholy temat korzystania EXEC. Wcześniejsze wersje używały innych podejść i okazały się niepotrzebnie skomplikowane i miały nieoczekiwane problemy z wersją . Jest to kluczowa funkcja dla nazwanych krotek, które są dokładnie takie same, jak klasa odręcznie napisana. - Raymond Hettinger

Dodatkowo, tutaj jest częścią opisu the original namedtuple recipe:

... recepta ewoluowała do obecnej exec stylu, gdzie mamy wszystko z wysokiej Pythona wbudowane argumenty prędkości sprawdzania za darmo. Nowy styl budowania i uruchamiania szablonu spowodował, że zarówno __new__, jak i __repr__ działa szybciej i czystsze niż w poprzednich wersjach tego przepisu.

Jeśli szukasz niektórych alternatywnych implementacjach:

+0

hmmm. to na pewno na ogół odpowiada na pytanie, ale chciałbym wiedzieć, jakie są te niespodziewane problemy. w zależności od tego, czym były, problemy mogą dotyczyć samych metaclasses, w którym to przypadku mogą one zostać naprawione. Wydaje się również, że odpowiedź na ten komentarz 2,5 roku później wywołuje pewne realne problemy, które niektórzy mogą mieć. w każdym razie, dzięki za link - dużo informacji. –

+13

Nigdy tak naprawdę nie kupiłem tego. Zawsze wydawało mi się, że odpowiedź brzmi: "ponieważ Raymond Hettinger jest w porządku z użyciem dziwnych hacków". – BrenBarn

+0

jako osoba, która się uczy, to naprawdę daje mi pauzę, aby zobaczyć coś takiego w standardowej bibliotece. założyłem, że standardowa biblioteka będzie dobrym miejscem do sprawdzenia, jak powinien wyglądać "dobry kod". ale użycie 'exec' w taki sposób wydaje się, jak powiedział komentator powyżej, jak włamanie i jest to trochę rozczarowujące. metaclasses są całkiem niesamowite, ale jeśli standardowa biblioteka sama używa ich w tak oczywistej sytuacji, jaki jest sens ich posiadania? –

4

jako a sidenote: Drugi sprzeciw, który widzę najczęściej przeciwko nam g exec jest to, że niektóre lokalizacje (czytaj firmy) wyłączają go ze względów bezpieczeństwa.

Poza zaawansowanym Enum i NamedConstant, the aenum library * ma również NamedTuple który metaclass -na.


* aenum jest napisany przez autora enum i enum34 backport.

1

Oto inne podejście.

""" Subclass of tuple with named fields """ 
from operator import itemgetter 
from inspect import signature 

class MetaTuple(type): 
    """ metaclass for NamedTuple """ 

    def __new__(mcs, name, bases, namespace): 
     cls = type.__new__(mcs, name, bases, namespace) 
     names = signature(cls._signature).parameters.keys() 
     for i, key in enumerate(names): 
      setattr(cls, key, property(itemgetter(i))) 
     return cls 

class NamedTuple(tuple, metaclass=MetaTuple): 
    """ Subclass of tuple with named fields """ 

    @staticmethod 
    def _signature(): 
     " Override in subclass " 

    def __new__(cls, *args): 
     new = super().__new__(cls, *args) 
     if len(new) == len(signature(cls._signature).parameters): 
      return new 
     return new._signature(*new) 

if __name__ == '__main__': 
    class Point(NamedTuple): 
     " Simple test " 
     @staticmethod 
     def _signature(x, y, z): # pylint: disable=arguments-differ                                                                          
      " Three coordinates " 
    print(Point((1, 2, 4))) 

Jeśli to podejście ma jakąkolwiek zaletę, jest to prostota. Byłoby prostsze, ale bez numeru NamedTuple.__new__, który służy tylko do wymuszania liczby elementów. Bez tego szczęśliwie dopuszcza dodatkowe anonimowe elementy poza nazwane, a podstawowym efektem pominięcia elementów jest IndexError dla pominiętych elementów podczas uzyskiwania dostępu do nich po nazwie (przy niewielkiej pracy, którą można przetłumaczyć na AttributeError). Komunikat o błędzie niepoprawnego liczenia elementów jest nieco dziwny, ale ma to decydujące znaczenie. Nie spodziewałbym się, że to zadziała z Pythonem 2.

Jest miejsce na dalsze komplikacje, takie jak metoda __repr__. Nie mam pojęcia, jak porównanie wydajności z innymi implementacjami (może pomóc buforowanie długości podpisu), ale zdecydowanie preferuję konwencję wywoływania w porównaniu z implementacją natywną namedtuple.