파이썬에서 메타 클래스 란 무엇입니까?


답변:


2869

메타 클래스는 클래스의 클래스입니다. 클래스는 클래스의 인스턴스 (예 : 객체)의 동작을 정의하고 메타 클래스는 클래스의 동작 방식을 정의합니다. 클래스는 메타 클래스의 인스턴스입니다.

파이썬에서는 메타 클래스 (예 : Jerub 쇼)에 대해 임의의 호출 가능 항목을 사용할 수 있지만 더 나은 방법은 실제 클래스 자체로 만드는 것입니다. type파이썬에서 일반적인 메타 클래스입니다. type그 자체가 클래스이며 자체 유형입니다. type파이썬에서 순수하게 무언가를 다시 만들 수는 없지만 파이썬은 약간의 속임수를 씁니다. 파이썬으로 자신 만의 메타 클래스를 만들려면 정말로 서브 클래스를 만들고 싶습니다 type.

메타 클래스는 클래스 팩토리로 가장 일반적으로 사용됩니다. 클래스를 호출하여 객체를 만들면 Python은 메타 클래스를 호출하여 새 클래스를 생성합니다 ( 'class'문을 실행할 때). 메타 클래스 는 일반 __init____new__메소드 와 결합 하여 클래스를 만들 때 새 클래스를 일부 레지스트리에 등록하거나 클래스를 완전히 다른 것으로 대체하는 것과 같이 '추가 항목'을 수행 할 수 있습니다.

class문장이 실행될 때 , 파이썬은 먼저 class문장 의 본문을 일반적인 코드 블록으로 실행합니다. 결과 네임 스페이스 (dict)는 클래스의 속성을 보유합니다. 메타 클래스는 클래스의 __metaclass__속성 (메타 클래스가 상 속됨), 클래스 의 속성 (있는 경우) 또는 __metaclass__글로벌 변수를보고 결정됩니다. 그런 다음 메타 클래스가 클래스의 이름, 기본 및 속성과 함께 호출되어이를 인스턴스화합니다.

그러나 메타 클래스는 실제로 팩토리가 아니라 클래스 의 유형 을 정의 하므로 더 많은 작업을 수행 할 수 있습니다. 예를 들어 메타 클래스에 일반 메소드를 정의 할 수 있습니다. 이러한 메타 클래스 메소드는 인스턴스없이 클래스에서 호출 될 수 있다는 점에서 클래스 메소드와 비슷하지만 클래스의 인스턴스에서 호출 할 수 없다는 점에서 클래스 메소드와는 다릅니다. 메타 클래스 type.__subclasses__()에있는 메소드의 예입니다 type. 당신은 또한 같은 정상 '마법'방법을 정의 할 수 있습니다 __add__, __iter____getattr__, 구현 또는 변경하는 방법 클래스 동작합니다 할 수 있습니다.

다음은 비트와 조각의 집계 된 예입니다.

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's called 'AutoClass'
print ExampleSibling
print ExampleSibling.__mro__

13
class A(type):pass<NEWLINE>class B(type,metaclass=A):pass<NEWLINE>b.__class__ = b
pppery

20
ppperry는 분명히 유형 자체를 메타 클래스로 사용하지 않고 유형을 다시 만들 수 없음을 의미했습니다. 말할만큼 공평합니다.
Holle van

3
Example 클래스의 인스턴스에서 unregister ()를 호출해서는 안됩니까?
Ciasto piekarz

5
참고 __metaclass__파이썬 3에서 파이썬 3 사용에서 지원되지는 class MyObject(metaclass=MyType)참조 python.org/dev/peps/pep-3115 아래의 대답을.
BlackShift

2
이 문서 는 메타 클래스 선택 방법을 설명 합니다 . 메타 클래스는 파생 된만큼 상속되지 않습니다. 메타 클래스를 지정하면 각 기본 클래스 메타 클래스의 하위 유형이어야합니다. 그렇지 않으면 서로 다른 기본 클래스 메타 클래스의 하위 유형 인 기본 클래스 메타 클래스를 사용합니다. 가능성이 있습니다 유효한 메타 클래스가 발견 될 수 있으며, 정의가 실패합니다.
chepner

6815

객체로서의 클래스

메타 클래스를 이해하기 전에 파이썬에서 클래스를 마스터해야합니다. 그리고 파이썬은 스몰 토크 언어에서 빌린 클래스가 무엇인지에 대해 매우 독특합니다.

대부분의 언어에서 클래스는 객체를 생성하는 방법을 설명하는 코드 조각 일뿐입니다. 파이썬에서도 마찬가지입니다.

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

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

그러나 클래스는 파이썬보다 더 많습니다. 클래스도 객체입니다.

예, 물건.

키워드를 사용하자마자 classPython은 키워드 를 실행하고 OBJECT를 만듭니다. 지시

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

이름이 "ObjectCreator"인 오브젝트를 메모리에 작성합니다.

이 객체 (클래스) 자체는 객체 (인스턴스)를 만들 수 있으며 이것이 클래스 인 이유 입니다.

그러나 여전히 객체이므로 다음과 같습니다.

  • 변수에 할당 할 수 있습니다
  • 당신은 그것을 복사 할 수 있습니다
  • 속성을 추가 할 수 있습니다
  • 함수 매개 변수로 전달할 수 있습니다

예 :

>>> print(ObjectCreator) # you can print a class because it's 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>

동적으로 클래스 만들기

클래스는 객체이기 때문에 다른 객체처럼 즉시 만들 수 있습니다.

먼저 다음을 사용하여 함수에서 클래스를 만들 수 있습니다 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>

그러나 여전히 전체 수업을 직접 작성해야하기 때문에 그렇게 역동적이지는 않습니다.

클래스는 객체이므로 무언가로 생성해야합니다.

class키워드 를 사용하면 Python이이 객체를 자동으로 만듭니다. 그러나 파이썬에서 대부분의 것들과 마찬가지로 수동으로 수행하는 방법을 제공합니다.

기능을 기억 type하십니까? 객체가 어떤 유형인지 알 수있는 좋은 오래된 기능 :

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

글쎄, type완전히 다른 능력을 가지고, 그것은 또한 즉시 수업을 만들 수 있습니다. type클래스의 설명을 매개 변수로 사용하여 클래스를 반환 할 수 있습니다.

(나는 동일한 함수가 전달하는 매개 변수에 따라 두 가지 완전히 다른 용도를 가질 수 있다는 것은 바보 같은 사실입니다. Python의 하위 호환성으로 인해 문제가됩니다)

type 이 방법으로 작동합니다 :

type(name, bases, attrs)

어디:

  • name: 수업 명
  • bases: 부모 클래스의 튜플 (상속을 위해 비어있을 수 있음)
  • attrs: 속성 이름 및 값을 포함하는 사전

예 :

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

이 방법으로 수동으로 만들 수 있습니다.

>>> 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>

"MyShinyClass"를 클래스 이름으로 사용하고 클래스 참조를 보유하는 변수로 사용합니다. 그것들은 다를 수 있지만 문제를 복잡하게 할 이유는 없습니다.

type클래스의 속성을 정의하기위한 사전을 허용합니다. 그래서:

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

다음과 같이 번역 될 수 있습니다.

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

그리고 일반 클래스로 사용됩니다.

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

물론 당신은 그것을 상속받을 수 있습니다.

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

될 것입니다 :

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

결국 클래스에 메소드를 추가하려고합니다. 적절한 서명으로 함수를 정의하고 속성으로 지정하십시오.

>>> 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

그리고 일반적으로 생성 된 클래스 객체에 메소드를 추가하는 것처럼 클래스를 동적으로 생성 한 후에 더 많은 메소드를 추가 할 수 있습니다.

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

우리가 어디로 가고 있는지 알 수 있습니다. 파이썬에서 클래스는 객체이며 동적으로 클래스를 즉석에서 만들 수 있습니다.

이것은 키워드를 사용할 때 파이썬이하는 class일이며 메타 클래스를 사용하여 그렇게합니다.

메타 클래스 (최종)

메타 클래스는 클래스를 만드는 '재료'입니다.

객체를 생성하기 위해 클래스를 정의합니다.

그러나 파이썬 클래스는 객체라는 것을 배웠습니다.

