Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Take the following minimal example:

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

Running main() gets you:

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

(The exception occurs on the instance = derived_type() line.)

But FooMethod shouldn't be abstract: I've overridden it with BarOverride. So, why is this raising exceptions?

Disclaimer: Yes, I could use the explicit class syntax, and accomplish the exact same thing. (And even better, I can make it work!) But this is a minimal test case, and the larger example is dynamically creating classes. :-) And I'm curious as to why this doesn't work.

Edit: And to prevent the other obvious non-answer: I don't want to pass BarOverride in the third argument to type: In the real example, BarOverride needs to have derived_type bound to it. It is easier to do this if I can define BarOverride after the creation of derived_type. (If I can't do this, then why?)

like image 526
Thanatos Avatar asked Nov 10 '11 21:11

Thanatos


2 Answers

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.

like image 146
Jochen Ritzel Avatar answered Nov 07 '22 06:11

Jochen Ritzel


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.

like image 45
agf Avatar answered Nov 07 '22 06:11

agf