Kas yra „Python“ metaklasės?

Kas yra metaklasės ir kodėl mes juos naudojame?

4879
19 сент. nustatytas e-satis 19 sep. 2008-09-19 09:10 '08 at 9:10 am. 2008-09-19 09:10
@ 19 atsakymų

Metaclass yra klasės klasė. Kadangi klasė apibrėžia klasės egzemplioriaus elgesį, metaklasė apibrėžia klasės elgesį. Klasė yra metaklasės pavyzdys.

Pythone galite naudoti savavališkus kvietimus teikti metaklases (kaip parodyta „ Jerub“ ), o naudingesnis požiūris yra tai, kad ji būtų pati klasė. type yra reguliari metaklasė Pythone. Jei jus domina, taip, type yra pati klasė, o tai yra jos tipas. Jūs negalite atkurti kažko panašaus type tik „Python“, bet „Python“ yra šiek tiek klaidinanti. Jei norite sukurti savo metaklasę „Python“, tiesiog norite naudoti type poklasį.

Metaklasė dažniausiai naudojama kaip klasės gamykla. Kaip ir kurdami klasės egzempliorių skambindami klasę, „Python“ sukuria naują klasę (kai ji atlieka „klasės“ operatorių) skambindama metaklasę. Kartu su įprastais metodais __init__ ir __new__ metaklasėmis, kurdami klasę, galite sukurti „papildomus dalykus“, pavyzdžiui, registruoti naują klasę registre arba net visiškai pakeisti klasę su kitu.

Kai vykdomas class pareiškimas, „Python“ pirmiausia atlieka class pareiškimo kūną kaip įprastą kodo bloką. Susidariusioje vardų erdvėje (dict) yra būsimos klasės atributai. Metaklasė nustatoma peržiūrint būsimos klasės bazines klases (metaklasės paveldimos), klasės (jei yra) __metaclass__ atributas arba pasaulinis kintamasis __metaclass__ . Tuomet metaklasė sukuriama su klasės pavadinimu, pagrindais ir atributais, kad būtų sukurtas jo egzempliorius.

Tačiau metaklasės faktiškai apibrėžia klasės tipą, o ne tik jo gamyklą, todėl su jais galite padaryti daug daugiau. Pavyzdžiui, galite nustatyti normalius metodus metaklasėje. Šie metaklasės metodai yra panašūs į klasės metodus ta prasme, kad juos galima pavadinti klasėje be instancijos, bet jie taip pat nėra panašūs į klasės metodus, nes jie negali būti vadinami klasės klasėje. type.__subclasses__() yra type metaklasės metodo pavyzdys. Taip pat galite nustatyti įprastus „magijos“ metodus, tokius kaip __add__ , __iter__ ir __getattr__ , kad įgyvendintumėte ar pakeistumėte klasės elgesį.

Čia yra apibendrintas bitų pavyzdys:

 def make_hook(f): """Decorator to turn 'foo' method into '__foo__'""" f.is_hook = 1 return f class MyType(type): def __new__(mcls, name, bases, attrs): if name.startswith('None'): return None # Go over attributes and see if they should be renamed. newattrs = {} for attrname, attrvalue in attrs.iteritems(): if getattr(attrvalue, 'is_hook', 0): newattrs['__%s__' % attrname] = attrvalue else: newattrs[attrname] = attrvalue return super(MyType, mcls).__new__(mcls, name, bases, newattrs) def __init__(self, name, bases, attrs): super(MyType, self).__init__(name, bases, attrs) # classregistry.register(self, self.interfaces) print "Would register class %s now." % self def __add__(self, other): class AutoClass(self, other): pass return AutoClass # Alternatively, to autogenerate the classname as well as the class: # return type(self.__name__ + other.__name__, (self, other), {}) def unregister(self): # classregistry.unregister(self) print "Would unregister class %s now." % self class MyObject: __metaclass__ = MyType class NoneSample(MyObject): pass # Will print "NoneType None" print type(NoneSample), repr(NoneSample) class Example(MyObject): def __init__(self, value): self.value = value @make_hook def add(self, other): return self.__class__(self.value + other.value) # Will unregister the class Example.unregister() inst = Example(10) # Will fail with an AttributeError #inst.unregister() print inst + inst class Sibling(MyObject): pass ExampleSibling = Example + Sibling # ExampleSibling is now a subclass of both Example and Sibling (with no # content of its own) although it will believe it called 'AutoClass' print ExampleSibling print ExampleSibling.__mro__ 
