Geçenlerde aynı soruyu sormuştum ve birkaç cevap buldum. Bahsedilen kullanım durumlarından birkaçını detaylandırmak ve birkaç yenisini eklemek istediğim için bu konuyu yeniden canlandırmanın sorun olmadığını umuyorum.
Gördüğüm metasınıfların çoğu iki şeyden birini yapıyor:
Kayıt (bir veri yapısına sınıf ekleme):
models = {}
class ModelMetaclass(type):
def __new__(meta, name, bases, attrs):
models[name] = cls = type.__new__(meta, name, bases, attrs)
return cls
class Model(object):
__metaclass__ = ModelMetaclass
Ne zaman alt Model
sınıf yaparsanız , sınıfınız models
sözlüğe kaydedilir:
>>> class A(Model):
... pass
...
>>> class B(A):
... pass
...
>>> models
{'A': <__main__.A class at 0x...>,
'B': <__main__.B class at 0x...>}
Bu, sınıf dekoratörleri ile de yapılabilir:
models = {}
def model(cls):
models[cls.__name__] = cls
return cls
@model
class A(object):
pass
Veya açık bir kayıt işlevi ile:
models = {}
def register_model(cls):
models[cls.__name__] = cls
class A(object):
pass
register_model(A)
Aslında, bu hemen hemen aynıdır: sınıf dekoratörlerinden olumsuz bir şekilde bahsediyorsunuz, ancak bu gerçekten bir sınıftaki bir işlev çağrısı için sözdizimsel şekerden başka bir şey değil, dolayısıyla bu konuda sihir yok.
Her neyse, bu durumda meta sınıfların avantajı, herhangi bir alt sınıf için çalıştıkları için kalıtımdır, oysa diğer çözümler yalnızca açıkça dekore edilmiş veya kayıtlı alt sınıflar için çalışır.
>>> class B(A):
... pass
...
>>> models
{'A': <__main__.A class at 0x...> # No B :(
Yeniden düzenleme (sınıf özniteliklerini değiştirme veya yenilerini ekleme):
class ModelMetaclass(type):
def __new__(meta, name, bases, attrs):
fields = {}
for key, value in attrs.items():
if isinstance(value, Field):
value.name = '%s.%s' % (name, key)
fields[key] = value
for base in bases:
if hasattr(base, '_fields'):
fields.update(base._fields)
attrs['_fields'] = fields
return type.__new__(meta, name, bases, attrs)
class Model(object):
__metaclass__ = ModelMetaclass
Model
Bazı Field
öznitelikleri alt sınıfa ayırıp tanımladığınızda , bunlar adlarıyla birlikte enjekte edilir (örneğin, daha bilgilendirici hata mesajları için) ve bir _fields
sözlüğe (kolay yineleme için, tüm sınıf özniteliklerine ve tüm temel sınıflarına bakmak zorunda kalmadan ) gruplanır. her seferinde öznitelikler):
>>> class A(Model):
... foo = Integer()
...
>>> class B(A):
... bar = String()
...
>>> B._fields
{'foo': Integer('A.foo'), 'bar': String('B.bar')}
Yine, bu bir sınıf dekoratörüyle yapılabilir (kalıtım olmadan):
def model(cls):
fields = {}
for key, value in vars(cls).items():
if isinstance(value, Field):
value.name = '%s.%s' % (cls.__name__, key)
fields[key] = value
for base in cls.__bases__:
if hasattr(base, '_fields'):
fields.update(base._fields)
cls._fields = fields
return cls
@model
class A(object):
foo = Integer()
class B(A):
bar = String()
# B.bar has no name :(
# B._fields is {'foo': Integer('A.foo')} :(
Veya açıkça:
class A(object):
foo = Integer('A.foo')
_fields = {'foo': foo} # Don't forget all the base classes' fields, too!
Okunabilir ve sürdürülebilir meta olmayan programlama savunuculuğunuzun aksine, bu çok daha külfetli, gereksiz ve hataya açık:
class B(A):
bar = String()
# vs.
class B(A):
bar = String('bar')
_fields = {'B.bar': bar, 'A.foo': A.foo}
En yaygın ve somut kullanım durumlarını göz önünde bulundurarak, metasınıf kullanmak zorunda olduğunuz tek durum, sınıf adını veya temel sınıflar listesini değiştirmek istediğiniz zamandır, çünkü tanımlandıktan sonra bu parametreler sınıfa eklenir ve dekoratör yoktur. veya işlev onları çözebilir.
class Metaclass(type):
def __new__(meta, name, bases, attrs):
return type.__new__(meta, 'foo', (int,), attrs)
class Baseclass(object):
__metaclass__ = Metaclass
class A(Baseclass):
pass
class B(A):
pass
print A.__name__ # foo
print B.__name__ # foo
print issubclass(B, A) # False
print issubclass(B, int) # True
Bu, benzer adlara sahip sınıflar veya eksik miras ağaçları tanımlandığında uyarılar yayınlamak için çerçevelerde yararlı olabilir, ancak bu değerleri gerçekten değiştirmek için trollemenin yanı sıra bir neden düşünemiyorum. Belki David Beazley yapabilir.
Her neyse, Python 3'te, metasınıflar ayrıca __prepare__
sınıf gövdesini a haricinde bir eşlemede değerlendirmenize izin veren bir yönteme sahiptir dict
, böylece sıralı öznitelikleri, aşırı yüklenmiş öznitelikleri ve diğer harika harika şeyleri destekler:
import collections
class Metaclass(type):
@classmethod
def __prepare__(meta, name, bases, **kwds):
return collections.OrderedDict()
def __new__(meta, name, bases, attrs, **kwds):
print(list(attrs))
# Do more stuff...
class A(metaclass=Metaclass):
x = 1
y = 2
# prints ['x', 'y'] rather than ['y', 'x']
class ListDict(dict):
def __setitem__(self, key, value):
self.setdefault(key, []).append(value)
class Metaclass(type):
@classmethod
def __prepare__(meta, name, bases, **kwds):
return ListDict()
def __new__(meta, name, bases, attrs, **kwds):
print(attrs['foo'])
# Do more stuff...
class A(metaclass=Metaclass):
def foo(self):
pass
def foo(self, x):
pass
# prints [<function foo at 0x...>, <function foo at 0x...>] rather than <function foo at 0x...>
Sıralı özniteliklerin oluşturma sayaçları ile elde edilebileceğini ve aşırı yüklemenin varsayılan bağımsız değişkenlerle simüle edilebileceğini iddia edebilirsiniz:
import itertools
class Attribute(object):
_counter = itertools.count()
def __init__(self):
self._count = Attribute._counter.next()
class A(object):
x = Attribute()
y = Attribute()
A._order = sorted([(k, v) for k, v in vars(A).items() if isinstance(v, Attribute)],
key = lambda (k, v): v._count)
class A(object):
def _foo0(self):
pass
def _foo1(self, x):
pass
def foo(self, x=None):
if x is None:
return self._foo0()
else:
return self._foo1(x)
Çok daha çirkin olmasının yanı sıra, aynı zamanda daha az esnektir: ya tamsayılar ve dizeler gibi sıralı değişmez nitelikler istiyorsanız? Ya None
için geçerli bir değerse x
?
İşte ilk sorunu çözmenin yaratıcı bir yolu:
import sys
class Builder(object):
def __call__(self, cls):
cls._order = self.frame.f_code.co_names
return cls
def ordered():
builder = Builder()
def trace(frame, event, arg):
builder.frame = frame
sys.settrace(None)
sys.settrace(trace)
return builder
@ordered()
class A(object):
x = 1
y = 'foo'
print A._order # ['x', 'y']
Ve işte ikincisini çözmenin yaratıcı bir yolu:
_undefined = object()
class A(object):
def _foo0(self):
pass
def _foo1(self, x):
pass
def foo(self, x=_undefined):
if x is _undefined:
return self._foo0()
else:
return self._foo1(x)
Ancak bu, basit bir metasınıftan çok, ÇOK büyücü (özellikle beyninizi gerçekten eriten ilki). Demek istediğim, meta sınıflara alışılmadık ve sezgisel olarak bakıyorsunuz, ancak bunlara programlama dillerinde bir sonraki evrim adımı olarak da bakabilirsiniz: sadece zihniyetinizi ayarlamanız gerekir. Sonuçta, işlev işaretçileriyle bir yapı tanımlamak ve onu işlevlerine ilk argüman olarak geçirmek dahil, muhtemelen C'de her şeyi yapabilirsiniz. C ++ 'ı ilk kez gören bir kişi, "Bu sihir nedir? Derleyici neden örtük olarak geçiyor?this
yöntemlere, ancak düzenli ve statik işlevlere değil mi? Tartışmalarınız hakkında açık ve ayrıntılı olmak daha iyidir. "Ama sonra, nesne yönelimli programlama çok daha güçlüdür; ve bu da, uh ... yarı yön odaklı programlama, sanırım. Metasınıfları anlayın, aslında çok basitler, öyleyse neden uygun olduğunda onları kullanmayasınız?
Ve son olarak, metasınıflar radikaldir ve programlama eğlenceli olmalıdır. Standart programlama yapılarını ve tasarım kalıplarını her zaman kullanmak sıkıcı ve sönüktür ve hayal gücünüzü engeller. Biraz yaşa! İşte size bir metameta sınıfı.
class MetaMetaclass(type):
def __new__(meta, name, bases, attrs):
def __new__(meta, name, bases, attrs):
cls = type.__new__(meta, name, bases, attrs)
cls._label = 'Made in %s' % meta.__name__
return cls
attrs['__new__'] = __new__
return type.__new__(meta, name, bases, attrs)
class China(type):
__metaclass__ = MetaMetaclass
class Taiwan(type):
__metaclass__ = MetaMetaclass
class A(object):
__metaclass__ = China
class B(object):
__metaclass__ = Taiwan
print A._label # Made in China
print B._label # Made in Taiwan
Düzenle
Bu oldukça eski bir soru, ancak hala artı oylar alıyor, bu yüzden daha kapsamlı bir cevaba bağlantı ekleyeceğimi düşündüm. Metasınıflar ve kullanımları hakkında daha fazla okumak isterseniz, burada bununla ilgili bir makale yayınladım .