메타 클래스가 이러한 객체를 만드는 것입니다. 그들은 수업의 수업입니다, 당신은 이런 식으로 그들을 그림으로 만들 수 있습니다

MyClass = MetaClass()
my_object = MyClass()

당신은 type이것을 다음과 같이 할 수 있다는 것을 보았습니다 .

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

함수 type가 실제로 메타 클래스 이기 때문 입니다. type파이썬은 배후에서 모든 클래스를 만드는 데 사용하는 메타 클래스입니다.

이제 도대체 왜 소문자로 쓰여 졌는지 궁금하지 Type않습니까?

글쎄, 그것은 str문자열 객체 int를 만드는 클래스 와 정수 객체를 만드는 클래스 와의 일관성 문제라고 생각 합니다. type클래스 객체를 만드는 클래스입니다.

__class__속성 을 확인하면 알 수 있습니다 .

모든 것은 모든 것을 의미합니다. 파이썬의 객체입니다. 여기에는 정수, 문자열, 함수 및 클래스가 포함됩니다. 그들 모두는 대상입니다. 그리고 그들 모두는 수업에서 만들어졌습니다.

>>> 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'>

자, 무엇 __class__모든의는 __class__?

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

따라서 메타 클래스는 클래스 객체를 만드는 것입니다.

원한다면 '클래스 팩토리'라고 부를 수 있습니다.

type 파이썬이 사용하는 내장 메타 클래스이지만, 물론 자신 만의 메타 클래스를 만들 수 있습니다.

__metaclass__속성

Python 2에서는 __metaclass__클래스를 작성할 때 속성을 추가 할 수 있습니다 (Python 3 구문은 다음 섹션 참조).

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

그렇게하면 파이썬은 메타 클래스를 사용하여 클래스를 만듭니다 Foo.

조심해, 까다 롭다.

class Foo(object)먼저 작성 하지만 클래스 객체 Foo는 아직 메모리에 작성되지 않았습니다.

파이썬은 __metaclass__클래스 정의에서 찾을 것입니다 . 발견되면이를 사용하여 객체 클래스를 만듭니다 Foo. 그렇지 않으면 type클래스를 만드는 데 사용 됩니다.

여러 번 읽어보세요.

할 때 :

class Foo(Bar):
    pass

파이썬은 다음을 수행합니다.

__metaclass__속성이 Foo있습니까?

예를 메모리에 클래스 객체를 생성하는 경우 이름으로, (I 클래스 객체 나 여기에 숙박했다) Foo에 무엇을 사용하여 __metaclass__.

파이썬이 찾을 수 없다면 , MODULE 레벨에서을 __metaclass__찾고 __metaclass__같은 것을 시도합니다 (그러나 기본적으로 구식 클래스를 상속받지 않는 클래스에 대해서만).

그 다음은 어떤 찾을 수없는 경우 __metaclass__에 모두를, 그것은 사용 Bar의 (첫 번째 부모) (기본이 될 수있는 자신의 메타 클래스를 type클래스 객체를 생성).

여기서 __metaclass__속성이 상속되지 않고 부모 ( Bar.__class__) 의 메타 클래스가 상속 된다는 점에주의하십시오 . 경우 Bar사용 된 __metaclass__만든 속성 Bartype()(와하지를 type.__new__()), 서브 클래스는 행동 것을 상속하지 않습니다.

이제 큰 문제는 무엇을 넣을 수 __metaclass__있습니까?

답은 클래스를 만들 수있는 것입니다.

그리고 무엇이 클래스를 만들 수 있습니까? type또는 서브 클래스로 사용하거나 사용하는 모든 것.

파이썬 3의 메타 클래스

메타 클래스를 설정하는 구문이 Python 3에서 변경되었습니다.

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

즉, __metaclass__기본 클래스 목록의 키워드 인수를 위해 속성이 더 이상 사용되지 않습니다.

그러나 메타 클래스의 동작은 거의 동일하게 유지됩니다 .

파이썬 3에서 메타 클래스에 추가 된 한 가지는 키워드 인수로 속성을 메타 클래스에 전달할 수 있다는 것입니다.

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

파이썬이 이것을 어떻게 처리하는지 아래 섹션을 읽으십시오.

커스텀 메타 클래스

메타 클래스의 주요 목적은 클래스가 생성 될 때 클래스를 자동으로 변경하는 것입니다.

일반적으로 현재 컨텍스트와 일치하는 클래스를 작성하려는 API에 대해이 작업을 수행합니다.

모듈의 모든 클래스에 특성이 대문자로 작성되어야한다고 결정하는 어리석은 예를 상상해보십시오. 여러 가지 방법이 있지만 한 가지 방법은 __metaclass__모듈 수준에서 설정 하는 것입니다.

이런 식으로이 모듈의 모든 클래스는이 메타 클래스를 사용하여 생성되며 모든 속성을 대문자로 바꾸도록 메타 클래스에 지시하면됩니다.

운 좋게도 __metaclass__실제로 호출 가능 할 수는 있지만 공식적인 클래스 일 필요는 없습니다 (이름에 'class'가있는 클래스는 클래스 일 필요는 없습니다.하지만 도움이됩니다).

함수를 사용하여 간단한 예부터 시작하겠습니다.