2286
19 сент. Atsakyti Thomas Wouters 19 rugsėjis 2008-09-19 10:01 '08 at 10:01 am 2008-09-19 10:01

Klasės kaip objektai

Prieš suvokdami metaklases, reikia mokyti klases „Python“. Python turi labai savitą idėją, kokias klases pasiskolinti iš Smalltalk kalbos.

Daugumoje kalbų klasės yra tiesiog kodai, kuriuose aprašoma, kaip sukurti objektą. Tai pasakytina ir apie „Python“:

 >>> class ObjectCreator(object): ... pass ... >>> my_object = ObjectCreator() >>> print(my_object) <__main__.ObjectCreator object at 0x8974f2c> 

Tačiau klasės yra didesnės nei pythone. Klasės taip pat yra objektai.

Taip, objektai.

Kai tik naudojate class raktinį žodį, „Python“ jį atlieka ir sukuria OBJEKTĄ. Instrukcija

 >>> class ObjectCreator(object): ... pass ... 

sukuria atmintyje objektą „ObjectCreator“.

Šis objektas (klasė) pats gali sukurti objektus (egzempliorius), todėl jis yra klasė .

Tačiau vis dėlto tai yra objektas, todėl:

  • Galite priskirti jį kintamajam.
  • Galite jį kopijuoti
  • Galite pridėti atributus.
  • Jį galite perduoti kaip funkcijų parametrą.

pavyzdžiui:

 >>> print(ObjectCreator) # you can print a class because it an object <class '__main__.ObjectCreator'> >>> def echo(o): ... print(o) ... >>> echo(ObjectCreator) # you can pass a class as a parameter <class '__main__.ObjectCreator'> >>> print(hasattr(ObjectCreator, 'new_attribute')) False >>> ObjectCreator.new_attribute = 'foo' # you can add attributes to a class >>> print(hasattr(ObjectCreator, 'new_attribute')) True >>> print(ObjectCreator.new_attribute) foo >>> ObjectCreatorMirror = ObjectCreator # you can assign a class to a variable >>> print(ObjectCreatorMirror.new_attribute) foo >>> print(ObjectCreatorMirror()) <__main__.ObjectCreator object at 0x8997b4c> 

Klasių kūrimas dinamiškai

Kadangi klasės yra objektai, galite juos kurti skrendant, kaip ir bet kokį objektą.

Pirma, galite sukurti klasę funkcijoje, naudojant class :

 >>> def choose_class(name): ... if name == 'foo': ... class Foo(object): ... pass ... return Foo # return the class, not an instance ... else: ... class Bar(object): ... pass ... return Bar ... >>> MyClass = choose_class('foo') >>> print(MyClass) # the function returns a class, not an instance <class '__main__.Foo'> >>> print(MyClass()) # you can create an object from this class <__main__.Foo object at 0x89c6d4c> 

Tačiau tai nėra tokia dinamiška, nes vis dar turite parašyti visą klasę.

Kadangi klasės yra objektai, jie turi būti generuojami kažką.

Kai naudojate class raktinį žodį, „Python“ automatiškai sukuria šį objektą. Tačiau, kaip ir daugelis Python, tai suteikia jums galimybę tai padaryti rankiniu būdu.

Prisiminti funkcijos type ? Gera senoji funkcija, leidžianti sužinoti objekto tipą:

 >>> print(type(1)) <type 'int'> >>> print(type("1")) <type 'str'> >>> print(type(ObjectCreator)) <type 'type'> >>> print(type(ObjectCreator())) <class '__main__.ObjectCreator'> 

Na, type turi visiškai kitokį gebėjimą, jis taip pat gali sukurti pamokas. type gali paimti klasės aprašymą kaip parametrus ir grąžinti klasę.

(Žinau, kad kvaila, kad ta pati funkcija gali turėti du visiškai skirtingus naudojimo parametrus, priklausomai nuo jūsų perduodamų parametrų. Tai yra problema dėl atgalinio suderinamumo „Python“)

type veikia taip:

 type(name of the class, tuple of the parent class (for inheritance, can be empty), dictionary containing attributes names and values) 

