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



Answers

오브젝트로서의 클래스

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

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

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

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

그러나 클래스는 파이썬보다 더 중요합니다. 클래스도 객체입니다.

네, 물건들.

키워드 class 를 사용하자마자 Python은 그것을 실행하고 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>

동적으로 클래스 만들기

클래스는 객체이기 때문에 어떤 객체와 마찬가지로 즉석에서 작성할 수 있습니다.

먼저 클래스를 사용하여 함수에 클래스를 만들 수 있습니다.

>>> 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 은 클래스에 대한 설명을 매개 변수로 취하고 클래스를 반환 할 수 있습니다.

(필자가 알고있는 바, 동일한 함수가 전달한 매개 변수에 따라 완전히 다른 두 가지 용도를 가질 수 있다는 것은 바보 같은 생각입니다. 파이썬에서 이전 버전과의 호환성 때문에 발생하는 문제입니다)

type 은 다음과 같이 작동합니다.

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

예 :

>>> 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 를 사용할 때 파이썬이하는 일이며, 메타 클래스를 사용하면됩니다.

메타 클래스 란 무엇입니까 (최종적으로)?

메타 클래스는 클래스를 만드는 '물건'입니다.

객체를 만들려면 클래스를 정의해야합니다.

그러나 우리는 Python 클래스가 객체라는 것을 알게되었습니다.

글쎄, 메타 클래스는 이러한 객체를 만드는 것입니다. 클래스의 클래스이므로 다음과 같이 그림을 그릴 수 있습니다.

MyClass = MetaClass()
my_object = MyClass()

type 은 다음과 같은 것을 할 수 있음을 보았습니다 :

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

함수 type 이 실제로는 메타 클래스이기 때문입니다. type 은 모든 클래스를 만드는 데 사용되는 메타 클래스입니다.

이제 도대체 왜 소문자로 쓰여져 있고 Type 이 아닌지 궁금해하십니까?

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

__class__ 속성을 확인하여이를 볼 수 있습니다.

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

>>> 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 은 내장 된 메타 클래스 Python이 사용하지만, 물론 여러분 만의 메타 클래스를 만들 수 있습니다.

__metaclass__ 속성

클래스를 작성할 때 __metaclass__ 속성을 추가 할 수 있습니다.

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

그렇게하면 Python은 metaclass를 사용하여 Foo 클래스를 만듭니다.

조심해, 까다 롭다.

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

파이썬은 클래스 정의에서 __metaclass__ 을 찾는다. 발견되면 Foo 라는 객체 클래스를 만드는 데 사용합니다. 그렇지 않은 경우, 클래스를 작성하기 위해서 type 를 사용합니다.

몇 번 읽어보십시오.

할 일 :

class Foo(Bar):
    pass

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

Foo __metaclass__ 속성이 있습니까?

그렇다면 메모리에 클래스 객체 (클래스 객체를 말하고 여기에 머물러 라.)를 만들고, Foo 라는 이름으로 __metaclass__ 있는 것을 사용합니다.

파이썬이 __metaclass__ 찾지 못하면, 모듈 수준에서 __metaclass__ 을 찾아서 똑같이하려고 시도합니다 (그러나 아무것도 상속하지 않는 클래스, 기본적으로 구식 클래스).

그런 다음 __metaclass__ 를 전혀 찾을 수 없으면 Bar 객체 (첫 번째 부모)가 자신의 메타 클래스 (기본 type 이 될 수 있음)를 사용하여 클래스 객체를 만듭니다.

여기에서 __metaclass__ 속성이 상속되지 않도록주의하십시오. 부모 ( Bar.__class__ )의 메타 클래스가 상속됩니다. Bartype() ( type.__new__() )이 아닌 Bar 를 만든 __metaclass__ 속성을 사용하면 하위 클래스는 해당 동작을 상속하지 않습니다.

이제 큰 질문은 __metaclass__ 무엇을 넣을 수 있는가입니다.

그 답은 클래스를 생성 할 수있는 것입니다.

무엇이 수업을 만들 수 있습니까? type 또는 하위 클래스 또는 사용하는 모든 것.

맞춤 메타 클래스

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

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

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

이렇게하면이 모듈의 모든 클래스가이 메타 클래스를 사용하여 만들어지며 모든 속성을 대문자로 전환하도록 메타 클래스에 지시해야합니다.

운 좋게도, __metaclass__ 는 실제로 호출 가능한 클래스가 될 수 있습니다. 공식 클래스 일 필요는 없습니다. (이름에서 '클래스'가 클래스 일 필요는 없습니다. 그림이 있지만 도움이됩니다.)

그래서 우리는 함수를 사용하여 간단한 예제로 시작합니다.

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

자, 똑같은 일을 해보 겠지만, 실제 클래스를 메타 클래스로 사용하십시오.

# 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_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)

