2012-05-25 3 views
12

나는 다음과 같은 파이썬 코드가 : 나는 FOOa 모두 호출되는 메타 클래스의 __setattr__을 기대했을 것이다Python 메타 클래스 : 클래스 정의 중 속성 세트에 __setattr__이 호출되지 않는 이유는 무엇입니까?

class FooMeta(type): 
    def __setattr__(self, name, value): 
     print name, value 
     return super(FooMeta, self).__setattr__(name, value) 

class Foo(object): 
    __metaclass__ = FooMeta 
    FOO = 123 
    def a(self): 
     pass 

합니다. 그러나, 그것은 전혀 호출되지 않습니다. 클래스가 정의 된 후에 Foo.whatever에 무언가를 할당하면 메서드가 호출되어이 호출됩니다.

이 동작이 발생하는 이유는 무엇이며 수업을 만드는 동안 발생하는 과제를 가로채는 방법이 있습니까? 내가 check if a method is being redefined을 원하기 때문에 attrs__new__에 사용하면 효과가 없습니다.

+0

이 정확한 문제는 메타 클래스 구문이 py3에서 변경된 이유입니다! – SingleNegationElimination

답변

20

클래스 블록은 사전을 작성한 다음 클래스 객체를 작성하기 위해 메타 클래스를 호출하는 대략적인 구문 설탕입니다.

이 :

class Foo(object): 
    __metaclass__ = FooMeta 
    FOO = 123 
    def a(self): 
     pass 

꽤 많이 당신이 작성했던 것처럼 나온다 :

d = {} 
d['__metaclass__'] = FooMeta 
d['FOO'] = 123 
def a(self): 
    pass 
d['a'] = a 
Foo = d.get('__metaclass__', type)('Foo', (object,), d) 

만 네임 스페이스 오염없이 (그리고 실제로 결정하는 모든 기반을 통해 검색도 있습니다 metaclass, 또는 metaclass 충돌이 있는지 여부는 여기에서 무시합니다).

__setattr__ 당신은 그것의 인스턴스 중 하나 (클래스 객체)에 대한 속성을 설정하려고 할 때 발생하는 상황을 제어 할 수 있습니다 만, 클래스 블록 내부 당신이 그 일을하지 않는 메타 클래스는 '당신은에 삽입하고 사전 개체이므로 dict 클래스는 메타 클래스가 아니라 현재 진행중인 작업을 제어합니다. 그래서 너는 운이 없어.


파이썬 3.x를 사용하지 않는 한! Python 3.x에서는 metaclass 생성자에 전달되기 전에 클래스 블록 내에 설정된 속성을 누적하기 위해 사용되는 객체를 제어하는 ​​메타 클래스에 클래스 메쏘드 (또는 staticmethod)를 정의 할 수 있습니다. 나에게주는이 실행

from collections import MutableMapping 


class SingleAssignDict(MutableMapping): 
    def __init__(self, *args, **kwargs): 
     self._d = dict(*args, **kwargs) 

    def __getitem__(self, key): 
     return self._d[key] 

    def __setitem__(self, key, value): 
     if key in self._d: 
      raise ValueError(
       'Key {!r} already exists in SingleAssignDict'.format(key) 
      ) 
     else: 
      self._d[key] = value 

    def __delitem__(self, key): 
     del self._d[key] 

    def __iter__(self): 
     return iter(self._d) 

    def __len__(self): 
     return len(self._d) 

    def __contains__(self, key): 
     return key in self._d 

    def __repr__(self): 
     return '{}({!r})'.format(type(self).__name__, self._d) 


class RedefBlocker(type): 
    @classmethod 
    def __prepare__(metacls, name, bases, **kwargs): 
     return SingleAssignDict() 

    def __new__(metacls, name, bases, sad): 
     return super().__new__(metacls, name, bases, dict(sad)) 


class Okay(metaclass=RedefBlocker): 
    a = 1 
    b = 2 


class Boom(metaclass=RedefBlocker): 
    a = 1 
    b = 2 
    a = 3 

: 기본 __prepare__는 단순히 일반 사전을 반환하지만 당신은 키를 재정의 할 수 없습니다 사용자 정의 딕셔너리와 같은 클래스를 구축하고, 당신의 특성을 축적하는 것을 사용할 수

Traceback (most recent call last): 
    File "/tmp/redef.py", line 50, in <module> 
    class Boom(metaclass=RedefBlocker): 
    File "/tmp/redef.py", line 53, in Boom 
    a = 3 
    File "/tmp/redef.py", line 15, in __setitem__ 
    'Key {!r} already exists in SingleAssignDict'.format(key) 
ValueError: Key 'a' already exists in SingleAssignDict 

