Mam znajomego, który lubi używać metaklas i regularnie oferuje je jako rozwiązanie.
Jestem zdania, że prawie nigdy nie musisz używać metaklas. Czemu? ponieważ myślę, że jeśli robisz coś takiego z klasą, prawdopodobnie powinieneś robić to z obiektem. Konieczne jest małe przeprojektowanie / refaktoryzacja.
Możliwość korzystania z metaklas spowodowała, że wiele osób w wielu miejscach używa klas jako pewnego rodzaju obiektu drugorzędnego, co po prostu wydaje mi się katastrofalne. Czy programowanie ma zostać zastąpione przez metaprogramowanie? Dodanie dekoratorów klasowych niestety uczyniło go jeszcze bardziej akceptowalnym.
Więc proszę, jestem zdesperowany, aby poznać twoje prawidłowe (konkretne) przypadki użycia metaklas w Pythonie. Albo aby dowiedzieć się, dlaczego czasami mutowanie klas jest lepsze niż mutowanie obiektów.
Zacznę:
Czasami podczas korzystania z biblioteki innej firmy przydatna jest możliwość zmodyfikowania klasy w określony sposób.
(To jedyny przypadek, jaki przychodzi mi do głowy i nie jest konkretny)
Odpowiedzi:
Mam klasę, która obsługuje nieinteraktywne kreślenie, jako nakładka na Matplotlib. Jednak czasami chce się wykonać interaktywne kreślenie. Dzięki kilku funkcjom stwierdziłem, że byłem w stanie zwiększyć liczbę figur, ręcznie wywołać rysowanie itp., Ale musiałem to zrobić przed i po każdym wywołaniu kreślenia. Tak więc, aby utworzyć zarówno interaktywne opakowanie do drukowania, jak i opakowanie do drukowania poza ekranem, okazało się, że bardziej wydajne jest zrobienie tego za pomocą metaklasy, zawijając odpowiednie metody, niż zrobić coś takiego:
Ta metoda nie nadąża za zmianami API i tak dalej, ale taka, która iteruje po atrybutach klasy
__init__
przed ponownym ustawieniem atrybutów klasy, jest bardziej wydajna i zapewnia aktualność:Oczywiście mogą być lepsze sposoby, aby to zrobić, ale okazało się, że jest to skuteczne. Oczywiście można to również zrobić w programie
__new__
lub__init__
, ale było to rozwiązanie, które uznałem za najprostsze.źródło
Ostatnio zadano mi to samo pytanie i otrzymałem kilka odpowiedzi. Mam nadzieję, że mogę ożywić ten wątek, ponieważ chciałem rozwinąć kilka z wymienionych przypadków użycia i dodać kilka nowych.
Większość metaklas, które widziałem, robi jedną z dwóch rzeczy:
Rejestracja (dodanie klasy do struktury danych):
Za każdym razem, gdy tworzysz podklasę
Model
, Twoja klasa jest rejestrowana wmodels
słowniku:Można to również zrobić za pomocą dekoratorów klas:
Lub z wyraźną funkcją rejestracji:
Właściwie jest to prawie to samo: niepomyślnie wspominasz o dekoratorach klas, ale tak naprawdę jest to nic innego jak cukier syntaktyczny dla wywołania funkcji w klasie, więc nie ma w tym żadnej magii.
W każdym razie zaletą metaklas w tym przypadku jest dziedziczenie, ponieważ działają one dla dowolnych podklas, podczas gdy inne rozwiązania działają tylko dla podklas jawnie ozdobionych lub zarejestrowanych.
Refaktoryzacja (modyfikacja atrybutów klas lub dodanie nowych):
Za każdym razem, gdy podklasujesz
Model
i definiujesz niektóreField
atrybuty, są one wstrzykiwane wraz z ich nazwami (na przykład w celu uzyskania bardziej szczegółowych komunikatów o błędach) i grupowane w_fields
słowniku (dla łatwej iteracji, bez konieczności przeglądania wszystkich atrybutów klasy i wszystkich jej klas podstawowych '' atrybuty za każdym razem):Ponownie, można to zrobić (bez dziedziczenia) za pomocą dekoratora klas:
Lub wyraźnie:
Chociaż, w przeciwieństwie do twojego poparcia dla czytelnego i łatwego w utrzymaniu programowania niemetatowego, jest to znacznie bardziej uciążliwe, nadmiarowe i podatne na błędy:
Biorąc pod uwagę najbardziej powszechne i konkretne przypadki użycia, jedynymi przypadkami, w których absolutnie MUSISZ używać metaklas, są sytuacje, gdy chcesz zmodyfikować nazwę klasy lub listę klas bazowych, ponieważ po zdefiniowaniu parametry te są wstawiane do klasy i nie ma dekoratora lub funkcja może je rozpalić.
Może to być przydatne w frameworkach do wydawania ostrzeżeń, gdy zdefiniowane są klasy o podobnych nazwach lub niepełnych drzewach dziedziczenia, ale nie mogę wymyślić powodu poza trollowaniem, aby faktycznie zmienić te wartości. Może David Beazley może.
W każdym razie w Pythonie 3 metaklasy mają również
__prepare__
metodę, która pozwala oszacować treść klasy na odwzorowanie inne niż adict
, obsługując w ten sposób uporządkowane atrybuty, przeciążone atrybuty i inne niesamowite fajne rzeczy:Możesz argumentować, że uporządkowane atrybuty można osiągnąć za pomocą liczników tworzenia, a przeciążenie można symulować za pomocą domyślnych argumentów:
Poza tym, że jest dużo bardziej brzydki, jest również mniej elastyczny: co jeśli chcesz uporządkować atrybuty dosłowne, takie jak liczby całkowite i łańcuchy? A co, jeśli
None
jest to prawidłowa wartośćx
?Oto kreatywny sposób rozwiązania pierwszego problemu:
A oto kreatywny sposób na rozwiązanie drugiego:
Ale to dużo, DUŻO voodoo-er niż zwykła metaklasa (zwłaszcza pierwsza, która naprawdę topi twój mózg). Chodzi mi o to, że patrzysz na metaklasy jako nieznane i sprzeczne z intuicją, ale możesz też spojrzeć na nie jako na kolejny krok ewolucji języków programowania: po prostu musisz dostosować swój sposób myślenia. W końcu mógłbyś prawdopodobnie zrobić wszystko w C, w tym zdefiniować strukturę ze wskaźnikami do funkcji i przekazać ją jako pierwszy argument do jej funkcji. Osoba, która zobaczy C ++ po raz pierwszy, może powiedzieć: „co to za magia? Dlaczego kompilator niejawnie przechodzi
this
do metod, ale nie do funkcji regularnych i statycznych? Lepiej jest mówić wprost i rozwlekle w swoich argumentach. ”Ale z drugiej strony programowanie zorientowane obiektowo jest znacznie potężniejsze, kiedy już je zrozumiesz; i tak samo jest z tym, uh ... programowaniem quasi-aspektowym, jak sądzę. rozumiesz metaklasy, w rzeczywistości są one bardzo proste, więc dlaczego nie używać ich, gdy jest to wygodne?I wreszcie metaklasy są świetne, a programowanie powinno być zabawne. Korzystanie ze standardowych konstrukcji programistycznych i wzorców projektowych przez cały czas jest nudne i mało inspirujące oraz ogranicza wyobraźnię. Żyć trochę! Oto metametaklasa specjalnie dla Ciebie.
Edytować
To dość stare pytanie, ale wciąż otrzymuję głosy za, więc pomyślałem, że dodam link do bardziej wyczerpującej odpowiedzi. Jeśli chcesz przeczytać więcej o metaklasach i ich zastosowaniach, właśnie opublikowałem tutaj artykuł na ten temat .
źródło
__metaclass__
atrybut, ale ten atrybut nie jest już specjalny w Pythonie 3. Czy istnieje sposób, aby to "klasy potomne były również konstruowane przez metaklasę rodzica" działały w Pythonie 3 ?init_subclass
, więc teraz możesz manipulować podklasami w klasie bazowej i nie potrzebujesz już do tego celu metaklasy.Celem metaklas nie jest zastępowanie rozróżnienia klasy / obiektu metaklasą / klasą - ma to na celu zmianę zachowania definicji klas (a tym samym ich instancji) w jakiś sposób. W rzeczywistości ma to na celu zmianę zachowania instrukcji class w sposób, który może być bardziej przydatny dla Twojej konkretnej domeny niż domyślny. Użyłem ich do:
Podklasy śledzące, zwykle do rejestrowania programów obsługi. Jest to przydatne, gdy używasz konfiguracji w stylu wtyczki, gdzie chcesz zarejestrować procedurę obsługi dla określonej rzeczy, po prostu przez podklasę i ustawienie kilku atrybutów klas. na przykład. załóżmy, że piszesz program obsługi dla różnych formatów muzycznych, gdzie każda klasa implementuje odpowiednie metody (tagi odtwarzania / pobierania itp.) dla swojego typu. Dodanie procedury obsługi dla nowego typu staje się:
Następnie metaklasa utrzymuje słownik
{'.mp3' : MP3File, ... }
itp. I konstruuje obiekt odpowiedniego typu, gdy żądasz programu obsługi za pośrednictwem funkcji fabryki.Zmiana zachowania. Możesz chcieć nadać specjalne znaczenie pewnym atrybutom, powodując zmianę zachowania, gdy są obecne. Na przykład możesz chcieć poszukać metod o nazwie
_get_foo
i_set_foo
przekonwertować je w sposób przezroczysty na właściwości. Jako przykład ze świata rzeczywistego, oto przepis, który napisałem, aby podać więcej definicji struktur podobnych do C. Metaklasa jest używana do konwersji zadeklarowanych elementów na łańcuch formatu struct, obsługi dziedziczenia itp. Oraz do stworzenia klasy, która może sobie z tym poradzić.Aby zapoznać się z innymi przykładami ze świata rzeczywistego, przyjrzyj się różnym ORMom, takim jak ORM sqlalchemy lub sqlobject . Ponownie, celem jest interpretacja definicji (tutaj definicji kolumn SQL) o szczególnym znaczeniu.
źródło
Zacznijmy od klasycznego cytatu Tima Petera:
Powiedziawszy to, natknąłem się (okresowo) na prawdziwe zastosowania metaklas. Przychodzi mi na myśl w Django, gdzie wszystkie modele dziedziczą po modelach. Model z kolei wykonuje poważną magię, aby otoczyć modele DB dobrocią ORM Django. Ta magia dzieje się za pośrednictwem metaklas. Tworzy wszelkiego rodzaju klasy wyjątków, klasy menedżerów itp.
Zobacz django / db / models / base.py, klasa ModelBase () na początek historii.
źródło
Metaklasy mogą być przydatne do tworzenia języków specyficznych dla domeny w Pythonie. Konkretnymi przykładami są Django, deklaratywna składnia schematów bazy danych SQLObject.
Podstawowy przykład z A Conservative Metaclass autorstwa Iana Bickinga :
Inne techniki: Składniki do budowania DSL w Pythonie (pdf).
Edytuj (autor: Ali): Wolałbym, aby to zrobić przy użyciu kolekcji i instancji. Ważnym faktem są instancje, które dają więcej mocy i eliminują powód do używania metaklas. Ponadto warto zauważyć, że Twój przykład używa mieszanki klas i instancji, co z pewnością wskazuje, że nie możesz tego wszystkiego zrobić za pomocą metaklas. I tworzy naprawdę niejednolity sposób na zrobienie tego.
Nie jest doskonały, ale już prawie nie ma magii, nie ma potrzeby stosowania metaklas i poprawiono jednolitość.
źródło
Rozsądnym wzorcem użycia metaklasy jest zrobienie czegoś raz, gdy klasa jest zdefiniowana, a nie powtarzanie się, gdy tworzona jest instancja tej samej klasy.
Gdy wiele klas ma to samo specjalne zachowanie, powtarzanie
__metaclass__=X
jest oczywiście lepsze niż powtarzanie kodu specjalnego przeznaczenia i / lub wprowadzanie współdzielonych klas nadrzędnych ad hoc.Ale nawet z tylko jedną klasą specjalną i bez przewidywalnego rozszerzenia
__new__
oraz__init__
metaklasy są czystszym sposobem inicjowania zmiennych klas lub innych danych globalnych niż mieszanie kodu specjalnego przeznaczenia i normalnychdef
iclass
instrukcji w treści definicji klasy.źródło
Jedyny raz, kiedy użyłem metaklas w Pythonie, to podczas pisania wrappera dla Flickr API.
Moim celem było zeskrobanie strony API Flickr i dynamiczne wygenerowanie pełnej hierarchii klas, aby umożliwić dostęp do API za pomocą obiektów Pythona:
W tym przykładzie, ponieważ wygenerowałem całe API Python Flickr ze strony internetowej, tak naprawdę nie znam definicji klas w czasie wykonywania. Możliwość dynamicznego generowania typów była bardzo przydatna.
źródło
Myślałem o tym samym wczoraj i całkowicie się z tym zgadzam. Komplikacje w kodzie spowodowane próbami uczynienia go bardziej deklaratywnym generalnie sprawiają, że baza kodu jest trudniejsza w utrzymaniu, trudniejsza do odczytania i moim zdaniem mniej pytoniczna. Zwykle wymaga również dużo kopiowania.copy () ing (aby zachować dziedziczenie i kopiować z klasy do instancji) i oznacza, że musisz szukać w wielu miejscach, aby zobaczyć, co się dzieje (zawsze patrząc od metaklasy w górę), co jest sprzeczne z ziarno pytona również. Przeglądałem kod formencode i sqlalchemy, aby zobaczyć, czy taki deklaratywny styl był tego wart, a wyraźnie nie. Taki styl należy pozostawić deskryptorom (takim jak właściwość i metody) oraz niezmiennym danym. Ruby ma lepsze wsparcie dla takich deklaratywnych stylów i cieszę się, że podstawowy język Pythona nie idzie tą drogą.
Widzę ich zastosowanie do debugowania, dodaję metaklasę do wszystkich klas podstawowych, aby uzyskać bogatsze informacje. Widzę również ich użycie tylko w (bardzo) dużych projektach, aby pozbyć się pewnego standardowego kodu (ale z utratą przejrzystości). Na przykład sqlalchemy używa ich gdzie indziej, aby dodać określoną niestandardową metodę do wszystkich podklas w oparciu o wartość atrybutu w ich definicji klasy, np. przykład zabawki
mogłaby mieć metaklasę, która generowała metodę w tej klasie ze specjalnymi właściwościami opartymi na „hello” (powiedzmy metodę, która dodała „hello” na końcu łańcucha). Dla łatwości utrzymania może być dobre upewnienie się, że nie musisz pisać metody w każdej tworzonej podklasie, a zamiast tego wystarczy zdefiniować wartość method_maker_value.
Potrzeba tego jest jednak tak rzadka i ogranicza się tylko do odrobiny pisania, że nie jest to warte rozważenia, chyba że masz wystarczająco dużą bazę kodów.
źródło
Nigdy absolutnie nie musisz używać metaklasy, ponieważ zawsze możesz skonstruować klasę, która robi to, co chcesz, używając dziedziczenia lub agregacji klasy, którą chcesz zmodyfikować.
To powiedziawszy, w Smalltalk i Ruby może być bardzo przydatna możliwość modyfikowania istniejącej klasy, ale Python nie lubi tego robić bezpośrednio.
Jest doskonały artykuł DeveloperWorks na temat metaklas w Pythonie, który może pomóc. Artykuł w Wikipedii też jest całkiem niezły.
źródło
Niektóre biblioteki GUI mają problemy, gdy wiele wątków próbuje z nimi współdziałać.
tkinter
jest jednym z takich przykładów; i chociaż można bezpośrednio poradzić sobie z problemem ze zdarzeniami i kolejkami, znacznie prostsze może być użycie biblioteki w sposób, który całkowicie ignoruje problem. Spójrz - magia metaklas.Możliwość bezproblemowego dynamicznego przepisywania całej biblioteki, tak aby działała zgodnie z oczekiwaniami w aplikacji wielowątkowej, może być niezwykle pomocna w niektórych okolicznościach. Safetkinter moduł robi to z pomocą metaklasą dostarczonych przez threadbox modułu - nie potrzebnej imprezy i kolejek.
Jednym z fajnych aspektów
threadbox
jest to, że nie obchodzi go, jaką klasę klonuje. Zawiera przykład tego, jak metaklasa może w razie potrzeby dotknąć wszystkich klas podstawowych. Kolejną zaletą metaklas jest to, że działają one również na klasach dziedziczących. Programy, które same się piszą - czemu nie?źródło
Jedynym uzasadnionym przypadkiem użycia metaklasy jest powstrzymanie innych wścibskich programistów przed dotykaniem twojego kodu. Gdy wścibski programista opanuje metaklasy i zacznie szperać w twoich, wrzuć kolejny poziom lub dwa, aby ich nie dopuścić. Jeśli to nie zadziała, zacznij używać
type.__new__
lub być może jakiegoś schematu przy użyciu rekurencyjnej metaklasy.(napisany z przymrużeniem oka, ale widziałem, jak robiono tego rodzaju zaciemnianie. Django to doskonały przykład)
źródło
Metaklasy nie zastępują programowania! To tylko sztuczka, która może zautomatyzować lub uczynić niektóre zadania bardziej eleganckimi. Dobrym tego przykładem jest biblioteka podświetlania składni Pygments . Posiada klasę o nazwie,
RegexLexer
która pozwala użytkownikowi zdefiniować zestaw reguł leksykalnych jako wyrażenia regularne w klasie. Metaklasa służy do przekształcania definicji w użyteczny parser.Są jak sól; jest zbyt łatwy w użyciu.
źródło
Sposób, w jaki użyłem metaklas, polegał na nadaniu klasom atrybutów. Weź na przykład:
umieści atrybut name w każdej klasie, dla której metaklasa będzie wskazywała na NameClass.
źródło
Jest to niewielkie zastosowanie, ale ... jedną rzeczą, do której przydały się metaklasy, jest wywołanie funkcji za każdym razem, gdy tworzona jest podklasa. Zakodowałem to w metaklasie, która szuka
__initsubclass__
atrybutu: za każdym razem, gdy tworzona jest podklasa, wywoływane są wszystkie klasy nadrzędne, które definiują tę metodę__initsubclass__(cls, subcls)
. Pozwala to na utworzenie klasy nadrzędnej, która następnie rejestruje wszystkie podklasy w jakimś rejestrze globalnym, przeprowadza niezmienne kontrole podklas, gdy są one zdefiniowane, wykonuje operacje późnego wiązania itp ... wszystko bez konieczności ręcznego wywoływania funkcji lub tworzenia niestandardowych metaklas, które wykonywać każdy z tych odrębnych obowiązków.Pamiętaj, że powoli zdałem sobie sprawę, że ukryta magia tego zachowania jest nieco niepożądana, ponieważ jest to nieoczekiwane, gdy patrzy się na definicję klasy poza kontekstem ... więc odeszłam od używania tego rozwiązania do czegokolwiek poważnego poza inicjowanie
__super
atrybutu dla każdej klasy i instancji.źródło
Niedawno musiałem użyć metaklasy, aby pomóc w deklaratywnym zdefiniowaniu modelu SQLAlchemy wokół tabeli bazy danych wypełnionej danymi ze spisu ludności USA z http://census.ire.org/data/bulkdata.html
IRE zapewnia powłoki baz danych dla tabel danych spisowych, które tworzą kolumny z liczbami całkowitymi zgodnie z konwencją nazewnictwa opracowaną przez Census Bureau p012015, p012016, p012017 itp.
Chciałem a) móc uzyskać dostęp do tych kolumn za pomocą
model_instance.p012017
składni, b) jasno określić, co robię oraz c) nie musieć jawnie definiować dziesiątek pól w modelu, więc podklasowałem SQLAlchemy,DeclarativeMeta
aby iterować przez zakres kolumny i automatycznie tworzą pola modelu odpowiadające kolumnom:Następnie mógłbym użyć tej metaklasy do mojej definicji modelu i uzyskać dostęp do automatycznie wyliczanych pól w modelu:
źródło
Wydaje się, że opisane tutaj uzasadnione użycie - przepisywanie dokumentów w języku Python za pomocą metaklasy.
źródło
Musiałem ich użyć raz dla parsera binarnego, aby był łatwiejszy w użyciu. Definiujesz klasę wiadomości z atrybutami pól obecnych w łączu. Należało je uporządkować w sposób, w jaki zadeklarowano, aby zbudować z niego ostateczny format drutu. Możesz to zrobić za pomocą metaklas, jeśli używasz uporządkowanej dyktatury przestrzeni nazw. W rzeczywistości jest to w przykładach dla metaklas:
https://docs.python.org/3/reference/datamodel.html#metaclass-example
Ale ogólnie: bardzo dokładnie oceń, czy naprawdę potrzebujesz dodatkowej złożoności metaklas.
źródło
odpowiedź od @Dan Gittik jest fajna
przykłady na końcu mogłyby wyjaśnić wiele rzeczy, zmieniłem go na Pythona 3 i podam kilka wyjaśnień:
źródło
Innym przypadkiem użycia jest możliwość modyfikowania atrybutów na poziomie klasy i upewnienia się, że ma to wpływ tylko na dany obiekt. W praktyce oznacza to „scalanie” faz metaklas i instancji klas, co prowadzi do zajmowania się tylko instancjami klas ich własnego (unikatowego) rodzaju.
Musiałem to również zrobić, gdy (ze względu na czytelność i polimorfizm ) chcieliśmy dynamicznie zdefiniować
property
wartości zwracane (mogą) wynikać z obliczeń opartych na (często zmieniających się) atrybutach na poziomie instancji, co można zrobić tylko na poziomie klasy , tj. po instancji metaklasy i przed instancją klasy.źródło