# 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_attrs):
    """
      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_attrs = {
        attr if attr.startswith("__") else attr.upper(): v
        for attr, v in future_class_attrs.items()
    }

    # let `type` do the class creation
    return type(future_class_name, future_class_parents, uppercase_attrs)

__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'

점검 해보자:

>>> hasattr(Foo, 'bar')
False
>>> hasattr(Foo, 'BAR')
True
>>> Foo.BAR
'bip'

이제 정확히 동일하게 수행하지만 메타 클래스에 실제 클래스를 사용하십시오.

# 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's 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_attrs):
        uppercase_attrs = {
            attr if attr.startswith("__") else attr.upper(): v
            for attr, v in future_class_attrs.items()
        }
        return type(future_class_name, future_class_parents, uppercase_attrs)

위의 내용을 다시 작성하지만 더 짧고 현실적인 변수 이름을 사용하여 의미를 알 수 있습니다.

class UpperAttrMetaclass(type):
    def __new__(cls, clsname, bases, attrs):
        uppercase_attrs = {
            attr if attr.startswith("__") else attr.upper(): v
            for attr, v in attrs.items()
        }
        return type(clsname, bases, uppercase_attrs)

당신은 여분의 주장을 알아 차렸을 것 cls입니다. 특별한 것은 없습니다 : __new__항상 정의 된 클래스를 첫 번째 매개 변수로받습니다. 그냥 당신이이 같은 self첫 번째 매개 변수 또는 클래스 메소드의 정의 클래스로 인스턴스를받을 일반 메소드.

그러나 이것은 적절한 OOP가 아닙니다. 우리는 type직접 전화를하고 부모의 것을 재정의하거나 전화하지 않습니다 __new__. 대신 해보자 :

class UpperAttrMetaclass(type):
    def __new__(cls, clsname, bases, attrs):
        uppercase_attrs = {
            attr if attr.startswith("__") else attr.upper(): v
            for attr, v in attrs.items()
        }
        return type.__new__(cls, clsname, bases, uppercase_attrs)

super상속을 용이하게하는 을 사용하여 더 깔끔하게 만들 수 있습니다.

class UpperAttrMetaclass(type):
    def __new__(cls, clsname, bases, attrs):
        uppercase_attrs = {
            attr if attr.startswith("__") else attr.upper(): v
            for attr, v in attrs.items()
        }
        return super(UpperAttrMetaclass, cls).__new__(
            cls, clsname, bases, uppercase_attrs)

아, 그리고 파이썬 3에서는 다음과 같이 키워드 인수 로이 호출을 수행하십시오.

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

그것을 사용하기 위해 메타 클래스에서 이것을 번역합니다.

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

그게 다야. 메타 클래스에 대해서는 더 이상 아무것도 없습니다.

메타 클래스를 사용하는 코드의 복잡성 뒤에있는 이유는 메타 클래스 때문이 아닙니다. 일반적으로 메타 클래스를 사용하여 내성, 상속 조작,와 같은 var __dict__등에 의존하는 트위스트 된 항목을 수행하기 때문입니다 .

실제로 메타 클래스는 블랙 매직을 수행하는 데 특히 유용하므로 복잡한 작업을 수행하는 데 특히 유용합니다. 그러나 그 자체로는 간단합니다.

  • 클래스 생성을 가로 채다
  • 수업을 수정하다
  • 수정 된 클래스를 반환

함수 대신 메타 클래스 클래스를 사용하는 이유는 무엇입니까?

__metaclass__호출 가능한 것을 받아 들일 수 있기 때문에 왜 더 복잡한 클래스를 사용해야합니까?

몇 가지 이유가 있습니다.

  • 의도는 분명하다. 당신이 읽을 때 UpperAttrMetaclass(type), 당신은 무엇을 따를 지 알고
  • OOP를 사용할 수 있습니다. 메타 클래스는 메타 클래스에서 상속 할 수 있고 부모 메소드를 대체 할 수 있습니다. 메타 클래스는 메타 클래스를 사용할 수도 있습니다.
  • 메타 클래스 클래스를 지정했지만 메타 클래스 함수가 ​​아닌 클래스의 서브 클래스는 메타 클래스의 인스턴스가됩니다.
  • 코드를 더 잘 구성 할 수 있습니다. 위의 예제처럼 사소한 것에 메타 클래스를 사용하지 마십시오. 일반적으로 복잡한 것입니다. 여러 메소드를 작성하고 하나의 클래스로 그룹화하는 기능이 있으면 코드를보다 쉽게 ​​읽을 수 있습니다.
  • 당신은에 연결할 수 있습니다 __new__, __init__하고 __call__. 다른 일을 할 수 있습니다. 일반적으로 모든 것을 할 수 있지만 __new__어떤 사람들은 사용하기가 더 편합니다 __init__.
  • 이것을 메타 클래스라고합니다. 뭔가를 의미해야합니다!

왜 메타 클래스를 사용 하시겠습니까?

이제 큰 질문입니다. 왜 모호한 오류가 발생하기 쉬운 기능을 사용 하시겠습니까?

글쎄, 보통 당신은하지 않습니다 :

메타 클래스는 99 %의 사용자가 걱정할 필요가없는 더 깊은 마법입니다. 필요한지 궁금하다면 필요하지 않습니다 (실제로 필요한 사람들은 필요하다는 것을 확실하게 알고 있으며 이유에 대한 설명이 필요하지 않습니다).

파이썬 전문가 팀 피터스

메타 클래스의 주요 사용 사례는 API를 만드는 것입니다. 이것의 전형적인 예는 Django ORM입니다. 다음과 같이 정의 할 수 있습니다.

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

그러나 이렇게하면 :

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

IntegerField객체를 반환하지 않습니다 . 를 반환 int하고 데이터베이스에서 직접 가져올 수도 있습니다.

이것은 간단한 문장으로 정의한 것을 데이터베이스 필드에 대한 복잡한 고리로 바꾸는 마법을 사용하고 models.Model정의 하기 때문에 가능 합니다.__metaclass__Person

Django는 간단한 API를 노출하고 메타 클래스를 사용하여이 API에서 코드를 다시 작성하여 배후에서 실제 작업을 수행함으로써 복잡한 것을 단순하게 만듭니다.

마지막 단어

먼저 클래스는 인스턴스를 생성 할 수있는 객체라는 것을 알고 있습니다.

실제로 클래스 자체는 실례입니다. 메타 클래스

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

모든 것은 파이썬의 객체이며, 모두 클래스의 인스턴스 또는 메타 클래스의 인스턴스입니다.

제외 type.

type실제로는 자체 메타 클래스입니다. 이것은 순수한 파이썬에서 재현 할 수있는 것이 아니며 구현 수준에서 약간의 부정 행위로 수행됩니다.

둘째, 메타 클래스가 복잡합니다. 매우 간단한 수업 변경에는 사용하지 않을 수도 있습니다. 두 가지 기술을 사용하여 클래스를 변경할 수 있습니다.

수업 변경이 필요한 시간의 99 %는이를 사용하는 것이 좋습니다.

그러나 시간의 98 %는 수업 변경이 전혀 필요하지 않습니다.


30
장고 에서는 앞서 언급 한 메타 클래스 마술을 수행 하는 클래스를 models.Model사용하는 __metaclass__것이 아니라 오히려 클래스 class Model(metaclass=ModelBase):를 참조하는 것으로 보입니다 ModelBase. 좋은 포스트! Django 소스는 다음과 같습니다 : github.com/django/django/blob/master/django/db/models/…
Max Goodridge

15
<< 여기서 __metaclass__속성은 상속되지 않으며 부모 ( Bar.__class__) 의 메타 클래스가 상속 된다는 점에주의하십시오 . 경우 Bar사용 된 __metaclass__만든 속성 Bartype()(와하지를 type.__new__(). 당신은 / 사람이 조금 더 깊은이 구절 설명해 주시겠습니까 -), 서브 클래스는 동작 >> 있음을 상속하지 않습니다?
petrux

15
@MaxGoodridge 이것이 메타 클래스를위한 파이썬 3 문법입니다. 참조 파이썬 3.6 데이터 모델 VS 파이썬 2.7 데이터 모델
TBBle

2
Now you wonder why the heck is it written in lowercase, and not Type?이 C로 구현있어 잘 때문에 - - 그것은 defaultdict가 OrderedDict 동안 소문자 같은 이유 (파이썬 2) 정상 낙타 표기법 인
Mr_and_Mrs_D

15
커뮤니티 위키 답변입니다 (따라서 수정 / 개선으로 댓글을 작성한 사람들은 자신의 의견이 정확하다면 답변에 대한 댓글 편집을 고려할 수 있습니다).
Brōtsyorfuzthrāx

403

이 답변은 2008 년에 작성된 Python 2.x에 대한 것이며 메타 클래스는 3.x에서 약간 다릅니다.

메타 클래스는 '클래스'가 작동하는 비밀 소스입니다. 새 스타일 객체의 기본 메타 클래스는 'type'입니다.

class type(object)
  |  type(object) -> the object's type
  |  type(name, bases, dict) -> a new type

메타 클래스는 3 개의 인수를 취합니다. ' name ', ' bases '및 ' dict '

여기 비밀이 시작됩니다. 이 예제 클래스 정의에서 이름,베이스 및 딕션의 출처를 찾으십시오.

class ThisIsTheName(Bases, Are, Here):
    All_the_code_here
    def doesIs(create, a):
        dict

' class : '가 어떻게 그것을 호출 하는지 보여줄 메타 클래스를 정의하자 .

def test_metaclass(name, bases, dict):
    print 'The Class Name is', name
    print 'The Class Bases are', bases
    print 'The dict has', len(dict), 'elems, the keys are', dict.keys()

    return "yellow"

class TestName(object, None, int, 1):
    __metaclass__ = test_metaclass
    foo = 1
    def baz(self, arr):
        pass

print 'TestName = ', repr(TestName)

# output => 
The Class Name is TestName
The Class Bases are (<type 'object'>, None, <type 'int'>, 1)
The dict has 4 elems, the keys are ['baz', '__module__', 'foo', '__metaclass__']
TestName =  'yellow'

그리고 이제 실제로 무언가를 의미하는 예제입니다. 그러면 목록의 변수가 클래스에서 "속성"으로 설정되고 없음으로 설정됩니다.

def init_attributes(name, bases, dict):
    if 'attributes' in dict:
        for attr in dict['attributes']:
            dict[attr] = None

    return type(name, bases, dict)

class Initialised(object):
    __metaclass__ = init_attributes
    attributes = ['foo', 'bar', 'baz']

print 'foo =>', Initialised.foo
# output=>
foo => None

Initialised메타 클래스를 가짐으로써 얻는 마법의 행동은 init_attributes의 서브 클래스로 전달되지 않습니다 Initialised.

다음은 클래스를 만들 때 작업을 수행하는 메타 클래스를 만들기 위해 'type'을 서브 클래 싱하는 방법을 보여주는보다 구체적인 예입니다. 이것은 매우 까다 롭습니다.

class MetaSingleton(type):
    instance = None
    def __call__(cls, *args, **kw):
        if cls.instance is None:
            cls.instance = super(MetaSingleton, cls).__call__(*args, **kw)
        return cls.instance

class Foo(object):
    __metaclass__ = MetaSingleton

a = Foo()
b = Foo()
assert a is b

169

다른 사람들은 메타 클래스의 작동 방식과 파이썬 유형 시스템에 어떻게 적합한 지 설명했습니다. 다음은 이들이 사용할 수있는 예입니다. 필자가 작성한 테스트 프레임 워크에서 클래스가 정의 된 순서를 추적하여 나중에이 순서대로 클래스를 인스턴스화 할 수 있기를 원했습니다. 메타 클래스를 사용하여 가장 쉬운 방법이라는 것을 알았습니다.

class MyMeta(type):

    counter = 0

    def __init__(cls, name, bases, dic):
        type.__init__(cls, name, bases, dic)
        cls._order = MyMeta.counter
        MyMeta.counter += 1

class MyType(object):              # Python 2
    __metaclass__ = MyMeta

class MyType(metaclass=MyMeta):    # Python 3
    pass

하위 클래스 인 클래스는 클래스가 정의 된 순서를 기록 MyType하는 클래스 속성을 가져 _order옵니다.


예를 주셔서 감사합니다. 왜 당신이 누구가, MyBase에서 상속보다 쉽게 찾을 않았다 __init__(self)라고 type(self)._order = MyBase.counter; MyBase.counter += 1?
Michael Gundlach

1
인스턴스가 아닌 클래스 자체에 번호를 매기를 원했습니다.
kindall

알았어 감사. 내 코드는 모든 인스턴스화에서 MyType의 특성을 재설정하고 MyType 인스턴스가 생성되지 않은 경우 특성을 설정하지 않습니다. 죄송합니다. (그리고 클래스 속성도 작동 할 수 있지만 메타 클래스와 달리 카운터를 저장할 장소는 분명하지 않습니다.)
Michael Gundlach

1
이것은 유쾌한 흥미로운 예입니다. 특히 메타 클래스가 왜이를 필요로하는지 알 수 있기 때문에 특정한 어려움에 대한 해결책을 제공 할 수 있기 때문입니다. OTOH 나는 누군가가 클래스가 정의 된 순서대로 객체를 인스턴스화해야한다는 것을 확신하기 위해 고심하고 있습니다 : 나는 우리가 그 말을해야한다고 생각합니다 :).
마이크 설치류

159

메타 클래스의 한 가지 용도는 인스턴스에 새로운 속성과 메서드를 자동으로 추가하는 것입니다.

예를 들어 Django models 을 보면 그 정의가 약간 혼란스러워 보입니다. 클래스 속성 만 정의하는 것처럼 보입니다.

class Person(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)

그러나 런타임시 Person 오브젝트는 모든 종류의 유용한 메소드로 채워집니다. 놀라운 메타 클래스에 대한 소스 를 참조하십시오 .


6
A를 새로운 속성과 메서드를 추가 메타 클래스를 사용하지 않습니다 클래스 가 아닌 인스턴스? 내가 이해하는 한 메타 클래스는 클래스 자체를 변경하므로 인스턴스는 변경된 클래스에 따라 다르게 구성 될 수 있습니다. 메타 클래스의 본질을 얻으려는 사람들에게는 약간 오해의 소지가 있습니다. 인스턴스에 유용한 메소드를 갖는 것은 정상적인 상속에 의해 달성 될 수 있습니다. 그러나 장고 코드에 대한 예는 훌륭합니다.
trixn

119

메타 클래스 프로그래밍에 대한 ONLamp 소개는 잘 작성되어 있으며 이미 몇 년이 지났음에도 불구하고 주제에 대한 좋은 소개를 제공합니다.

http://www.onlamp.com/pub/a/python/2003/04/17/metaclasses.html (에 보관 https://web.archive.org/web/20080206005253/http://www.onlamp. com / pub / a / python / 2003 / 04 / 17 / metaclasses.html )

한마디로 : 클래스는 인스턴스 생성을위한 청사진이고, 메타 클래스는 클래스 생성을위한 청사진입니다. 파이썬 클래스에서는이 동작을 가능하게하기 위해 일류 객체 여야한다는 것을 쉽게 알 수 있습니다.

필자는 직접 작성한 적이 없지만 장고 프레임 워크 에서 메타 클래스를 가장 잘 사용하는 방법 중 하나를 볼 수 있다고 생각 합니다. 모델 클래스는 메타 클래스 접근 방식을 사용하여 선언적인 스타일의 새 모델 또는 양식 클래스를 작성할 수 있습니다. 메타 클래스가 클래스를 작성하는 동안 모든 멤버는 클래스 자체를 사용자 정의 할 수 있습니다.

말에 남은 것은입니다 : 당신이 메타 클래스가 무엇인지 모른다면, 당신은 확률 을 필요로하지 않습니다는 99 %입니다.


109

메타 클래스 란 무엇입니까? 무엇을 위해 사용합니까?

TLDR : 메타 클래스는 클래스와 마찬가지로 클래스의 동작을 인스턴스화하고 정의하며 인스턴스의 동작을 정의합니다.

의사 코드 :

>>> Class(...)
instance

위의 내용은 익숙해야합니다. 글쎄, 어디서 Class온거야? 메타 클래스 (의사 코드)의 인스턴스입니다.

>>> Metaclass(...)
Class

실제 코드에서는 기본 메타 클래스 인 type클래스를 인스턴스화하고 클래스를 얻는 데 필요한 모든 것을 전달할 수 있습니다 .

>>> type('Foo', (object,), {}) # requires a name, bases, and a namespace
<class '__main__.Foo'>

다르게 퍼팅

  • 메타 클래스가 클래스에 대한 클래스는 인스턴스에 대한 것입니다.

    객체를 인스턴스화하면 인스턴스가 생성됩니다.

    >>> object()                          # instantiation of class
    <object object at 0x7f9069b4e0b0>     # instance

    마찬가지로 기본 메타 클래스로 클래스를 명시 적으로 정의하면 type인스턴스화됩니다.

    >>> type('Object', (object,), {})     # instantiation of metaclass
    <class '__main__.Object'>             # instance
  • 다시 말해서 클래스는 메타 클래스의 인스턴스입니다.

    >>> isinstance(object, type)
    True
  • 세 번째 방법으로 메타 클래스는 클래스의 클래스입니다.

    >>> type(object) == type
    True
    >>> object.__class__
    <class 'type'>

클래스 정의를 작성하고 파이썬이이를 실행하면 메타 클래스를 사용하여 클래스 객체를 인스턴스화합니다 (그러면 해당 클래스의 인스턴스를 인스턴스화하는 데 사용됨).

클래스 정의를 사용하여 사용자 정의 객체 인스턴스의 동작 방식을 변경할 수있는 것처럼 메타 클래스 클래스 정의를 사용하여 클래스 객체의 동작 방식을 변경할 수 있습니다.

그들은 무엇을 위해 사용될 수 있습니까? 로부터 문서 :

메타 클래스의 잠재적 용도는 무한합니다. 탐색 된 일부 아이디어에는 로깅, 인터페이스 검사, 자동 위임, 자동 속성 생성, 프록시, 프레임 워크 및 자동 리소스 잠금 / 동기화가 포함됩니다.

그럼에도 불구하고 일반적으로 사용자가 절대적으로 필요한 경우가 아니면 메타 클래스를 사용하지 않는 것이 좋습니다.

클래스를 만들 때마다 메타 클래스를 사용합니다.

예를 들어 다음과 같이 클래스 정의를 작성할 때

class Foo(object): 
    'demo'

클래스 객체를 인스턴스화합니다.

>>> Foo
<class '__main__.Foo'>
>>> isinstance(Foo, type), isinstance(Foo, object)
(True, True)

type적절한 인수를 사용하여 기능적으로 호출 하고 결과를 해당 이름의 변수에 지정하는 것과 같습니다.

name = 'Foo'
bases = (object,)
namespace = {'__doc__': 'demo'}
Foo = type(name, bases, namespace)

__dict__네임 스페이스에 자동으로 추가되는 항목 이 있습니다.

>>> Foo.__dict__
dict_proxy({'__dict__': <attribute '__dict__' of 'Foo' objects>, 
'__module__': '__main__', '__weakref__': <attribute '__weakref__' 
of 'Foo' objects>, '__doc__': 'demo'})

두 경우 모두 생성 한 객체 의 메타 클래스type입니다.

(클래스의 내용에 측면 참고 __dict__: __module__클래스가 정의되는 곳 알고 있어야하기 때문에 존재 __dict__하고 __weakref__우리는 정의하지 않기 때문에이입니다 __slots__- 우리는 경우에 정의__slots__ 우리가 경우에 공간의 비트를 절약 할 수로, 우리는 허용하지 않을 수 있습니다 __dict____weakref__이를 제외하여 예를 들면 다음과 같습니다.

>>> Baz = type('Bar', (object,), {'__doc__': 'demo', '__slots__': ()})
>>> Baz.__dict__
mappingproxy({'__doc__': 'demo', '__slots__': (), '__module__': '__main__'})

...하지만 나는 산만하다.)

type다른 클래스 정의처럼 확장 할 수 있습니다 :

기본 __repr__클래스 는 다음과 같습니다 .

>>> Foo
<class '__main__.Foo'>

Python 객체를 작성할 때 기본적으로 할 수있는 가장 유용한 작업 중 하나는 좋은 객체를 제공하는 것 __repr__입니다. 우리가 부를 때 우리 는 동등성에 대한 테스트를 요구하는 help(repr)좋은 테스트가 있다는 것을 알게 됩니다. 다음과 같은 간단한 구현 하고 우리 형 클래스의 클래스 인스턴스는 기본 개선 할 수있는 데모 우리에게 제공 클래스 :__repr__obj == eval(repr(obj))__repr____eq____repr__

class Type(type):
    def __repr__(cls):
        """
        >>> Baz
        Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})
        >>> eval(repr(Baz))
        Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})
        """
        metaname = type(cls).__name__
        name = cls.__name__
        parents = ', '.join(b.__name__ for b in cls.__bases__)
        if parents:
            parents += ','
        namespace = ', '.join(': '.join(
          (repr(k), repr(v) if not isinstance(v, type) else v.__name__))
               for k, v in cls.__dict__.items())
        return '{0}(\'{1}\', ({2}), {{{3}}})'.format(metaname, name, parents, namespace)
    def __eq__(cls, other):
        """
        >>> Baz == eval(repr(Baz))
        True            
        """
        return (cls.__name__, cls.__bases__, cls.__dict__) == (
                other.__name__, other.__bases__, other.__dict__)

이제이 메타 클래스를 사용하여 객체를 만들면 __repr__명령 줄에 에코 표시가 기본값보다 훨씬 덜보기 좋습니다.

>>> class Bar(object): pass
>>> Baz = Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})
>>> Baz
Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})

__repr__클래스 인스턴스를 잘 정의 하면 코드를 디버깅 할 수있는 능력이 향상됩니다. 그러나 eval(repr(Class))기능을 기본 값에서 평가하는 것은 다소 불가능하므로 훨씬 더 자세한 검사 는 거의 불가능합니다 __repr__.

예상 사용법 : __prepare__네임 스페이스

예를 들어, 클래스의 메소드가 어떤 순서로 작성되는지 알고 싶다면, 클래스의 네임 스페이스로서 순서화 된 dict를 제공 할 수 있습니다. 우리는 함께이 할 것이 __prepare__있는 가 파이썬 3에서 구현되는 경우 클래스의 네임 스페이스 딕셔너리를 반환합니다 :

from collections import OrderedDict

class OrderedType(Type):
    @classmethod
    def __prepare__(metacls, name, bases, **kwargs):
        return OrderedDict()
    def __new__(cls, name, bases, namespace, **kwargs):
        result = Type.__new__(cls, name, bases, dict(namespace))
        result.members = tuple(namespace)
        return result

그리고 사용법 :

class OrderedMethodsObject(object, metaclass=OrderedType):
    def method1(self): pass
    def method2(self): pass
    def method3(self): pass
    def method4(self): pass

그리고 이제 우리는이 메소드들과 다른 클래스 속성들이 생성 된 순서에 대한 기록을 가지고 있습니다 :

>>> OrderedMethodsObject.members
('__module__', '__qualname__', 'method1', 'method2', 'method3', 'method4')

이 예제는 문서 에서 수정 되었습니다. 표준 라이브러리 의 새 열거 형 이이를 수행합니다.

우리가 한 일은 클래스를 만들어 메타 클래스를 인스턴스화하는 것이 었습니다. 다른 클래스와 마찬가지로 메타 클래스를 처리 할 수도 있습니다. 메소드 해결 순서가 있습니다.

>>> inspect.getmro(OrderedType)
(<class '__main__.OrderedType'>, <class '__main__.Type'>, <class 'type'>, <class 'object'>)

그리고 그것은 거의 정확합니다 repr(함수를 나타내는 방법을 찾을 수 없다면 더 이상 평가할 수 없습니다).

>>> OrderedMethodsObject
OrderedType('OrderedMethodsObject', (object,), {'method1': <function OrderedMethodsObject.method1 at 0x0000000002DB01E0>, 'members': ('__module__', '__qualname__', 'method1', 'method2', 'method3', 'method4'), 'method3': <function OrderedMet
hodsObject.method3 at 0x0000000002DB02F0>, 'method2': <function OrderedMethodsObject.method2 at 0x0000000002DB0268>, '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'OrderedMethodsObject' objects>, '__doc__': None, '__d
ict__': <attribute '__dict__' of 'OrderedMethodsObject' objects>, 'method4': <function OrderedMethodsObject.method4 at 0x0000000002DB0378>})

78

파이썬 3 업데이트

메타 클래스에는 현재 두 가지 주요 메소드가 있습니다.

  • __prepare__,
  • __new__

__prepare__OrderedDict클래스를 만드는 동안 네임 스페이스로 사용할 사용자 지정 매핑 (예 :)을 제공 할 수 있습니다 . 선택한 네임 스페이스의 인스턴스를 반환해야합니다. 구현하지 않으면 __prepare__법선 dict이 사용됩니다.

__new__ 최종 클래스의 실제 생성 / 수정에 대한 책임이 있습니다.

베어 본, 할 일 없음 엑스트라 메타 클래스는 다음과 같습니다.

class Meta(type):

    def __prepare__(metaclass, cls, bases):
        return dict()

    def __new__(metacls, cls, bases, clsdict):
        return super().__new__(metacls, cls, bases, clsdict)

간단한 예 :

그것은 항상해야처럼 - 당신은 몇 가지 간단한 검증 코드가 속성에 실행하려는 말 int또는를 str. 메타 클래스가 없으면 클래스는 다음과 같습니다.

class Person:
    weight = ValidateType('weight', int)
    age = ValidateType('age', int)
    name = ValidateType('name', str)

보시다시피, 속성 이름을 두 번 반복해야합니다. 이것은 자극적 인 버그와 함께 오타가 가능합니다.

간단한 메타 클래스가이 문제를 해결할 수 있습니다.

class Person(metaclass=Validator):
    weight = ValidateType(int)
    age = ValidateType(int)
    name = ValidateType(str)

메타 클래스는 다음과 같습니다 ( __prepare__필요하지 않기 때문에 사용 하지 않음).

class Validator(type):
    def __new__(metacls, cls, bases, clsdict):
        # search clsdict looking for ValidateType descriptors
        for name, attr in clsdict.items():
            if isinstance(attr, ValidateType):
                attr.name = name
                attr.attr = '_' + name
        # create final class and return it
        return super().__new__(metacls, cls, bases, clsdict)

샘플 런 :

p = Person()
p.weight = 9
print(p.weight)
p.weight = '9'

생산 :

9
Traceback (most recent call last):
  File "simple_meta.py", line 36, in <module>
    p.weight = '9'
  File "simple_meta.py", line 24, in __set__
    (self.name, self.type, value))
TypeError: weight must be of type(s) <class 'int'> (got '9')

참고 :이 예제는 클래스 데코레이터로도 달성 할 수있을만큼 간단하지만 실제 메타 클래스는 훨씬 더 많은 작업을 수행합니다.

참조를위한 'ValidateType'클래스 :

class ValidateType:
    def __init__(self, type):
        self.name = None  # will be set by metaclass
        self.attr = None  # will be set by metaclass
        self.type = type
    def __get__(self, inst, cls):
        if inst is None:
            return self
        else:
            return inst.__dict__[self.attr]
    def __set__(self, inst, value):
        if not isinstance(value, self.type):
            raise TypeError('%s must be of type(s) %s (got %r)' %
                    (self.name, self.type, value))
        else:
            inst.__dict__[self.attr] = value

와우, 이것은 파이썬 3에 존재하지 않은 멋진 새로운 기능입니다. 예제를 주셔서 감사합니다 !!
Rich Lysakowski PhD

python 3.6부터는 __set_name__(cls, name)descriptor ( ValidateType) 에서 디스크립터 의 이름을 설정할 수 있습니다 ( self.name이 경우에도 self.attr). 이 특정 일반적인 사용 사례를 위해 메타 클래스에 들어 가지 않아도되도록 추가되었습니다 (PEP 487 참조).
라스

68

__call__()클래스 인스턴스 생성시 메타 클래스의 역할

몇 달 이상 파이썬 프로그래밍을 해보았다면 결국 다음과 같은 코드를 발견하게 될 것입니다.

# define a class
class SomeClass(object):
    # ...
    # some definition here ...
    # ...

# create an instance of it
instance = SomeClass()

# then call the object as if it's a function
result = instance('foo', 'bar')

후자는 __call__()클래스 에서 magic 메소드 를 구현할 때 가능합니다 .

class SomeClass(object):
    # ...
    # some definition here ...
    # ...

    def __call__(self, foo, bar):
        return bar + foo

__call__()클래스의 인스턴스가 호출로 사용하는 경우 메서드가 호출됩니다. 그러나 이전 답변에서 보았 듯이 클래스 자체는 메타 클래스의 인스턴스이므로 클래스를 호출 가능 클래스로 사용할 때 (즉, 인스턴스를 만들 때) 실제로 메타 클래스의 __call__()메서드를 호출합니다 . 이 시점에서 대부분의 Python 프로그래머는 이와 같은 인스턴스를 만들 때 메서드를 instance = SomeClass()호출 한다고 들었 기 때문에 약간 혼란스러워 __init__()합니다. 조금 더 깊이 파고했습니다 어떤 사람들은 전에 알고 __init__()있다 __new__(). 글쎄, 오늘날 __new__()메타 클래스가 있기 전에 또 다른 진실의 층이 밝혀지고있다 __call__().

클래스의 인스턴스를 만드는 관점에서 메서드 호출 체인을 연구 해 봅시다.

이것은 인스턴스가 생성되기 전과 인스턴스를 반환하려는 순간을 정확하게 기록하는 메타 클래스입니다.

class Meta_1(type):
    def __call__(cls):
        print "Meta_1.__call__() before creating an instance of ", cls
        instance = super(Meta_1, cls).__call__()
        print "Meta_1.__call__() about to return instance."
        return instance

이 메타 클래스를 사용하는 클래스입니다

class Class_1(object):

    __metaclass__ = Meta_1

    def __new__(cls):
        print "Class_1.__new__() before creating an instance."
        instance = super(Class_1, cls).__new__(cls)
        print "Class_1.__new__() about to return instance."
        return instance

    def __init__(self):
        print "entering Class_1.__init__() for instance initialization."
        super(Class_1,self).__init__()
        print "exiting Class_1.__init__()."

이제 인스턴스를 만들어 봅시다 Class_1

instance = Class_1()
# Meta_1.__call__() before creating an instance of <class '__main__.Class_1'>.
# Class_1.__new__() before creating an instance.
# Class_1.__new__() about to return instance.
# entering Class_1.__init__() for instance initialization.
# exiting Class_1.__init__().
# Meta_1.__call__() about to return instance.

위의 코드는 실제로 작업을 기록하는 것 이상을 수행하지 않습니다. 각 메소드는 실제 작업을 상위 구현에 위임하므로 기본 동작을 유지합니다. 이후 type입니다 Meta_1의 부모 클래스 ( type기본 부모 메타 클래스 인) 이상 출력의 순서 순서를 고려, 우리는 지금의 의사 구현 될 사항에 대한 단서를 가지고 type.__call__():

class type:
    def __call__(cls, *args, **kwarg):

        # ... maybe a few things done to cls here

        # then we call __new__() on the class to create an instance
        instance = cls.__new__(cls, *args, **kwargs)

        # ... maybe a few things done to the instance here

        # then we initialize the instance with its __init__() method
        instance.__init__(*args, **kwargs)

        # ... maybe a few more things done to instance here

        # then we return it
        return instance

메타 클래스의 __call__()메소드가 가장 먼저 호출되는 것을 볼 수 있습니다 . 그런 다음 인스턴스 생성을 클래스의 __new__()메서드에 위임 하고 초기화를 인스턴스에 위임 __init__()합니다. 또한 궁극적으로 인스턴스를 반환하는 것이기도합니다.

위의는 메타 클래스는 '고 줄기에서 __call__()또한 여부를 전화를 결정할 수있는 기회를 부여 Class_1.__new__()하거나 Class_1.__init__()결국 될 것이다. 실행 과정에서 실제로 이러한 방법 중 하나에 의해 만지지 않은 객체를 반환 할 수 있습니다. 싱글 톤 패턴에 대한이 접근 방식을 예로 들어 보겠습니다.

class Meta_2(type):
    singletons = {}

    def __call__(cls, *args, **kwargs):
        if cls in Meta_2.singletons:
            # we return the only instance and skip a call to __new__()
            # and __init__()
            print ("{} singleton returning from Meta_2.__call__(), "
                   "skipping creation of new instance.".format(cls))
            return Meta_2.singletons[cls]

        # else if the singleton isn't present we proceed as usual
        print "Meta_2.__call__() before creating an instance."
        instance = super(Meta_2, cls).__call__(*args, **kwargs)
        Meta_2.singletons[cls] = instance
        print "Meta_2.__call__() returning new instance."
        return instance

class Class_2(object):

    __metaclass__ = Meta_2

    def __new__(cls, *args, **kwargs):
        print "Class_2.__new__() before creating instance."
        instance = super(Class_2, cls).__new__(cls)
        print "Class_2.__new__() returning instance."
        return instance

    def __init__(self, *args, **kwargs):
        print "entering Class_2.__init__() for initialization."
        super(Class_2, self).__init__()
        print "exiting Class_2.__init__()."

반복적으로 유형의 객체를 만들려고 할 때 어떤 일이 발생하는지 관찰하십시오 Class_2

a = Class_2()
# Meta_2.__call__() before creating an instance.
# Class_2.__new__() before creating instance.
# Class_2.__new__() returning instance.
# entering Class_2.__init__() for initialization.
# exiting Class_2.__init__().
# Meta_2.__call__() returning new instance.

b = Class_2()
# <class '__main__.Class_2'> singleton returning from Meta_2.__call__(), skipping creation of new instance.

c = Class_2()
# <class '__main__.Class_2'> singleton returning from Meta_2.__call__(), skipping creation of new instance.

a is b is c # True

이것은 이전에 공표 된 "허용 된 답변"에 추가 된 것입니다. 중간 코더가 씹는 예제를 제공합니다.
Rich Lysakowski PhD

56

메타 클래스는 다른 클래스를 작성하는 방법을 알려주는 클래스입니다.

이것은 메타 클래스를 내 문제에 대한 해결책으로 보았습니다. 실제로 복잡한 문제가 있었으며 아마도 다르게 해결할 수 있었지만 메타 클래스를 사용하여 해결하기로 선택했습니다. 복잡성 때문에 모듈의 주석이 작성된 코드의 양을 능가하는 몇 가지 모듈 중 하나입니다. 여기있어...

#!/usr/bin/env python

# Copyright (C) 2013-2014 Craig Phillips.  All rights reserved.

# This requires some explaining.  The point of this metaclass excercise is to
# create a static abstract class that is in one way or another, dormant until
# queried.  I experimented with creating a singlton on import, but that did
# not quite behave how I wanted it to.  See now here, we are creating a class
# called GsyncOptions, that on import, will do nothing except state that its
# class creator is GsyncOptionsType.  This means, docopt doesn't parse any
# of the help document, nor does it start processing command line options.
# So importing this module becomes really efficient.  The complicated bit
# comes from requiring the GsyncOptions class to be static.  By that, I mean
# any property on it, may or may not exist, since they are not statically
# defined; so I can't simply just define the class with a whole bunch of
# properties that are @property @staticmethods.
#
# So here's how it works:
#
# Executing 'from libgsync.options import GsyncOptions' does nothing more
# than load up this module, define the Type and the Class and import them
# into the callers namespace.  Simple.
#
# Invoking 'GsyncOptions.debug' for the first time, or any other property
# causes the __metaclass__ __getattr__ method to be called, since the class
# is not instantiated as a class instance yet.  The __getattr__ method on
# the type then initialises the class (GsyncOptions) via the __initialiseClass
# method.  This is the first and only time the class will actually have its
# dictionary statically populated.  The docopt module is invoked to parse the
# usage document and generate command line options from it.  These are then
# paired with their defaults and what's in sys.argv.  After all that, we
# setup some dynamic properties that could not be defined by their name in
# the usage, before everything is then transplanted onto the actual class
# object (or static class GsyncOptions).
#
# Another piece of magic, is to allow command line options to be set in
# in their native form and be translated into argparse style properties.
#
# Finally, the GsyncListOptions class is actually where the options are
# stored.  This only acts as a mechanism for storing options as lists, to
# allow aggregation of duplicate options or options that can be specified
# multiple times.  The __getattr__ call hides this by default, returning the
# last item in a property's list.  However, if the entire list is required,
# calling the 'list()' method on the GsyncOptions class, returns a reference
# to the GsyncListOptions class, which contains all of the same properties
# but as lists and without the duplication of having them as both lists and
# static singlton values.
#
# So this actually means that GsyncOptions is actually a static proxy class...
#
# ...And all this is neatly hidden within a closure for safe keeping.
def GetGsyncOptionsType():
    class GsyncListOptions(object):
        __initialised = False

    class GsyncOptionsType(type):
        def __initialiseClass(cls):
            if GsyncListOptions._GsyncListOptions__initialised: return

            from docopt import docopt
            from libgsync.options import doc
            from libgsync import __version__

            options = docopt(
                doc.__doc__ % __version__,
                version = __version__,
                options_first = True
            )

            paths = options.pop('<path>', None)
            setattr(cls, "destination_path", paths.pop() if paths else None)
            setattr(cls, "source_paths", paths)
            setattr(cls, "options", options)

            for k, v in options.iteritems():
                setattr(cls, k, v)

            GsyncListOptions._GsyncListOptions__initialised = True

        def list(cls):
            return GsyncListOptions

        def __getattr__(cls, name):
            cls.__initialiseClass()
            return getattr(GsyncListOptions, name)[-1]

        def __setattr__(cls, name, value):
            # Substitut option names: --an-option-name for an_option_name
            import re
            name = re.sub(r'^__', "", re.sub(r'-', "_", name))
            listvalue = []

            # Ensure value is converted to a list type for GsyncListOptions
            if isinstance(value, list):
                if value:
                    listvalue = [] + value
                else:
                    listvalue = [ None ]
            else:
                listvalue = [ value ]

            type.__setattr__(GsyncListOptions, name, listvalue)

    # Cleanup this module to prevent tinkering.
    import sys
    module = sys.modules[__name__]
    del module.__dict__['GetGsyncOptionsType']

    return GsyncOptionsType

# Our singlton abstract proxy class.
class GsyncOptions(object):
    __metaclass__ = GetGsyncOptionsType()

43

tl; dr 버전

type(obj)함수는 객체의 유형을 가져옵니다.

type()클래스의 그것입니다 메타 클래스 .

메타 클래스를 사용하려면

class Foo(object):
    __metaclass__ = MyMetaClass

type자체 메타 클래스입니다. 클래스의 클래스는 메타 클래스입니다. 클래스의 본문은 클래스를 구성하는 데 사용되는 메타 클래스에 전달되는 인수입니다.

여기 에서는 메타 클래스를 사용하여 클래스 구성을 사용자 정의하는 방법에 대해 읽을 수 있습니다.


42

type실제로 metaclass다른 클래스를 만드는 클래스입니다. 대부분 metaclass의 하위 클래스입니다 type. 는 metaclass받는 new첫 번째 인자로 클래스를 아래에 언급 한 바와 같이 세부 클래스 객체에 대한 액세스를 제공합니다 :

>>> class MetaClass(type):
...     def __init__(cls, name, bases, attrs):
...         print ('class name: %s' %name )
...         print ('Defining class %s' %cls)
...         print('Bases %s: ' %bases)
...         print('Attributes')
...         for (name, value) in attrs.items():
...             print ('%s :%r' %(name, value))
... 

>>> class NewClass(object, metaclass=MetaClass):
...    get_choch='dairy'
... 
class name: NewClass
Bases <class 'object'>: 
Defining class <class 'NewClass'>
get_choch :'dairy'
__module__ :'builtins'
__qualname__ :'NewClass'

Note:

클래스는 언제라도 인스턴스화되지 않았습니다. 클래스를 생성하는 간단한 행위로의 실행이 트리거되었습니다 metaclass.


27

파이썬 클래스 자체는 메타 클래스의 객체입니다.

클래스를 다음과 같이 결정할 때 적용되는 기본 메타 클래스 :

class foo:
    ...

메타 클래스는 전체 클래스 세트에 규칙을 적용하는 데 사용됩니다. 예를 들어, 데이터베이스에 액세스하기 위해 ORM을 작성하고 각 테이블의 레코드가 필드, 비즈니스 규칙 등을 기반으로 해당 테이블에 맵핑 된 클래스의 메타 클래스 사용을 원한다고 가정하십시오. 예를 들어 모든 테이블의 모든 레코드 클래스에서 공유하는 연결 풀 논리입니다. 다른 용도로는 외래 키를 지원하는 논리가 있으며 여기에는 여러 레코드 클래스가 포함됩니다.

메타 클래스를 정의 할 때 서브 클래스 유형을 지정하고 다음 매직 메소드를 재정 의하여 로직을 삽입 할 수 있습니다.

class somemeta(type):
    __new__(mcs, name, bases, clsdict):
      """
  mcs: is the base metaclass, in this case type.
  name: name of the new class, as provided by the user.
  bases: tuple of base classes 
  clsdict: a dictionary containing all methods and attributes defined on class

  you must return a class object by invoking the __new__ constructor on the base metaclass. 
 ie: 
    return type.__call__(mcs, name, bases, clsdict).

  in the following case:

  class foo(baseclass):
        __metaclass__ = somemeta

  an_attr = 12

  def bar(self):
      ...

  @classmethod
  def foo(cls):
      ...

      arguments would be : ( somemeta, "foo", (baseclass, baseofbase,..., object), {"an_attr":12, "bar": <function>, "foo": <bound class method>}

      you can modify any of these values before passing on to type
      """
      return type.__call__(mcs, name, bases, clsdict)


    def __init__(self, name, bases, clsdict):
      """ 
      called after type has been created. unlike in standard classes, __init__ method cannot modify the instance (cls) - and should be used for class validaton.
      """
      pass


    def __prepare__():
        """
        returns a dict or something that can be used as a namespace.
        the type will then attach methods and attributes from class definition to it.

        call order :

        somemeta.__new__ ->  type.__new__ -> type.__init__ -> somemeta.__init__ 
        """
        return dict()

    def mymethod(cls):
        """ works like a classmethod, but for class objects. Also, my method will not be visible to instances of cls.
        """
        pass

어쨌든이 두 가지가 가장 일반적으로 사용되는 고리입니다. 메타 클래 싱 (metaclassing)은 강력하며, 메타 클래 싱에 대한 사용 목록은 거의 없습니다.


21

type () 함수는 객체의 유형을 반환하거나 새 유형을 만들 수 있습니다.

예를 들어 type () 함수를 사용하여 Hi 클래스를 만들 수 있으며 Hi (object) 클래스와 함께이 방법을 사용할 필요는 없습니다.

def func(self, name='mike'):
    print('Hi, %s.' % name)

Hi = type('Hi', (object,), dict(hi=func))
h = Hi()
h.hi()
Hi, mike.

type(Hi)
type

type(h)
__main__.Hi

type ()을 사용하여 클래스를 동적으로 생성하는 것 외에도 클래스의 생성 동작을 제어하고 메타 클래스를 사용할 수 있습니다.

Python 객체 모델에 따르면 클래스는 객체이므로 클래스는 다른 특정 클래스의 인스턴스 여야합니다. 기본적으로 Python 클래스는 유형 클래스의 인스턴스입니다. 즉, type은 대부분의 내장 클래스의 메타 클래스와 사용자 정의 클래스의 메타 클래스입니다.

class ListMetaclass(type):
    def __new__(cls, name, bases, attrs):
        attrs['add'] = lambda self, value: self.append(value)
        return type.__new__(cls, name, bases, attrs)

class CustomList(list, metaclass=ListMetaclass):
    pass

lst = CustomList()
lst.add('custom_list_1')
lst.add('custom_list_2')

lst
['custom_list_1', 'custom_list_2']

매직은 메타 클래스에서 키워드 인수를 전달할 때 적용되며, ListMetaclass를 통해 CustomList를 작성하는 Python 인터프리터를 나타냅니다. new (),이 시점에서 클래스 정의를 수정하고 새 메소드를 추가 한 후 수정 된 정의를 리턴 할 수 있습니다.


11

게시 된 답변 외에도 metaclass클래스의 동작을 정의 한다고 말할 수 있습니다 . 따라서 메타 클래스를 명시 적으로 설정할 수 있습니다. 파이썬은 키워드 class를 얻을 때마다를 검색하기 시작합니다 metaclass. 찾을 수없는 경우 – 기본 메타 클래스 유형을 사용하여 클래스의 객체를 만듭니다. 이 __metaclass__속성을 사용 metaclass하여 클래스를 설정할 수 있습니다 .

class MyClass:
   __metaclass__ = type
   # write here other method
   # write here one more method

print(MyClass.__metaclass__)

다음과 같이 출력을 생성합니다.

class 'type'

물론 metaclass클래스를 사용하여 만든 클래스의 동작을 정의 하기 위해 직접 만들 수도 있습니다 .

그렇게 metaclass하려면 기본 유형 클래스가 기본 유형이므로 상속해야합니다 metaclass.

class MyMetaClass(type):
   __metaclass__ = type
   # you can write here any behaviour you want

class MyTestClass:
   __metaclass__ = MyMetaClass

Obj = MyTestClass()
print(Obj.__metaclass__)
print(MyMetaClass.__metaclass__)

출력은 다음과 같습니다.

class '__main__.MyMetaClass'
class 'type'

4

객체 지향 프로그래밍에서 메타 클래스는 인스턴스가 클래스 인 클래스입니다. 일반 클래스가 특정 객체의 동작을 정의하는 것처럼 메타 클래스는 특정 클래스 및 해당 인스턴스의 동작을 정의합니다. 메타 클래스라는 용어는 단순히 클래스를 만드는 데 사용되는 것을 의미합니다. 다시 말해, 그것은 클래스의 클래스입니다. 메타 클래스는 클래스의 인스턴스가되는 객체처럼 클래스를 만드는 데 사용되며 클래스는 메타 클래스의 인스턴스입니다. 파이썬에서는 클래스도 객체로 간주됩니다.


Bookish 정의를 제공하는 대신 몇 가지 예를 추가하면 더 좋을 것입니다. 귀하의 답변의 첫 번째 줄은 메타 클래스의 Wikipedia 항목에서 복사 된 것으로 보입니다.
verisimilitude

@verisimilitude 나는 또한 당신이 경험에서 실제적인 예를 제공 함으로써이 답변을 향상시키는 데 도움을 줄 수 있습니까?
Venu Gopal Tewari

2

사용할 수있는 또 다른 예는 다음과 같습니다.

  • 를 사용하여 metaclass인스턴스 (클래스)의 기능을 변경할 수 있습니다 .
class MetaMemberControl(type):
    __slots__ = ()

    @classmethod
    def __prepare__(mcs, f_cls_name, f_cls_parents,  # f_cls means: future class
                    meta_args=None, meta_options=None):  # meta_args and meta_options is not necessarily needed, just so you know.
        f_cls_attr = dict()
        if not "do something or if you want to define your cool stuff of dict...":
            return dict(make_your_special_dict=None)
        else:
            return f_cls_attr

    def __new__(mcs, f_cls_name, f_cls_parents, f_cls_attr,
                meta_args=None, meta_options=None):

        original_getattr = f_cls_attr.get('__getattribute__')
        original_setattr = f_cls_attr.get('__setattr__')

        def init_getattr(self, item):
            if not item.startswith('_'):  # you can set break points at here
                alias_name = '_' + item
                if alias_name in f_cls_attr['__slots__']:
                    item = alias_name
            if original_getattr is not None:
                return original_getattr(self, item)
            else:
                return super(eval(f_cls_name), self).__getattribute__(item)

        def init_setattr(self, key, value):
            if not key.startswith('_') and ('_' + key) in f_cls_attr['__slots__']:
                raise AttributeError(f"you can't modify private members:_{key}")
            if original_setattr is not None:
                original_setattr(self, key, value)
            else:
                super(eval(f_cls_name), self).__setattr__(key, value)

        f_cls_attr['__getattribute__'] = init_getattr
        f_cls_attr['__setattr__'] = init_setattr

        cls = super().__new__(mcs, f_cls_name, f_cls_parents, f_cls_attr)
        return cls


class Human(metaclass=MetaMemberControl):
    __slots__ = ('_age', '_name')

    def __init__(self, name, age):
        self._name = name
        self._age = age

    def __getattribute__(self, item):
        """
        is just for IDE recognize.
        """
        return super().__getattribute__(item)

    """ with MetaMemberControl then you don't have to write as following
    @property
    def name(self):
        return self._name

    @property
    def age(self):
        return self._age
    """


def test_demo():
    human = Human('Carson', 27)
    # human.age = 18  # you can't modify private members:_age  <-- this is defined by yourself.
    # human.k = 18  # 'Human' object has no attribute 'k'  <-- system error.
    age1 = human._age  # It's OK, although the IDE will show some warnings. (Access to a protected member _age of a class)

    age2 = human.age  # It's OK! see below:
    """
    if you do not define `__getattribute__` at the class of Human,
    the IDE will show you: Unresolved attribute reference 'age' for class 'Human'
    but it's ok on running since the MetaMemberControl will help you.
    """


if __name__ == '__main__':
    test_demo()

metaclass당신이 그것으로 할 수 있지만,이는 만에 알 수 있습니다 조심해야 할 수 있습니다 (예 : 원숭이 마법과 같은) 여러 가지가 있습니다, 강력하다.


2

파이썬에서 클래스는 객체이며 다른 객체와 마찬가지로 "무언가"의 인스턴스입니다. 이 "무언가"는 메타 클래스라고합니다. 이 메타 클래스는 다른 클래스의 객체를 생성하는 특수한 유형의 클래스입니다. 따라서 메타 클래스는 새로운 클래스를 만드는 역할을합니다. 이를 통해 프로그래머는 클래스 생성 방식을 사용자 정의 할 수 있습니다.

메타 클래스를 만들려면 일반적으로 new () 및 init () 메서드를 재정의 합니다. 객체 생성 방식을 변경하기 위해 new ()를 재정의 하고 객체 초기화 방식을 변경하기 위해 init ()를 재정의 할 수 있습니다. 메타 클래스는 여러 가지 방법으로 만들 수 있습니다. 방법 중 하나는 type () 함수를 사용하는 것입니다. type () 함수는 3 개의 매개 변수와 함께 호출 될 때 메타 클래스를 만듭니다. 매개 변수는 다음과 같습니다.

  1. 수업 명
  2. 기본 클래스가 클래스에 상속 된 튜플
  3. 모든 클래스 메소드와 클래스 변수가있는 사전

메타 클래스를 만드는 또 다른 방법은 '메타 클래스'키워드로 구성됩니다. 메타 클래스를 간단한 클래스로 정의하십시오. 상속 된 클래스의 매개 변수에서 metaclass = metaclass_name을 전달하십시오.

메타 클래스는 다음과 같은 상황에서 구체적으로 사용될 수 있습니다.

  1. 모든 서브 클래스에 특정 효과를 적용해야 할 때
  2. 수업 생성시 자동 변경이 필요합니다.
  3. API 개발자

2

python 3.6에서는 __init_subclass__(cls, **kwargs)메타 클래스에 대한 많은 일반적인 사용 사례를 대체하기 위해 새로운 dunder 메소드 가 도입되었습니다. Is는 정의 클래스의 서브 클래스가 작성 될 때 호출됩니다. 파이썬 문서를 참조하십시오 .


-3

메타 클래스는 클래스의 작동 방식을 정의하는 일종의 클래스이거나 클래스 자체가 메타 클래스의 인스턴스라고 말할 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.