pavyzdžiui:

 >>> class MyShinyClass(object): ... pass 

tokiu būdu galima sukurti rankiniu būdu:

 >>> MyShinyClass = type('MyShinyClass', (), {}) # returns a class object >>> print(MyShinyClass) <class '__main__.MyShinyClass'> >>> print(MyShinyClass()) # create an instance with the class <__main__.MyShinyClass object at 0x8997cec> 

Jūs pastebėsite, kad mes naudojame „MyShinyClass“ kaip klasės pavadinimą ir kaip kintamąjį, kad išsaugotume nuorodą į klasę. Jie gali būti skirtingi, tačiau nėra jokios priežasties apsunkinti.

type priskiria žodyną, kad nustatytų klasės atributus. Taigi:

 >>> class Foo(object): ... bar = True 

Gali versti į:

 >>> Foo = type('Foo', (), {'bar':True}) 

Ir naudojama kaip įprastinė klasė:

 >>> print(Foo) <class '__main__.Foo'> >>> print(Foo.bar) True >>> f = Foo() >>> print(f) <__main__.Foo object at 0x8a9b84c> >>> print(f.bar) True 

Ir, žinoma, galite paveldėti iš jo, kad:

 >>> class FooChild(Foo): ... pass 

būtų:

 >>> FooChild = type('FooChild', (Foo,), {}) >>> print(FooChild) <class '__main__.FooChild'> >>> print(FooChild.bar) # bar is inherited from Foo True 

Galiausiai norėsite pridėti metodus savo klasėje. Tiesiog nustatykite funkciją su teisingu parašu ir priskirkite jį kaip atributą.

 >>> def echo_bar(self): ... print(self.bar) ... >>> FooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar}) >>> hasattr(Foo, 'echo_bar') False >>> hasattr(FooChild, 'echo_bar') True >>> my_foo = FooChild() >>> my_foo.echo_bar() True 

Be to, dinamiškai kurdami klasę, galite pridėti dar daugiau metodų, kaip ir būdų pridėti prie įprastai sukurto klasės objekto.

 >>> def echo_bar_more(self): ... print('yet another method') ... >>> FooChild.echo_bar_more = echo_bar_more >>> hasattr(FooChild, 'echo_bar_more') True 

Jūs matote, kur mes einame: Python klasėse yra objektai, o jūs galite dinamiškai kurti klases skristi.

Būtent tai daro Python, kai naudojate class raktinį žodį, ir tai daroma naudojant metaklasę.

Kas yra metaklasės (pagaliau)

Metaklasės yra „medžiaga“, kuri sukuria klases.

Jūs nustatote objektų kūrimo klases, ar ne?

Tačiau sužinojome, kad Python klasės yra objektai.

Na, metaklasės sukuria šiuos objektus. Tai yra klasių klasės, galite jas pavaizduoti taip:

 MyClass = MetaClass() my_object = MyClass() 

Matėte, kad šis type leidžia daryti kažką panašaus:

 MyClass = type('MyClass', (), {}) 

Taip yra todėl type funkcijos type tikrųjų yra metaklasė. type yra metaklasė, kurią Python naudoja kuriant visas klases užkulisiuose.

Dabar jums įdomu, ką pragaras rašo mažosiomis raidėmis, o ne „ Type ?

Na, manau, kad tai yra suderinamumo su str , klasė, kuri sukuria styginių objektus, ir int su klasės, kuri sukuria sveikų skaičių objektus, klausimas. type yra tik klasė, kuri sukuria klasės objektus.

Tai matote __class__ atributą __class__ .

Viskas, ir aš turiu galvoje viską, yra Python objektas. Tai apima sveikuosius skaičius, eilutes, funkcijas ir klases. Visi jie yra objektai. Ir jie visi buvo sukurti iš klasės:

 >>> age = 35 >>> age.__class__ <type 'int'> >>> name = 'bob' >>> name.__class__ <type 'str'> >>> def foo(): pass >>> foo.__class__ <type 'function'> >>> class Bar(object): pass >>> b = Bar() >>> b.__class__ <class '__main__.Bar'> 

Dabar, kas yra __class__ bet kokio __class__ ?

 >>> age.__class__.__class__ <type 'type'> >>> name.__class__.__class__ <type 'type'> >>> foo.__class__.__class__ <type 'type'> >>> b.__class__.__class__ <type 'type'> 