그러나 이것은 실제로 OOP가 아닙니다. 우리는 직접 type 호출하고 우리는 부모를 __new__ 대체하거나 호출하지 않습니다. 해보 죠.

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)

추가 인수 인 upperattr_metaclass 주목 upperattr_metaclass 입니다. 특별한 것은 없습니다 : __new__ 항상 정의 된 클래스를 첫 번째 매개 변수로받습니다. 인스턴스를 첫 번째 매개 변수로받는 일반 메서드 나 클래스 메서드의 정의 클래스에 대한 self 처럼.

물론 여기에 사용 된 이름은 명확성을 위해 오래 있지만, self 과 마찬가지로 모든 주장은 일반적인 이름을 사용합니다. 따라서 실제 제작 메타 클래스는 다음과 같습니다.

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)

상속을 쉽게 할 수있는 super 를 사용하면 더 깨끗하게 만들 수 있습니다 (예, 메타 클래스를 가질 수 있고, 메타 클래스를 상속하고, 상속받을 수 있기 때문입니다).

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)

그게 전부 야. 메타 클래스에 관해서는 실제로 아무것도 없습니다.

메타 클래스를 사용하는 코드의 복잡성에 대한 이유는 메타 클래스 때문이 아니라 인트로 __dict__ , 상속 조작, __dict__ 과 같은 변수 등을 사용하여 꼬인 물건을 만들기 위해 일반적으로 메타 클래스를 사용하기 때문입니다.

실제로, 메타 클래스는 흑 마술을 수행하는 데 특히 유용하며, 따라서 복잡한 작업이 필요합니다. 하지만 그 자체로는 단순합니다.

  • 클래스 생성을 가로 채기
  • 수업을 수정하다
  • 수정 한 클래스를 돌려 준다

왜 함수 대신 메타 클래스를 사용합니까?

__metaclass__ 는 호출 가능을 받아 들일 수 있기 때문에 분명히 더 복잡하기 때문에 클래스를 사용하는 이유는 무엇입니까?

이렇게해야 할 몇 가지 이유가 있습니다.

  • 그 의도는 분명합니다. UpperAttrMetaclass(type) 을 읽을 때, 당신은 무엇을 따라야하는지 안다.
  • OOP를 사용할 수 있습니다. 메타 클래스는 메타 클래스를 상속하고 부모 메서드를 재정의 할 수 있습니다. 메타 클래스는 심지어 메타 클래스도 사용할 수 있습니다.
  • 메타 클래스 클래스를 지정한 경우 클래스의 하위 클래스는 해당 메타 클래스의 인스턴스이지만 메타 클래스 함수는 포함하지 않습니다.
  • 코드를 더 잘 구조화 할 수 있습니다. 위의 예제처럼 사소한 것으로는 결코 메타 클래스를 사용하지 마십시오. 대개 뭔가 복잡한 일이 있습니다. 하나의 클래스에서 여러 메소드를 만들고 그룹화하는 기능을 사용하면 코드를 읽기 쉽게 만들 수 있습니다.
  • __new__ , __init____call__ 에 연결할 수 있습니다. 그러면 다른 것들을 할 수있게됩니다. 일반적으로 __new__ 에서 모든 작업을 수행 할 수 있다고하더라도 일부 사람들은 __init__ 사용하는 것이 더 편합니다.
  • 이것들은 metaclasses라고 불 렸습니다. 젠장! 뭔가 의미가 있어야합니다!

왜 당신은 메타 클래스를 사용할 것인가?

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

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

메타 크릭은 99 %의 사용자가 걱정할 필요가없는 더 깊은 마술입니다. 당신이 필요로하는지 궁금하게 생각한다면, 실제로는 필요로하는 사람들은 그들이 필요하다는 것을 확실히 알고 있고, 이유에 대해 설명 할 필요가 없습니다.

파이썬 전문가 Tim Peters

메타 클래스의 주요 사용 사례는 API를 만드는 것입니다. 이것의 전형적인 예가 Django ORM입니다.

다음과 같이 정의 할 수 있습니다.

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

그러나 이렇게하면 :

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

IntegerField 객체를 반환하지 않습니다. 그것은 int 를 리턴 할 것이고 심지어 데이터베이스에서 직접 가져올 수도 있습니다.

이것은 models.Model__metaclass__ 정의하고 단순한 명령문으로 방금 정의한 Person 을 데이터베이스 필드에 대한 복잡한 후크로 바꾸는 마법을 사용하기 때문에 가능합니다.

Django는 간단한 API를 노출하고 메타 클래스를 사용하여이 API의 코드를 다시 작성하여 복잡한 작업을 단순하게 처리합니다.

마지막 단어

첫째, 클래스는 인스턴스를 만들 수있는 객체라는 것을 알고 있습니다.

사실, 클래스 자체가 인스턴스입니다. 메타 클래스 중.

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

