Python元类冲突/类型错误

3

我正在调试一个 Python 脚本(Python 并不是我首选的语言),这是我第一次在 Python 中使用元类。当我按照下面的代码运行时,我遇到了元类冲突错误。

TypeError: Error when calling the metaclass bases
    metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases

在尝试解决它时,我将 MySQLMSoftware 中的元类声明注释掉了,认为这是多余的,因为它继承自具有相同元类声明的类,但这会导致:

TypeError: Error when calling the metaclass bases
    module.__init__() takes at most 2 arguments (3 given)

任何见解、指导或方向都将不胜感激。我一直在阅读有关Python元类实现的文章,但至今仍未找到问题所在。 MSoftware.py
from abc import ABCMeta, abstractmethod

class MSoftware(object) :
    __metaclass__ = ABCMeta

    def __init__(self,name,spark=None,mysql=None):
        self.name = name
        ...

MySQLMSoftware.py

from mouse import Mouse, MSoftware
from abc import ABCMeta, abstractmethod

class MySQLMSoftware(MSoftware): # Traceback always goes here
    __metaclass__ = ABCMeta

    MAX_ROWS = 30000000

    def __init__(self,name,years,spark=None,mysql=None):
        MSoftware.__init__(self,name,spark,mysql)
        self.years = years
        ...

TTime.py

from mouse.Mouse import Mouse
from mouse.MySQLMSoftware import MySQLMSoftware

class TTime(MySQLMSoftware) :

    DATABASE = "Database"
    NAME = "table"
    YEARS = [2014,2016]

    def __init__(self,spark=None,mysql=None):
        MySQLMSoftware.__init__(self,TTime.NAME,TTime.YEARS,spark,mysql)
        ...

main.py

import sys
from mouse.Mouse import Mouse
from mouse.TTime import TTime

...

在我看来,MSoftware 似乎有一个元类(不是 ABCMeta)——这是真的吗?如果是这样,你可以尝试使用 __metaclass__ = type('MySQLMSoftware', (type(MSoftware), ABCMeta), {})。但我没有这些类来测试,所以不能保证它会起作用... - mgilson
你使用的是哪个版本的Python? - Keozon
Python 2.7。@mgilson我相信MSoftware的元类是ABCMeta,但它正在扩展“object”,这是我第一次看到“__metaclass__”声明。我会尝试您的建议。 - ivywit
@Keozon -- 这肯定是Python2.x,因为__metaclass__语法在Python3.x中有所改变,而在Python3.x中,在类上设置__metaclass__属性不会出现错误。 - mgilson
我不确定它是否与真正的问题相关,还是仅仅是将代码复制到问题中的副产品,但你在MSoftware.py文件中显示了一个MSoftware类,但是你的其他文件(发生异常的文件)继承自mouse模块中定义的MSoftware类,而不是MSoftware模块。你能找到并展示mouse.py吗?也许mouse.MSoftwareMSoftware.MSoftware不是同一个东西。 - Blckknght
@Blckknght 除了 main.py 文件外,所有文件都是 mouse 模块的一部分。该模块创建在名为 mouse 的文件夹中,其中包含一个 init.py 文件。 - ivywit
2个回答

5
问题在于当选择元类时,Python会选择最继承的版本。然而,在这种情况下,您有两个冲突的元类(ABCMetaMSoftware)在起作用。
我认为python3.x文档在这方面比python2.x文档略微更正确:
适当的类定义元类的确定如下: - 如果没有给出基类和显式元类,则使用type()。 - 如果给出了显式元类并且它不是type()的实例,则直接使用它作为元类。 - 如果给出了type()的实例作为显式元类或定义了基类,则使用最终派生的元类。
一种解决方法是创建一个新的元类,将两个元类混合在一起:
class NewMeta(type(MSoftware), ABCMeta):
    pass

在这种情况下,由于所有元类都是MSoftware元类的“实例”,因此将选择NewMeta,因为它是最派生的元类,并且应该可以工作(前提是MSoftware的元类可以用于协同多继承)。
以下是一个示例,我动态创建NewMeta来克服一些虚拟类的问题:
class Meta1(type):
    pass

class Meta2(type):
    pass

class Class1(object):
    __metaclass__ = Meta1


# TypeError!
# class Class2(Class1):
#     __metaclass__ = Meta2

class Class2(Class1):
    __metaclass__ = type('Class2Meta', (type(Class1), Meta2), {})

非常感谢您的帮助。我已经在本地测试过了,问题似乎没有出现。如果MSoftware的元类也是ABCMeta,那么它们之间不应该有冲突,这样理解对吗?可能是ipython笔记本在服务器上运行时出了问题。之前我遇到过一个问题,就是它无法正确导入任何东西。我会与服务器管理员联系,看看他们能否在那里修复它。无论如何,非常感谢您的帮助! - ivywit
1
@phelpsiv -- 如果 MSoftware 的元类也是 ABCMeta,那么就不应该有冲突(尽管在这种情况下你也不需要在你的类中提供元类)。要检查 MSoftware 的元类,可以使用 print type(MSoftware) - mgilson
我花了4天时间才找到这个答案。你救了我的一天! - Thanh Nguyen

3
问题与您的导入有关,以及相同名称的类和模块之间的混淆。在MySQLMSoftware.py中导入的MSoftware是在MSoftware.py中实现的模块,而不是该模块内具有相同名称的类。如果要获取后者(而不更改导入),则需要使用MSoftware.MSoftwareMouse类和模块可能存在类似的问题(这甚至更糟,因为顶级包也命名为mouse)。
尝试更改以下行:
from mouse import Mouse, MSoftware

对于这两行代码:

from mouse.Mouse import Mouse
from mouse.MSoftware import MSoftware

这将解决元类冲突问题,并使MySQLMSoftware类中的元类声明变得不必要。
需要注意的是,从更高层次的角度来看,这个问题的根本原因是模块设计不佳。您有几个模块,每个模块都包含一个与它们同名的单个类。这是其他语言(如Java)中常见的项目布局,但在Python中并非必要或理想。mouse.Mouse.Mouse从来没有好的理由成为一个东西。
也许您应该将大多数(或可能全部)包模块合并为较少的文件,可能首先将大部分内容放入mouse/__init__.py中(或者如果您将它们全部合并并且不再需要包,则放入顶级mouse.py中)。这样就不会有太多冗余的导入了。

考虑到错误信息“module.init() takes at most 2 arguments (3 given)”,这是有道理的。 - Dunes
这个答案对我当前的实现非常有帮助。谢谢! - Lëmön

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接