TypeError: 未绑定方法 __init__() .... 在重新打包后的单元测试中

1 投票
4 回答
960 浏览
提问于 2025-04-16 02:09

我刚刚重新打包了我的程序。之前,所有的模块都放在一个叫“whyteboard”的包里,还有一个“fakewidgets”的包,里面放了一些假冒的图形界面测试对象。

现在,我把所有的模块都放在了不同的包里,比如说 whyteboard.gui、whyteboard.misc 和 whyteboard.test - 现在 fakewidgets 就在这里面。

不过,当我运行测试的时候,出现了一个异常,

  File "/home/steve/Documents/whyteboard/whyteboard/gui/canvas.py", line 77, in __init__
    wx.ScrolledWindow.__init__(self, tab, style=wx.NO_FULL_REPAINT_ON_RESIZE | wx.CLIP_CHILDREN)
TypeError: unbound method __init__() must be called with ScrolledWindow instance as first argument (got Canvas instance instead)

这是相关的类

class Canvas(wx.ScrolledWindow):
    def __init__(self, tab, gui, area):
        wx.ScrolledWindow.__init__(self, tab, style=wx.NO_FULL_REPAINT_ON_RESIZE | wx.CLIP_CHILDREN)

但是,我的程序加载和运行都没问题,只有单元测试出问题。代码是一样的,只是我测试中导入的代码需要从新包里引入。

之前:

import os
import wx

import fakewidgets
import gui
import lib.mock as mock

from canvas import Canvas, RIGHT, DIAGONAL, BOTTOM
from fakewidgets.core import Bitmap, Event, Colour

from lib.configobj import ConfigObj
from lib.pubsub import pub
from lib.validate import Validator

现在:

import os
import wx

import whyteboard.test
import whyteboard.gui.frame as gui

from whyteboard.lib import ConfigObj, mock, pub, Validator
from whyteboard.gui.canvas import Canvas, RIGHT, DIAGONAL, BOTTOM
from whyteboard.test.fakewidgets.core import Bitmap, Event, Colour, PySimpleApp

值得注意的是,fakewidgets 包里做了一些小把戏,让我的程序觉得它在使用 wxPython 的类,尽管实际上它们是模拟的。这是从一个被 whyteboard.test.fakewidgets 导入的模块的 __init__ 文件里来的。

class Window(object):
    def __init__(self, parent, *args, **kwds):
        self.parent = parent
        self.Enabled = True
        self.calls = []
        self.size = (0, 0)
        self.captured = False

    def GetClientSizeTuple(self):
        return (0, 0)
        self.captured = True

    def GetId(self):
        pass

    def Fit(self):
        pass

    def SetFocus(self):
        pass

    def PrepareDC(self, dc):
        pass

    def Destroy(self):
        pass

...


class ScrolledWindow(Window):
    def SetVirtualSize(self, *size):
        pass

    def SetVirtualSizeHints(self, *size):
        pass

import wx
wx.__dict__.update(locals())

4 个回答

1

请在定义 class Canvas 之前,以及在 Canvas.__init__ 的第一行,打印出 wxwx.ScrolledWindow。我很怀疑这两个打印出来的结果会不一样。

你有没有在使用 __new__ 或者元类之类的东西搞什么花样?

1

代码是一样的,只是我测试用的导入部分从新的包中拉取的内容不同。

这听起来像是你的导入部分引入了你没有预料到的东西。有一次,我把自己的一个文件命名成了和系统模块一样的名字,结果花了我好几个小时才搞清楚出了什么问题。

试试看当你改变了 sys.path 会发生什么。

3

当你使用 import whyteboard.test 这行代码时,是否会自动运行 whyteboard.test.fakewidgets.core 呢?我觉得问题在于 Canvas 这个东西是在模拟代码运行之前就被创建了。这就解释了为什么会出现混乱。

>>> import wx
>>> class Test1(wx.Window):
...    pass
... 
>>> wx.Window = object
>>> class Test2(wx.Window):
...    pass
... 
>>> dir(Test1)[:10]
['AcceleratorTable', 'AcceptsFocus', 'AcceptsFocusFromKeyboard', 
 'AddChild', 'AddPendingEvent', 'AdjustForLayoutDirection', 
 'AssociateHandle', 'AutoLayout', 'BackgroundColour', 'BackgroundStyle']
>>> dir(Test2)[:10]
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', 
 '__getattribute__', '__hash__', '__init__', '__module__', '__new__']

在你之前发的旧文件里,fakewidgets 是在 canvas 之前被引入的。

如果这样还不行,可以把这段代码直接放在 import wx 之后,其他引入代码之前:

import inspect

class DummyMeta(type):
    def __new__(meta, clsname, bases, clsdict):
        if clsname == 'Canvas':
            lineno = inspect.stack()[1][2]
            print "creating Canvas with mro: {0}".format(inspect.getmro(bases[0]))
            print "file:{0}:{1}".format(__file__, lineno)
        return super(DummyMeta, meta).__new__(meta, clsname, bases, clsdict)

class ScrolledWindowDummy(wx.Window):
    __metaclass__ = DummyMeta

wx.ScrolledWindow = ScrolledWindowDummy

这样可以显示出 Canvas 类是在模拟代码生效之前就被创建了,并且会告诉你具体在哪个文件和哪一行发生了这个情况。简单来说,对于 MRO(方法解析顺序),你应该看不到任何来自 wx 的内容。如果我说错了,那你根本不会看到任何东西,因为在创建任何名为 'Canvas' 的类之前,你已经把 ScrolledWindowDummy 替换成了一个没有 DummyMeta 类型的类。

撰写回答