Taigi metaklasė yra tik medžiaga, kuria sukuriami klasės objektai.

Jei norite, galite jį pavadinti „klasės gamykla“.

type yra integruota metaklasė, kuri naudoja „Python“, tačiau, žinoma, galite sukurti savo metaklasę.

__metaclass__

„Python 2“ rašydami klasę galite pridėti __metaclass__ atributą (žr. Šį skyrių apie „Python 3“ sintaksę):

 class Foo(object): __metaclass__ = something... [...] 

Jei tai padarysite, „Python“ naudos metaklasę, kad sukurtų „ Foo klasę.

Atidžiai, sunku.

Pirmiausia rašote class Foo(object) , bet klasėje Foo objektas dar nėra sukurtas atmintyje.

Python ieškos __metaclass__ klasės klasėje. Jei jis jį suras, jis naudos jį sukurti „ Foo objektų klasę. Jei ne, jis naudos type kad sukurtų klasę.

Skaitykite keletą kartų.

Kai darote:

 class Foo(Bar): pass 

„Python“ atlieka šiuos veiksmus:

Ar Foo yra __metaclass__ atributas?

Jei taip, sukurkite klasės objektą atmintyje (pasakiau klasės objektą, likite čia su manimi) pavadinimu „ Foo , naudojant tai, kas yra __metaclass__ .

Jei „Python“ negali rasti __metaclass__ , jis ieškos __metaclass__ MODULE lygiu ir bandys tai padaryti (bet tik toms klasėms, kurios paveldi nieko, daugiausia senojo stiliaus klases).

Tada, jei jis visai negalės rasti __metaclass__ , jis naudos savo metaklasės Bar (pirmąjį tėvą) (kuris gali būti numatytasis type ), kad sukurtumėte klasės objektą.

Čia jūs turite būti atsargūs, __metaclass__ atributas __metaclass__ nebuvo paveldėtas, o tėvų Bar.__class__ ( Bar.__class__ ) bus. Jei Bar naudojo __metaclass__ atributą, kuris sukūrė Bar su type() (o ne type.__new__() ), poklasiai nepaveldės šio elgesio.

Dabar yra didelis klausimas, ką galite įdėti į __metaclass__ ?

Atsakymas: kažkas, kas gali sukurti klasę.

O kas gali sukurti klasę? type , arba kažką, kad poklasiai arba naudoja jį.

Metodikėliai Python 3

Sintaksė metaklonui nustatyti buvo pakeista „Python 3“:

 class Foo(object, metaclass=something): ... 

t.y. „ __metaclass__ atributas nebenaudojamas pagrindinių argumentų sąrašo pagrindų klasių sąraše naudai.

Tačiau metaklasių elgesys iš esmės išlieka tas pats .

Vienas dalykas, pridėtas prie „Python 3“ metaklasių, yra tai, kad taip pat galite perduoti atributus kaip raktinių žodžių argumentus metaklasei, pavyzdžiui:

 class Foo(object, metaclass=something, kwarg1=value1, kwarg2=value2): ... 

Perskaitykite toliau pateiktą skyrių, kad sužinotumėte, kaip tai daro Python.

Individualios metaklasės

Pagrindinis metaklasės tikslas yra automatiškai pakeisti klasę, kai jis sukuriamas.

Paprastai tai darote API, kur norite sukurti klases, atitinkančias dabartinį kontekstą.

Įsivaizduokite kvailą pavyzdį, kai nuspręsite, kad visos jūsų modulio klasės turėtų turėti savo atributus didžiosiomis raidėmis. Tai galima padaryti keliais būdais, tačiau vienas iš jų - nustatyti __metaclass__ modulio lygmeniu.

Taigi, visos šio modulio klasės bus sukurtos naudojant šią metaklasę, ir mes tiesiog turime nurodyti metaklasę, kad visi atributai būtų konvertuoti į didžiosios raidės.

Laimei, __metaclass__ iš tikrųjų gali būti bet kuris skambintojas, jis neturi būti formalus klasė (aš žinau, kad kažkas su pavadinimu „vardas“ neturi būti klasė, suprasti, kad ... bet tai naudinga).

