“Can't instantiate abstract class … with abstract methods” on class that shouldn't have any abstract method

前端 未结 4 1752
遇见更好的自我
遇见更好的自我 2021-01-11 14:12

Take the following minimal example:

import abc

class FooClass(object):
  __metaclass__ = abc.ABCMeta

  @abc.abstractmethod
  def FooMethod(self):
    raise         


        
相关标签:
4条回答
  • 2021-01-11 15:03

    I know this topic is really old but... That is really a nice question.

    It doesn't work because abc can only check for abstract methods during instatiation of types, that is, when type('Derived', (FooClass,), {}) is running. Any setattr done after that is not accessible from abc.

    So, setattr wont work, buuut... Your problem of addressing the name of a class that wasn't previously declared or defined looks solvable:

    I wrote a little metaclass that lets you use a placeholder "clazz" for accessing any class that will eventually get the method you are writing outside a class definition.

    That way you won't get TypeError from abc anymore, since you can now define your method BEFORE instatiating your type, and then pass it to type at the dict argument. Then abc will see it as a proper method override.

    Aaand, with the new metaclass you can refer to the class object during that method. And this is super, because now you can use super! =P I can guess you were worried about that too...

    Take a look:

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

    The output is:

    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 讨论(0)
  • 2021-01-11 15:05

    Because the docs say so:

    Dynamically adding abstract methods to a class, or attempting to modify the abstraction status of a method or class once it is created, are not supported. The abstractmethod() only affects subclasses derived using regular inheritance; “virtual subclasses” registered with the ABC’s register() method are not affected.

    A metaclass is only called when a class is defined. When abstractmethod has marked a class as abstract that status won't change later.

    0 讨论(0)
  • 2021-01-11 15:05

    Jochen is right; the abstract methods are set at class creation and won't me modified just because you reassign an attribute.

    You can manually remove it from the list of abstract methods by doing

    DerivedType.__abstractmethods__ = frozenset()
    

    or

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

    as well as setattr, so it doesn't still think that FooMethod is abstract.

    0 讨论(0)
  • 2021-01-11 15:07

    Well, if you must do it this way, then you could just pass a dummy dict {'FooMethod':None} as the third argument to type. This allows derived_type to satisfy ABCMeta's requirement that all abstract methods be overridden. Later on you can supply the real 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 讨论(0)
提交回复
热议问题