2011-11-10 5 views
7

는 다음과 같은 최소한의 예를 가지고하지 말았어야 클래스의 "추상 메소드와 추상 클래스 ... 인스턴스화 할 수 없습니다"어떤 추상적 인 방법

import abc 

class FooClass(object): 
    __metaclass__ = abc.ABCMeta 

    @abc.abstractmethod 
    def FooMethod(self): 
    raise NotImplementedError() 


def main(): 
    derived_type = type('Derived', (FooClass,), {}) 

    def BarOverride(self): 
    print 'Hello, world!' 
    derived_type.FooMethod = BarOverride 

    instance = derived_type() 

main()을 실행하면을 가져옵니다

TypeError: Can't instantiate abstract class Derived with abstract methods FooMethod 

(예외는 instance = derived_type() 행에서 발생합니다.)

FooMethod은 추상적이 아니어야합니다 :로 덮어 썼습니다.3210. 그래서, 왜 이런 예외가 발생합니까?

면책 조항 : 예, 명시 적으로 class 구문을 사용하고 똑같은 작업을 수행 할 수 있습니다. (그리고 더 좋게 만들 수 있습니다!)하지만 이것은 최소한의 테스트 케이스이며 더 큰 예제는 클래스를 동적으로 생성하는 것입니다. :-) 그리고 이것이 왜 효과가 없는지 궁금합니다.

편집 :는 다른 명백한 비 답을 방지하기 위해 : 나는 type에 세 번째 인수에 BarOverride을 통과하지 않으 : 실제 예에서, BarOverridederived_type가 결합 있어야합니다. derived_type을 작성한 후에 BarOverride을 정의 할 수 있으면 더 쉽게 수행 할 수 있습니다. (그때,이 작업을 수행 할 수없는 경우 그 이유는 무엇입니까?)

+1

. 클래스를 생성하는 동안'FooMethod'를 사전에 포함 시키면 어떨까요? –

+0

@SvenMarnach : 실제 예제에서'BarOverride'는 파생 클래스를 바인딩해야합니다. 이후에 함수를 작성하는 것이이 작업을 수행하는 가장 쉬운 방법입니다. – Thanatos

+0

나는 그 점을 이해하지 못한다. "파생 된 클래스를 바인딩해야합니다"라는 의미는 무엇입니까? –

답변

4

Because the docs say so:

가 동적으로 클래스에 추상 메소드를 추가하거나이 생성되면 메소드 나 클래스의 추상화 상태를 수정 를 시도, 은 지원되지 않습니다. abstractmethod()는 정규 상속을 사용하여 파생 된 하위 클래스 에만 영향을줍니다. ABC의 register() 메소드와 함께 에 등록 된 "가상 서브 클래스"는 영향을받지 않습니다.

메타 클래스는 클래스 정의시에만 호출됩니다. abstractmethod이 클래스를 추상으로 표시하면 상태는 나중에 변경되지 않습니다.

+0

대답으로 받아 들여지는 이유는 (내 코드가 작동하지 않는 이유가 있기 때문입니다.)하지만 세 가지 모두 흥미 롭습니다. 현재 문제의 해결 방법으로 unutbu의 답변을 선택했습니다. – Thanatos

2

글쎄, 당신이 이런 식으로 해야하는 경우, 당신은 그냥 입력 할 세 번째 인수로 {'FooMethod':None} 더미 dict 그냥 전달할 수 있습니다. 따라서 derived_type은 ABCMeta의 모든 추상 메서드를 재정의해야한다는 요구 사항을 충족시킬 수 있습니다. 나중에 당신은 진짜를 공급할 수 있습니다 FooMethod :

def main(): 
    derived_type = type('Derived', (FooClass,), {'FooMethod':None}) 
    def BarOverride(self): 
    print 'Hello, world!' 
    setattr(derived_type, 'FooMethod', BarOverride) 
    instance = derived_type() 
+0

그러나 이것은 유형 생성과'setattr'가 분리되어 있다면 메소드를 설정하기 전에'derived_type'을 인스턴스화 할 수 있기 때문에'FooMethod' 추상화의 일부를 무효화합니다. – agf

+0

'ABCMeta' 타입의 클래스는 다른 클래스와 마찬가지로 원숭이 - 패치 가능해야합니다. 세 번째 인수를 제공함으로써'Derived '는'FooMethod'를 정의해야 함을 인정합니다. 그렇다고해서 나중에 구현에 대한 생각을 바꿀 수는 없습니다. – unutbu

3

