2011-11-10 16 views
7

次最小限の例を見てみましょう:main()を実行クラスの「抽象メソッドで...抽象クラスをインスタンス化できません」

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

はあなたを取得します。

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

(例外は、instance = derived_type()行で発生します)

FooMethodは、抽象的ではありません。私はで上書きしました。。だから、なぜこの例外が発生していますか?

免責事項:はい、私は明示的にclassの構文を使用して、まったく同じことを達成できます。 (そしてもっとうまく、私はそれを動作させることができます!)しかし、これは最小限のテストケースであり、より大きい例は動的にクラスを作成することです。 :-)そして、なぜこれがうまくいかないのか不思議です。

編集:そして、他の明白な非答えを防ぐために:私はtypeに第三引数にBarOverrideを渡したくない:実際の例では、BarOverridederived_typeがそれにバインドされている必要があります。 をderived_typeの作成後に定義できる場合は、これを行う方が簡単です。 (私はこれを行うことができない場合は、なぜ?)

+1

。クラスを作成するときに辞書に 'FooMethod'を入れるだけでいいのですか? –

+0

@SvenMarnach:実際の例の 'BarOverride'は、それにバインドされた派生クラスを持つ必要があります。後でこの関数を作成するのが最も簡単な方法です。 – Thanatos

+0

私はその点を知りません。 「派生クラスをバインドする必要がある」とはどういう意味ですか? –

答えて

4

Because the docs say so:

動的にクラスに抽象メソッドを追加すること、または にしようと、それが作成されると、メソッドやクラスの抽象化の状態を変更し、 はサポートされていません。 abstractmethod()は、正規継承を使用して派生したサブクラス にのみ影響します。 ABCのregister()メソッドを使用して登録された の "仮想サブクラス"は影響を受けません。

メタクラスは、クラスが定義されている場合にのみ呼び出されます。 abstractmethodがクラスを抽象クラスとしてマークした場合、ステータスは後で変更されません。

+0

これは答えとして受け入れられます(私のコードが機能しない理由が含まれているため)が、3つすべてが面白いです。私はunutbuの答えを手元の問題の回避策として選択しました。 – Thanatos

2

このようにする必要がある場合は、入力する3番目の引数としてダミーの辞書{'FooMethod':None}を渡すことができます。これにより、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

しかし、これは 'FooMethod'抽象化のポイントのいくつかを打ち消します。なぜなら、型作成と' setattr'が分離されていると、メソッドを設定する前に 'derived_type'をインスタンス化できるからです。 – agf

+0

'ABCMeta'型のクラスは他のクラスと同じようにサルパッチ可能でなければなりません。第3引数を与えることによって、 'Derived'は' FooMethod'を定義しなければならないことを認めます。それは後でその実装についてあなたの心を変えることからあなたを妨げるものではありません。 – unutbu

3

Jochenが正しいです。抽象メソッドはクラス作成時に設定され、属性を再割り当てするだけでは変更されません。

それはまだFooMethodが抽象的であると考えていないので、手動で、

DerivedType.__abstractmethods__ = frozenset() 

または

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

などsetattrを行うことによって、抽象メソッドのリストから削除することができます。

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

関連する問題