Taigi, pradedame nuo paprasto pavyzdžio naudojant funkciją.

 # the metaclass will automatically get passed the same argument # that you usually pass to 'type' def upper_attr(future_class_name, future_class_parents, future_class_attr): """ Return a class object, with the list of its attribute turned into uppercase. """ # pick up any attribute that doesn't start with '__' and uppercase it uppercase_attr = {} for name, val in future_class_attr.items(): if not name.startswith('__'): uppercase_attr[name.upper()] = val else: uppercase_attr[name] = val # let 'type' do the class creation return type(future_class_name, future_class_parents, uppercase_attr) __metaclass__ = upper_attr # this will affect all classes in the module class Foo(): # global __metaclass__ won't work with "object" though # but we can define __metaclass__ here instead to affect only this class # and this will work with "object" children bar = 'bip' print(hasattr(Foo, 'bar')) # Out: False print(hasattr(Foo, 'BAR')) # Out: True f = Foo() print(f.BAR) # Out: 'bip' 

Dabar atlikite tą patį, bet naudojant tikros klasės metaklasę:

 # remember that 'type' is actually a class like 'str' and 'int' # so you can inherit from it class UpperAttrMetaclass(type): # __new__ is the method called before __init__ # it the method that creates the object and returns it # while __init__ just initializes the object passed as parameter # you rarely use __new__, except when you want to control how the object # is created. # here the created object is the class, and we want to customize it # so we override __new__ # you can do some stuff in __init__ too if you wish # some advanced use involves overriding __call__ as well, but we won't # see this def __new__(upperattr_metaclass, future_class_name, future_class_parents, future_class_attr): uppercase_attr = {} for name, val in future_class_attr.items(): if not name.startswith('__'): uppercase_attr[name.upper()] = val else: uppercase_attr[name] = val return type(future_class_name, future_class_parents, uppercase_attr) 

Tačiau tai ne visai PLO. Mes tiesiogiai vadiname type o ne __new__ arba nesikreipiame į tėvą __new__ . Padarykime tai:

 class UpperAttrMetaclass(type): def __new__(upperattr_metaclass, future_class_name, future_class_parents, future_class_attr): uppercase_attr = {} for name, val in future_class_attr.items(): if not name.startswith('__'): uppercase_attr[name.upper()] = val else: uppercase_attr[name] = val # reuse the type.__new__ method # this is basic OOP, nothing magic in there return type.__new__(upperattr_metaclass, future_class_name, future_class_parents, uppercase_attr) 

Galbūt pastebėjote papildomą argumentą upperattr_metaclass . Nėra nieko ypatingo: __new__ visada gauna klasę, kurioje jis yra apibrėžiamas kaip pirmasis parametras. Lygiai taip pat, kaip jūs turite įprastų metodų, kurie gauna pavyzdį kaip pirmąjį parametrą, arba klasių metodų apibrėžimo klasę.

Žinoma, čia vartojami pavadinimai yra ilgi aiškumo sumetimais, bet, kaip ir self , visi argumentai turi sąlyginius pavadinimus. Taigi, tikroji gamybos metaklasė atrodys taip:

 class UpperAttrMetaclass(type): def __new__(cls, clsname, bases, dct): uppercase_attr = {} for name, val in dct.items(): if not name.startswith('__'): uppercase_attr[name.upper()] = val else: uppercase_attr[name] = val return type.__new__(cls, clsname, bases, uppercase_attr) 

Mes galime padaryti jį net švaresniu naudojant super , o tai palengvina paveldėjimą (nes taip, jūs galite turėti metaklases, paveldėti iš metaklasių, paveldėtų iš tipo):

 class UpperAttrMetaclass(type): def __new__(cls, clsname, bases, dct): uppercase_attr = {} for name, val in dct.items(): if not name.startswith('__'): uppercase_attr[name.upper()] = val else: uppercase_attr[name] = val return super(UpperAttrMetaclass, cls).__new__(cls, clsname, bases, uppercase_attr) 

Taip, o „Python 3“, jei skambinate naudodami raktinių žodžių argumentus, pavyzdžiui:

 class Foo(object, metaclass=Thing, kwarg1=value1): ... 

Tai verčia jį į metaklasę, kad ją būtų galima naudoti:

 class Thing(type): def __new__(class, clsname, bases, dct, kwargs1=default): ... 

