“无法实例化抽象类”使用抽象方法ÉD;在不应该有任何抽象方法的类上

发布于 2024-12-15 06:56:49 字数 1020 浏览 1 评论 0原文

采取以下最小示例:

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 = obliged_type() 行上。)

但是 FooMethod不应该是抽象的:我已经用 BarOverride 覆盖了它。那么,为什么会引发异常呢?

免责声明:是的,我可以使用显式的class语法,并完成完全相同的事情。 (更好的是,我可以让它工作!)但这只是一个最小的测试用例,更大的示例是动态创建类。 :-) 我很好奇为什么这不起作用。

编辑:并且为了防止其他明显的非答案:我不想将第三个参数中的 BarOverride 传递给 type:真实的例子,BarOverride 需要绑定衍生类型。如果我可以在创建衍生类型之后定义BarOverride,那么这样做会更容易。 (如果我做不到,那为什么?)

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

仅一夜美梦 2024-12-22 06:56:49

因为文档是这么说的:

向类动态添加抽象方法,或尝试
创建方法或类后修改其抽象状态,
不支持。 Abstractmethod() 只影响子类
使用常规继承派生; “虚拟子类”注册
使用 ABC 的 register() 方法不受影响。

仅当定义类时才调用元类。当abstractmethod将一个类标记为抽象时,该状态以后不会改变。

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.

榆西 2024-12-22 06:56:49

约亨是对的;抽象方法是在类创建时设置的,我不会仅仅因为重新分配属性而对其进行修改。

手动将其从抽象方法列表中删除,因此它仍然不会认为 FooMethod 是抽象的。

DerivedType.__abstractmethods__ = frozenset()

您可以通过执行或

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

以及 setattr

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.

孤君无依 2024-12-22 06:56:49

我知道这个话题确实很老了但是......这确实是一个很好的问题。

它不起作用,因为 abc 只能在类型实例化期间(即 type('Derived', (FooClass,), {}) 运行时)检查抽象方法。此后完成的任何 setattr 都无法从 abc 访问。

所以,setattr 不起作用,但是......
您解决先前未声明或定义的类名称的问题看起来是可以解决的:

我编写了一个小元类,它允许您使用占位符“clazz”来访问任何类,该类最终将获得您在类外部编写的方法定义。

这样你就不会再从 abc 得到 TypeError 了,因为你现在可以在实例化你的类型之前定义你的方法,然后将它传递给 dict 参数的 type 。然后 abc 会将其视为正确的方法覆盖。

Aaand,使用新的元类,您可以在该方法中引用类对象。
这就是 super,因为现在你可以使用 super! =P
我猜你也担心这个……

看一下:

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

输出是:

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...

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...
娇俏 2024-12-22 06:56:49

好吧,如果你必须这样做,那么你可以只传递一个虚拟字典 {'FooMethod':None} 作为 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()

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()
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文