나는 최근에 같은 질문을 받았고 몇 가지 대답을 내놓았습니다. 언급 된 몇 가지 사용 사례에 대해 자세히 설명하고 몇 가지 새로운 사례를 추가하고 싶었으므로이 스레드를 되살려도 괜찮기를 바랍니다.
내가 본 대부분의 메타 클래스는 다음 두 가지 중 하나를 수행합니다.
등록 (데이터 구조에 클래스 추가) :
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
하위 클래스를 만들 때마다 Model
클래스가 models
사전에 등록됩니다 .
>>> class A(Model):
... pass
...
>>> class B(A):
... pass
...
>>> models
{'A': <__main__.A class at 0x...>,
'B': <__main__.B class at 0x...>}
이것은 클래스 데코레이터로도 가능합니다 :
models = {}
def model(cls):
models[cls.__name__] = cls
return cls
@model
class A(object):
pass
또는 명시 적 등록 기능 사용 :
models = {}
def register_model(cls):
models[cls.__name__] = cls
class A(object):
pass
register_model(A)
실제로 이것은 거의 동일합니다. 클래스 데코레이터를 불리하게 언급하지만 실제로는 클래스의 함수 호출에 대한 구문 설탕에 지나지 않으므로 마술은 없습니다.
어쨌든,이 경우 메타 클래스의 장점은 모든 서브 클래스에서 작동하기 때문에 상속성입니다. 반면 다른 솔루션은 명시 적으로 장식되거나 등록 된 서브 클래스에서만 작동합니다.
>>> class B(A):
... pass
...
>>> models
{'A': <__main__.A class at 0x...> # No B :(
리팩토링 (클래스 속성 수정 또는 새 속성 추가) :
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
하고 일부 Field
속성을 정의 할 때마다 이름이 삽입되고 (예 : 더 많은 정보를 제공하는 오류 메시지를 위해) _fields
사전에 그룹화됩니다 (모든 클래스 속성과 모든 기본 클래스를 살펴볼 필요없이 쉽게 반복 할 수 있도록). 매번 속성) :
>>> class A(Model):
... foo = Integer()
...
>>> class B(A):
... bar = String()
...
>>> B._fields
{'foo': Integer('A.foo'), 'bar': String('B.bar')}
다시 말하지만, 이것은 클래스 데코레이터로 (상속없이) 할 수 있습니다 :
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')} :(
또는 명시 적으로 :
class A(object):
foo = Integer('A.foo')
_fields = {'foo': foo} # Don't forget all the base classes' fields, too!
읽기 쉽고 유지 관리가 가능한 비 메타 프로그래밍에 대한 귀하의 옹호와는 달리 이것은 훨씬 더 번거롭고 중복되며 오류가 발생하기 쉽습니다.
class B(A):
bar = String()
# vs.
class B(A):
bar = String('bar')
_fields = {'B.bar': bar, 'A.foo': A.foo}
가장 일반적이고 구체적인 사용 사례를 고려했을 때 메타 클래스를 절대적으로 사용해야하는 유일한 경우는 클래스 이름이나 기본 클래스 목록을 수정하려는 경우입니다. 일단 정의되면 이러한 매개 변수가 클래스에 구워지고 데코레이터가 없기 때문입니다. 또는 기능은 그들을 구울 수 있습니다.
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
이것은 유사한 이름을 가진 클래스 나 불완전한 상속 트리가 정의 될 때마다 경고를 발행하는 프레임 워크에서 유용 할 수 있지만 실제로 이러한 값을 변경하는 트롤링 외에 이유를 생각할 수 없습니다. 아마도 David Beazley는 할 수 있습니다.
어쨌든 Python 3에서 메타 클래스에는 __prepare__
메서드가있어 클래스 본문을 a 이외의 매핑으로 평가할 수 dict
있으므로 순서가 지정된 속성, 오버로드 된 속성 및 기타 멋진 항목을 지원합니다.
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...>
정렬 된 속성은 생성 카운터를 사용하여 얻을 수 있으며 오버로딩은 기본 인수로 시뮬레이션 할 수 있습니다.
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)
훨씬 더 추악 할뿐만 아니라 유연성도 떨어집니다. 정수 및 문자열과 같은 순서가 지정된 리터럴 속성을 원한다면 어떨까요? 어떤 경우에하는 것은 None
유효한 값입니다 x
?
다음은 첫 번째 문제를 해결하는 창의적인 방법입니다.
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']
두 번째 문제를 해결하는 창의적인 방법은 다음과 같습니다.
_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)
그러나 이것은 단순한 메타 클래스 (특히 뇌를 정말로 녹이는 첫 번째 메타 클래스)보다 훨씬 더 부두교입니다. 내 요점은 메타 클래스를 생소하고 직관적이지 않은 것으로 보지만 프로그래밍 언어에서 다음 단계의 진화로 볼 수도 있다는 것입니다. 사고 방식을 조정하기 만하면됩니다. 결국 함수 포인터로 구조체를 정의하고이를 함수에 대한 첫 번째 인수로 전달하는 것을 포함하여 C에서 모든 작업을 수행 할 수 있습니다. C ++를 처음 보는 사람은 "이 마법은 무엇입니까? 컴파일러가 암시 적으로 전달하는 이유는 무엇입니까?this
메서드에는 있지만 일반 및 정적 함수에는 적용되지 않습니까? 당신의 주장에 대해 명시적이고 장황하게 말하는 것이 더 낫습니다. "그러나 객체 지향 프로그래밍은 일단 이해하면 훨씬 더 강력합니다. 그리고 이것은 어 ... 준 종횡 지향 프로그래밍입니다. 메타 클래스를 이해하면 실제로 매우 간단합니다. 편리 할 때 사용하지 않겠습니까?
마지막으로, 메타 클래스는 rad이고 프로그래밍은 재미 있어야합니다. 항상 표준 프로그래밍 구조와 디자인 패턴을 사용하는 것은 지루하고 영감을주지 않으며 상상력을 방해합니다. 조금 살다! 여기 당신만을위한 metametaclass가 있습니다.
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
편집하다
이것은 꽤 오래된 질문이지만 여전히 찬성 투표를 받고 있으므로보다 포괄적 인 답변에 대한 링크를 추가 할 것이라고 생각했습니다. 메타 클래스와 그 용도에 대해 더 알고 싶다면 여기에 기사를 게시했습니다 .