모든 것은 파이썬에서 하나의 객체이며, 모두 클래스 나 메타 클래스 인스턴스 중 하나입니다.

type 제외하고.

type 은 실제로 자체 metaclass입니다. 이것은 순수한 파이썬에서 재현 할 수있는 것이 아니며 구현 레벨에서 약간의 부정 행위로 끝납니다.

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

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

그러나 시간의 98 %, 당신은 클래스 변경이 전혀 필요하지 않습니다.

Question

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




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

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

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

그러나 런타임에 Person 객체는 모든 유용한 메소드로 채워진다. 놀라운 metaclassery에 대한 source 를 참조하십시오.




The type() function can return the type of an object or create a new type,

for example, we can create a Hi class with the type() function and do not need to use this way with class 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

In addition to using type() to create classes dynamically, you can control creation behavior of class and use metaclass.

According to the Python object model, the class is the object, so the class must be an instance of another certain class. By default, a Python class is instance of the type class. That is, type is metaclass of most of the built-in classes and metaclass of user-defined classes.

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

Magic will take effect when we passed keyword arguments in metaclass, it indicates the Python interpreter to create the CustomList through ListMetaclass. new (), at this point, we can modify the class definition, for example, and add a new method and then return the revised definition.




Role of a metaclass's __call__() method when creating a class instance

If you've done Python programming for more than a few months you'll eventually stumble upon code that looks like this:

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

The latter is possible when you implement the __call__() magic method on the class.

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

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

The __call__() method is invoked when an instance of a class is used as a callable. But as we've seen from previous answers a class itself is an instance of a metaclass, so when we use the class as a callable (ie when we create an instance of it) we're actually calling its metaclass's __call__() method. At this point most Python programmers are a bit confused because they've been told that when creating an instance like this instance = SomeClass() you're calling it's __init__() method. Some who've dug a bit deeper know that before __init__() there's __new__() . Well, today another layer of truth is being revealed, before __new__() there's the metaclass's __call__() .

Let's study the method call chain from specifically the perspective of creating an instance of a class.

This is a metaclass that logs exactly the moment before an instance is created and the moment it's about to return it.

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

This is a class that uses that metaclass

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__()."

And now let's create an instance of 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.

The code above doesn't actually do anything other than logging the task and then delegating the actual work to the parent (ie keeping the default behavior). So with type being Meta_1 's parent class, we can imagine that this would be the pseudo implementation of 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

We can see that the metaclass's __call__() method is the one that's called first. It then delegates creation of the instance to the class's __new__() method and initialization to the instance's __init__() . It's also the one that ultimately returns the instance.

From the above it stems that the metaclass's __call__() is also given the opportunity to decide whether or not a call to Class_1.__new__() or Class_1.__init__() will eventually be made. Over the course of its execution it could actually return an object that hasn't been touched by either of these methods. Take for example this approach to the singleton pattern:

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__()."

Let's observe what happens when repeatedly trying to create an object of type 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



Python 3 update

There are (at this point) two key methods in a metaclass:

  • __prepare__ , and
  • __new__

__prepare__ lets you supply a custom mapping (such as an OrderedDict ) to be used as the namespace while the class is being created. You must return an instance of whatever namespace you choose. If you don't implement __prepare__ a normal dict is used.

__new__ is responsible for the actual creation/modification of the final class.

A bare-bones, do-nothing-extra metaclass would like:

class Meta(type):

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

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

간단한 예 :

Say you want some simple validation code to run on your attributes -- like it must always be an int or a str . Without a metaclass, your class would look something like:

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

As you can see, you have to repeat the name of the attribute twice. This makes typos possible along with irritating bugs.

A simple metaclass can address that problem:

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

This is what the metaclass would look like (not using __prepare__ since it is not needed):

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)

A sample run of:

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

Note : This example is simple enough it could have also been accomplished with a class decorator, but presumably an actual metaclass would be doing much more.

The 'ValidateType' class for reference:

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



The tl;dr version

The type(obj) function gets you the type of an object.

The type() of a class is its metaclass .

To use a metaclass:

class Foo(object):
    __metaclass__ = MyMetaClass



나는 ONLamp 소개가 메타 클래스 프로그래밍에 잘 쓰여 있고 몇 년 전에 이미 주제에 대한 훌륭한 소개를하고 있다고 생각한다.

http://www.onlamp.com/pub/a/python/2003/04/17/metaclasses.html

즉, 클래스는 인스턴스 생성을위한 청사진이며, 메타 클래스는 클래스 생성을위한 청사진입니다. Python 클래스에서이 동작을 가능하게하는 퍼스트 클래스 객체가되어야한다는 것을 쉽게 알 수 있습니다.

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

말할 것도 남은 것은 : 만약 당신이 어떤 메타 클래스인지 모른다면, 당신이 필요로하지 않을 확률은 99 %입니다.




Links