Tai yra. Metaklasėse nieko nėra.

Kodo, naudojant metaklases, sudėtingumo priežastis nėra metaklasės, bet tai, kad dažniausiai naudojate metaklases sukimui, remdamiesi savimi, manipuliuojant paveldėjimu, kintamaisiais, pvz., __dict__ ir kt.

Iš tiesų, metaklasės yra ypač naudingos juodajai magijai ir todėl sudėtingiems dalykams. Bet savaime jie yra paprasti.

  • perimti klasės kūrimą
  • keisti klasę
  • grąžinkite pakeistą klasę

Kodėl vietoj funkcijų naudojate metaklasės klases?

Kadangi __metaclass__ gali priimti bet kokį __metaclass__ , kodėl jums reikia naudoti klasę, nes tai akivaizdžiai sudėtingesnė?

Yra keletas priežasčių:

  • Tikslas yra aiškus. Perskaitę „ UpperAttrMetaclass(type) , žinote, kas yra toliau.
  • Galite naudoti OOP. Metaklasė gali paveldėti iš metaklasės, ignoruoti pagrindinius metodus. Metaklasės gali naudoti net metaklases.
  • Klasės poklasis bus jo metaklasės atvejai, jei nurodėte metaklasės klasę, bet ne su metaklasės funkcija.
  • Galite geriau struktūrizuoti savo kodą. Jūs niekada nenaudojate metaklasių kažkam tokiam trivialiam, kaip aukščiau pateiktas pavyzdys. Paprastai tai yra kažką sudėtingo. Gebėjimas atlikti kelis metodus ir juos suskirstyti į vieną klasę yra labai naudingas, kad kodą būtų lengviau skaityti.
  • Galite prisijungti prie __new__ , __init__ ir __call__ . Tai leis jums atlikti skirtingus dalykus. Net jei paprastai tai galite padaryti __new__ , kai kurie žmonės tiesiog patogiau naudoti __init__ .
  • Tai vadinama metaklassais, velniškai! Tai turėtų reikšti kažką!

Kodėl naudojate metaklases?

Dabar didelis klausimas. Kodėl jums reikia paslėptų klaidų?

Na, paprastai jūs to nedarote:

Metaklasės yra gilesnės magijos, apie kurias 99% vartotojų niekada neturėtų nerimauti. Jei įdomu, ar jums jų reikia, jums to nereikia (žmonės, kuriems jų tikrai reikia, įsitikinę, kad jiems jų reikia, ir jiems nereikia paaiškinti, kodėl).

Python Guru Tim Peters

Pagrindinis metaklasės naudojimo atvejis yra API sukūrimas. Tipiškas pavyzdys yra Django ORM.

Tai leidžia jums apibrėžti kažką panašaus:

 class Person(models.Model): name = models.CharField(max_length=30) age = models.IntegerField() 

Bet jei tai darote:

 guy = Person(name='bob', age='35') print(guy.age) 

Jis IntegerField grąžinti „ IntegerField objekto. Jis grįš į int Ir netgi gali jį paimti tiesiai iš duomenų bazės.

Tai įmanoma, nes models.Model apibrėžia __metaclass__ ir naudoja magiją, kuri paverčia jūsų nustatytą Person , naudojant paprastus operatorius, į sudėtingą privalomą duomenų bazės lauką.

Django daro kažką sudėtingo paprasta, pateikdama paprastą API ir naudodama metaklases, atkurdama šio API kodą, kad realus darbas būtų užkulisiuose.

Paskutinis žodis

Pirma, žinote, kad klasės yra objektai, galintys sukurti atvejus.

Tiesą sakant, pačios klasės yra atvejai. Metaklasės.

 >>> class Foo(object): pass >>> id(Foo) 142630324 

„Python“ viskas yra objektas, ir jie visi yra metaklasių klasių ar atvejų pavyzdžiai.

Išskyrus type .

type iš tikrųjų yra savo metaklasė. Tai nėra kažkas, ką galite atkurti gryname Pythone, ir tai daroma apgaudinėjant įgyvendinimo lygmeniu.

Antra, metaklasės yra sudėtingos. Galbūt nenorite jų naudoti labai paprastiems klasės pakeitimams. Вы можете изменить классы, используя два разных метода: