[python] `classmethod`와 메타 클래스 메소드의 차이점은 무엇입니까?

파이썬에서는 @classmethod데코레이터를 사용하여 클래스 메소드를 만들 수 있습니다 .

>>> class C:
...     @classmethod
...     def f(cls):
...             print(f'f called with cls={cls}')
...
>>> C.f()
f called with cls=<class '__main__.C'>

또는 메타 클래스에서 일반적인 (인스턴스) 메소드를 사용할 수 있습니다.

>>> class M(type):
...     def f(cls):
...             print(f'f called with cls={cls}')
...
>>> class C(metaclass=M):
...     pass
...
>>> C.f()
f called with cls=<class '__main__.C'>

의 결과에서 알 수 있듯이이 C.f()두 가지 접근 방식은 유사한 기능을 제공합니다.

@classmethod메타 클래스에서 일반 메소드를 사용 하고 사용하는 것의 차이점은 무엇입니까 ?



답변

클래스는 메타 클래스의 인스턴스이므로 메타 클래스의 “인스턴스 메소드”가 클래스 메소드처럼 작동하는 것은 예상치 못한 일이 아닙니다.

그러나 그렇습니다. 차이점이 있습니다. 그중 일부는 의미 이상의 것입니다.

  1. 가장 중요한 차이점은 메타 클래스의 메소드가 클래스 인스턴스 에서 “보이지”않는다는 것 입니다. 파이썬에서 속성 조회 (간단한 방식으로-설명자가 우선 할 수 있음)가 인스턴스에서 속성을 검색합니다-인스턴스에 속성이 없으면 Python은 해당 인스턴스의 클래스를 찾은 다음 검색을 계속합니다. 클래스의 슈퍼 클래스 이지만 클래스의 클래스에는 없습니다 . Python stdlib는 abc.ABCMeta.register메소드 에서이 기능을 사용합니다 . 클래스 자체와 관련된 메소드는 충돌없이 인스턴스 속성으로 자유롭게 재사용 할 수 있기 때문에이 기능을 유용하게 사용할 수 있습니다 (그러나 메소드는 여전히 충돌합니다).
  2. 명백한 또 다른 차이점은 메타 클래스에 선언 된 메소드는 여러 클래스에서 사용할 수 있다는 것입니다. 다른 클래스 계층 구조가 있고 처리하는 것과 전혀 관련이 없지만 모든 클래스에 공통적 인 기능을 원할 경우 , 당신은 mixin 클래스를 생각해 내야합니다. 믹스 인 클래스는 두 계층 모두에 기본으로 포함되어야합니다 (예를 들어, 어플리케이션 레지스트리에 모든 클래스를 포함시키는 것). (NB. 믹스 인은 때때로 메타 클래스보다 더 나은 호출 일 수 있습니다)
  3. classmethod는 특수한 “classmethod”객체이고 메타 클래스의 메서드는 일반적인 함수입니다.

따라서 클래스 메소드가 사용하는 메커니즘은 ” 서술자 프로토콜 “입니다. 일반 함수에는 인스턴스에서 검색 할 때 인수 __get__를 삽입하고 self클래스에서 검색 할 때 해당 인수를 비워 두는 메소드가 있지만, classmethod객체에는 다른 __get__클래스가 있으며, 클래스 자체를 “소유자” 두 상황 모두에서 첫 번째 매개 변수

이것은 대부분 실질적인 차이를 만들지 않지만, 함수에 메소드에 대한 액세스를 원한다면, 동적으로 데코레이터를 추가하거나 메타 클래스의 메소드에 대해 다른 것을 추가 meta.method하기 위해 사용할 준비가 된 함수 cls.my_classmethod.__func__ 클래스 메소드에서 검색하는 데 사용해야 classmethod하지만 래핑을 수행하는 경우 다른 오브젝트 를 작성 하고 다시 지정해야합니다.

기본적으로 다음 두 가지 예가 있습니다.


class M1(type):
    def clsmethod1(cls):
        pass

class CLS1(metaclass=M1):
    pass

def runtime_wrap(cls, method_name, wrapper):
    mcls = type(cls)
    setattr(mcls, method_name,  wrapper(getatttr(mcls, method_name)))

def wrapper(classmethod):
    def new_method(cls):
        print("wrapper called")
        return classmethod(cls)
    return new_method

runtime_wrap(cls1, "clsmethod1", wrapper)

class CLS2:
    @classmethod
    def classmethod2(cls):
        pass

 def runtime_wrap2(cls, method_name, wrapper):
    setattr(cls, method_name,  classmethod(
                wrapper(getatttr(cls, method_name).__func__)
        )
    )

runtime_wrap2(cls1, "clsmethod1", wrapper)

다시 말해 , 메타 클래스에 정의 된 메소드가 인스턴스에서 볼 수 있다는 점과 classmethod객체 가 볼 수 없다는 중요한 차이점을 제외하고는 런타임에 다른 차이점이 모호하고 의미가없는 것처럼 보이지만 언어가 필요하지 않기 때문에 발생합니다. 클래스 메쏘드에 대한 특별한 규칙으로 진행 : 언어 디자인의 결과로 클래스 메쏘드를 선언하는 두 가지 방법이 가능합니다. 하나는 클래스 자체가 하나의 객체이고 다른 하나는 가능성이 있기 때문입니다. 인스턴스와 클래스에서 속성 액세스를 전문화 할 수있는 디스크립터 프로토콜 사용 :

classmethod내장은 네이티브 코드로 정의되고 있지만, 순수한 파이썬으로 코딩 할 수 있으며, 동일한 방식으로 작동합니다. 5 라인 수준의 울부 짖는 소리는로 사용할 수 있습니다 classmethod내장없이 런타임 차이 장식 @classmethod" at all (though distinguishable through introspection such as calls toisinstance , and even물론 repr`) :


class myclassmethod:
    def __init__(self, func):
        self.__func__ = func
    def __get__(self, instance, owner):
        return lambda *args, **kw: self.__func__(owner, *args, **kw)

그리고 메소드 외에 @property메타 클래스에서 와 같은 특수 속성 은 놀라운 동작없이 전혀 동일하게 특수 클래스 속성으로 작동 한다는 점을 명심해야 합니다.


답변

질문에서 @classmethod와 같이 말하면 메타 클래스와 비슷하게 보일 수 있지만 목적이 다릅니다. @classmethod의 인수에 삽입 된 클래스 는 일반적으로 인스턴스 (예 : 대체 생성자)를 구성하는 데 사용됩니다. 반면에 메타 클래스는 일반적으로 클래스 자체를 수정하는 데 사용됩니다 (예 : Django가 모델 DSL로 수행하는 것과 유사 함).

그것은 클래스 메소드 내부에서 클래스를 수정할 수 없다고 말하는 것이 아닙니다. 그러나 문제는 왜 클래스를 처음에 수정하려는 방식으로 정의하지 않았습니까? 그렇지 않은 경우 여러 클래스를 사용하도록 리 팩터를 제안 할 수 있습니다.

첫 번째 예제를 조금 확장 해 봅시다.

class C:
    @classmethod
    def f(cls):
        print(f'f called with cls={cls}')

파이썬 문서 에서 빌리면 위의 내용은 다음과 같이 확장됩니다.

class ClassMethod(object):
    "Emulate PyClassMethod_Type() in Objects/funcobject.c"

    def __init__(self, f):
        self.f = f

    def __get__(self, obj, klass=None):
        if klass is None:
            klass = type(obj)
        def newfunc(*args):
            return self.f(klass, *args)
        return newfunc

class C:
    def f(cls):
        print(f'f called with cls={cls}')
    f = ClassMethod(f)

어떻게 참고 __get__인스턴스 또는 클래스 (또는 둘 다) 하나를 사용할 수 있으므로 둘 다 할 수 C.fC().f. 이것은 AttributeErrorfor 를 던질 메타 클래스 예제와 다릅니다 C().f.

또한 메타 클래스 예제에서는에 f존재하지 않습니다 C.__dict__. 로 속성 f을 조회 할 때 C.f인터프리터는 C.__dict__찾은 다음 실패한 후 type(C).__dict__()를 봅니다 M.__dict__. 당신이 유연성을 무시하려는 경우에 문제가 있습니다 f에서 C나는이 이제까지 실용화 될 것입니다 의심하지만,.


답변

귀하의 예에서, 차이점은 M을 메타 클래스로 설정하는 다른 클래스에 있습니다.

class M(type):
    def f(cls):
        pass

class C(metaclass=M):
    pass

class C2(metaclass=M):
    pass

C.f()
C2.f()
class M(type):
     pass

class C(metaclass=M):
     @classmethod
     def f(cls):
        pass

class C2(metaclass=M):
    pass

C.f()
# C2 does not have 'f'

메타 클래스에 대한 자세한 내용 메타 클래스
에 대한 (구체적인) 사용 사례는 무엇입니까?


답변

@classmethod와 Metaclass는 모두 다릅니다.

파이썬의 모든 것은 객체입니다. 모든 것은 모든 것을 의미합니다.

메타 클래스 란 무엇입니까?

말했듯이 모든 것은 객체입니다. 클래스는 사실 객체입니다. 클래스는 공식적으로 메타 클래스라고 불리는 다른 신비한 객체의 인스턴스입니다. 파이썬의 기본 메타 클래스는 지정되지 않은 경우 “type”입니다

기본적으로 정의 된 모든 클래스는 유형의 인스턴스입니다.

클래스는 메타 클래스의 인스턴스입니다

metioned 행동을 이해하는 것은 중요하지 않습니다

  • 클래스는 메타 클래스의 인스턴스입니다.
  • 모든 인스턴스화 된 객체와 마찬가지로 객체 (인스턴스)는 클래스에서 속성을 가져옵니다. 클래스는 메타 클래스에서 속성을 얻습니다.

다음 코드를 고려하십시오

class Meta(type):
    def foo(self):
        print(f'foo is called self={self}')
        print('{} is instance of {}: {}'.format(self, Meta, isinstance(self, Meta)))

class C(metaclass=Meta):
    pass

C.foo()

어디,

  • 클래스 C는 클래스 메타의 인스턴스입니다
  • “클래스 C”는 “클래스 메타”의 인스턴스 인 클래스 오브젝트입니다.
  • 다른 객체 (인스턴스) “클래스 C”와 마찬가지로 “클래스 메타”클래스에 정의 된 속성 / 메소드에 액세스 할 수 있습니다.
  • 따라서 “C.foo ()”디코딩. “C”는 “Meta”의 인스턴스이고 “foo”는 “C”인 “Meta”의 인스턴스를 통해 호출하는 메소드입니다.
  • “foo”메소드의 첫 번째 인수는 “classmethod”와 달리 클래스가 아닌 인스턴스에 대한 참조입니다.

“class C”가 “Class Meta의 인스턴스 인 것처럼 확인할 수 있습니다.

  isinstance(C, Meta)

클래스 방법은 무엇입니까?

파이썬 메소드는 바인딩되어 있습니다. 파이썬은 인스턴스에서만 메소드를 호출해야한다는 제한을 부과합니다. 때로는 인스턴스를 만들지 않고 인스턴스 (java의 정적 멤버와 거의 유사)없이 클래스를 통해 직접 메서드를 호출하려고 할 수도 있습니다. 기본 인스턴스는 메서드를 호출해야합니다. 해결 방법으로 파이썬은 내장 함수 classmethod를 제공하여 주어진 메소드를 인스턴스 대신 클래스에 바인딩합니다.

클래스 메소드는 클래스에 바인딩됩니다. 인스턴스 (자체) 대신 클래스 자체를 참조하는 적어도 하나의 인수가 필요합니다.

내장 함수 / 데코레이터 클래스 메소드를 사용하는 경우 첫 번째 인수는 인스턴스 대신 클래스에 대한 참조입니다

class ClassMethodDemo:
    @classmethod
    def foo(cls):
        print(f'cls is ClassMethodDemo: {cls is ClassMethodDemo}')

“classmethod”를 사용 했으므로 다음과 같이 인스턴스를 작성하지 않고 “foo”메소드를 호출합니다.

ClassMethodDemo.foo()

위의 메소드 호출은 True를 반환합니다. 첫 번째 인수 cls는 실제로 “ClassMethodDemo”에 대한 참조이므로

요약:

  • Classmethod는 “클래스 (전통 cls) 자체에 대한 참조”인 첫 번째 인수를받습니다.
  • 메타 클래스의 메소드는 클래스 메소드가 아닙니다. 메타 클래스의 메소드는 “클래스가 아닌 인스턴스 (전통적으로 자체)”에 대한 첫 번째 인수를받습니다.

답변