Jochen이 옳습니다. 추상 메소드는 클래스 생성시 설정되며 속성을 재 할당하기 만하면 수정되지 않습니다.

수동

DerivedType.__abstractmethods__ = frozenset() 

또는

DerivedType.__abstractmethods__ = frozenset(
     elem for elem in DerivedType.__abstractmethods__ if elem != 'FooMethod') 

뿐만 아니라 setattr

을 수행하여 추상적 인 방법 목록에서 제거 할 수 있습니다, 그래서 여전히 FooMethod가 추상적이라고 생각하지 않습니다.

3

이 주제는 정말 오래되었지만 ... 정말 좋은 질문입니다.

abc는 유형을 지정하는 동안, 즉 type('Derived', (FooClass,), {})이 실행 중일 때만 추상 메소드를 확인할 수 있기 때문에 작동하지 않습니다. 그 후에 수행되는 setattr은 abc에서 액세스 할 수 없습니다.

그래서, 않은 setattr 실 거예요 작품은 buuut는 ... 이전에 선언 또는 정의되지 않은 클래스의 이름을 주소의 귀하의 문제는 해결 가능 같습니다

내가 당신이 자리를 사용할 수 있습니다 약간의 메타 클래스를 썼다 클래스 정의 외부에서 작성하는 메소드를 결국 얻을 수있는 클래스에 액세스하기위한 "clazz".

그런 식으로 abc에서 더 이상 TypeError를 얻지 못할 것입니다. 이제 유형을 설명하기 전에 메소드를 정의한 다음 dict 인수에 입력하십시오. 그러면 abc는 적절한 메소드 오버라이드로 간주합니다.

새 메타 클래스를 사용하면 해당 메서드에서 클래스 개체를 참조 할 수 있습니다. 그리고 이것은 슈퍼입니다, 왜냐하면 지금 당신은 슈퍼를 사용할 수 있기 때문입니다!

import abc 
import inspect 

clazz = type('clazz', (object,), {})() 

def clazzRef(func_obj): 
    func_obj.__hasclazzref__ = True 
    return func_obj 

class MetaClazzRef(type): 
    """Makes the clazz placeholder work. 

    Checks which of your functions or methods use the decorator clazzRef 
    and swaps its global reference so that "clazz" resolves to the 
    desired class, that is, the one where the method is set or defined. 

    """ 
    methods = {} 
    def __new__(mcs, name, bases, dict): 
     ret = super(MetaClazzRef, mcs).__new__(mcs, name, bases, dict) 
     for (k,f) in dict.items(): 
      if getattr(f, '__hasclazzref__', False): 
       if inspect.ismethod(f): 
        f = f.im_func 
       if inspect.isfunction(f): 
        for (var,value) in f.func_globals.items(): 
         if value is clazz: 
          f.func_globals[var] = ret 
     return ret 

class MetaMix(abc.ABCMeta, MetaClazzRef): 
    pass 

class FooClass(object): 
    __metaclass__ = MetaMix 
    @abc.abstractmethod 
    def FooMethod(self): 
     print 'Ooops...' 
     #raise NotImplementedError() 


def main(): 
    @clazzRef 
    def BarOverride(self): 
     print "Hello, world! I'm a %s but this method is from class %s!" % (type(self), clazz) 
     super(clazz, self).FooMethod() # Now I have SUPER!!! 

    derived_type = type('Derived', (FooClass,), {'FooMethod': BarOverride}) 

    instance = derived_type() 
    instance.FooMethod() 

    class derivedDerived(derived_type): 
     def FooMethod(self): 
      print 'I inherit from derived.' 
      super(derivedDerived,self).FooMethod() 

    instance = derivedDerived() 
    instance.FooMethod() 

main() 

출력은 다음과 같습니다 : = P 나는

이 봐 ... 당신이 너무 그것에 대해 걱정했다 추측 할 수

Hello, world! I'm a <class 'clazz.Derived'> but this method is from class <class 'clazz.Derived'>! 
Ooops... 
I inherit from derived. 
Hello, world! I'm a <class 'clazz.derivedDerived'> but this method is from class <class 'clazz.Derived'>! 
Ooops... 
클래스의 추상성 클래스 건설 중에 결정됩니다
+0

이것이 잘못된 것 같습니다. 전역()이 아닌 함수의 클로저를 사용하여이 작업을 수행해야합니다. Globals()가 잘못되었습니다 ... 이상하게도 코드는 더 많은 클래스로도 예상대로 작동합니다. – rbertoche

관련 문제