일부 노트 :이 일하기 전에 호출되고 있기 때문에

  1. __prepare__classmethod 또는 staticmethod 수있다 e metaclass '인스턴스 (클래스)가 존재합니다. ,
  2. type 여전히 실제 dict로 세 번째 매개 변수를 필요, 그래서 당신은 아마 (2) 피할 것 정상적인 내가 dict 서브 클래스 수도 하나
  3. SingleAssignDict 변환하는 __new__ 방법을 가지고 있지만 난 정말 그게 싫어하는 이유는 update과 같은 기본이 아닌 메서드가 기본 메서드의 재정의를 __setitem__과 같이 존중하지 않기 때문입니다. 따라서 하위 클래스 인 collections.MutableMapping을 선호하며 사전을 감싸줍니다.
  4. typetype에 의해 설정 되었기 때문에 실제 Okay.__dict__ 개체는 일반적인 사전입니다. 원하는 사전에 대해 까다로운 내용입니다. 즉, 클래스 작성 후 클래스 속성을 겹쳐 쓰더라도 예외가 발생하지 않습니다. 수퍼 클래스 호출 후 __dict__ 속성을 덮어 쓸 수 있습니다. __new__ 클래스 개체의 사전에 의해 덮어 쓰기를 유지하려는 경우입니다.

는 안타깝게도이 기술은 파이썬 2.x에서 (내가 확인)에서 사용할 수 없습니다. __prepare__ 메쏘드는 호출되지 않습니다. 이것은 Python 2.x에서처럼 메타 블록은 클래스 블록의 특별한 키워드가 아닌 __metaclass__ 매직 속성에 의해 결정됩니다; 이는 메타 클래스가 알려질 때까지 클래스 블록에 대한 속성을 누적하는 데 사용되는 dict 객체가 이미 있음을 의미합니다.

파이썬 2 비교 :

class Foo(object): 
    __metaclass__ = FooMeta 
    FOO = 123 
    def a(self): 
     pass 

는에 거의 동등한 것 :

class Foo(metaclass=FooMeta): 
    FOO = 123 
    def a(self): 
     pass 

존재 : 호출 할 수있는 메타 클래스는 파이썬 3 대, 사전 결정됩니다

d = {} 
d['__metaclass__'] = FooMeta 
d['FOO'] = 123 
def a(self): 
    pass 
d['a'] = a 
Foo = d.get('__metaclass__', type)('Foo', (object,), d) 

대략 다음과 같습니다 :

d = FooMeta.__prepare__('Foo',()) 
d['Foo'] = 123 
def a(self): 
    pass 
d['a'] = a 
Foo = FooMeta('Foo',(), d) 

여기서 사용할 사전은 메타 클래스에서 결정됩니다.

2

클래스 속성은 단일 사전으로 메타 클래스에 전달되며이 속성을 사용하여 클래스의 __dict__ 속성을 한 번에 업데이트하는 것으로 가정합니다. 각 항목에 setattr()을 수행하는 대신 cls.__dict__.update(dct)과 같은 것을 사용하십시오. 더 중요한 것은 C- 랜드에서 모두 처리되었으며 사용자 정의 __setattr__()을 호출하기 위해 작성된 것이 아닙니다.

클래스 이름 공간을 dict으로 전달했기 때문에 메타 클래스의 __init__() 메서드에서 클래스의 속성에 원하는대로 쉽게 처리 할 수 ​​있습니다.

7

클래스를 생성하는 동안 할당이 발생하지 않습니다. 또는 : 그들은 일어나고 있습니다. 그러나 당신이 생각하는 상황에서는 그렇지 않습니다. 모든 클래스 속성은 클래스 본문의 범위에서 수집 마지막 인자로, 메타 클래스 '__new__에 전달됩니다

class FooMeta(type): 
    def __new__(self, name, bases, attrs): 
     print attrs 
     return type.__new__(self, name, bases, attrs) 

class Foo(object): 
    __metaclass__ = FooMeta 
    FOO = 123 

이유 : 클래스 본문의 코드가 실행될 때, 더 클래스는 아직 없다. 즉, 어떤 것도 가로 챌 수있는 기회가 없습니다. .

0

클래스 작성 중에 네임 스페이스는 dict로 평가되고 클래스 이름 및 기본 클래스와 함께 메타 클래스에 인수로 전달됩니다. 그렇기 때문에 클래스 정의 내에 클래스 속성을 할당하는 것이 예상대로 작동하지 않습니다. 빈 클래스를 만들고 모든 것을 할당하지 않습니다. 또한 dict에는 중복 된 키를 가질 수 없으므로 클래스 작성 중 속성은 이미 중복 제거됩니다. 클래스 정의 다음에 속성을 설정하면 사용자 정의 __setattr__을 트리거 할 수 있습니다.

네임 스페이스가 dict이므로 다른 질문에서 제안한 것처럼 중복 된 방법을 확인할 방법이 없습니다. 실제로 할 수있는 유일한 방법은 소스 코드를 파싱하는 것입니다.

관련 문제