9

这个问题的关键是帮助单元测试。如果我很忙__init__(即__init__进行复杂的初始化),我不能简单地实例化一个类的对象,但我需要模拟/存根在__init__.

为了说明这个问题,这里是一个例子:

class SomeClass(object):
    def __init__(self, dep1, dep2, some_string):
        self._dep1 = dep1
        self._dep2 = dep2
        self._some_string = some_string

        # I would need to mock everything here (imagine some even more
        # complicated example)
        for dep2element in self._dep2:
            dep2element.set_dep(dep1)
        self._dep1.set_some_string(some_string)

    def fun1(self):
        ...
    def fun2(self):
        ...
    def fun3(self):
        ...

为了测试fun*功能,每个测试都必须执行复杂的构造。

class TestSomeClass(TestCase):
    def create_SomeClass(self, some_string):
        dep1 = Mock()
        # mock everything required by SomeClass' constructor

        dep2 = Mock()
        # mock everything required by SomeClass' constructor

        return SomeClass(dep1, dep2, some_string)

    def test_fun1(self):
        sc = self.create_SomeClass('some string')
        ...

    def test_fun2(self):
        sc = self.create_SomeClass('some other string')
        ...

    def test_fun3(self):
        sc = self.create_SomeClass('yet another string')
        ...

我发现这是多余的,并且想知道如何在 python 中优雅地处理这个问题,如果不是通过从构造函数中移动工作的话。

解决方案:

正如@ecatmur 所建议的,要测试一些特定的功能,这段代码应该可以解决问题:

def test_some_method():
    mobject = Mock(SomeClass)
    SomeClass.fun1(mobject)

使用这种方法,所有方法都将被模拟出来。如果fun1调用您想要执行的其他方法(例如fun2),您可以这样做:

def test_some_method():
    mobject = Mock(SomeClass)
    mobject.fun2 = SomeClass.fun2.__get__(mobject)
    SomeClass.fun1(mobject)

SomeClass.fun2.__get__(mobject)将产生instancemethod将提供正确绑定的内容。

¡万岁蟒蛇!

原始问题:

最初的问题集中在将完成的工作__init__转移到单独的init方法和围绕该方法的不同问题上。我通常的做法是做这个

class SomeClass(object):
    def __init__(self, dep1, dep2, some_string)
        self._dep1 = dep1
        self._dep2 = dep2

        # lots of mumbo-jumbo here...

变成这个

class SomeClass(object):
    def __init__(self, dep1, dep2)
        self._dep1 = dep1
        self._dep2 = dep2

    def initiate(self, some-string)
        # lots of mumto-jumbo here...

普遍的看法是,将工作移出__init__不是一种常见的做法,对于经验丰富的 Python 开发人员来说毫无意义。

4

5 回答 5

11

__init__如果您将初始化函数与经验丰富的开发人员分开编写,那么您肯定会将您的代码视为儿童游乐场。

如果您担心能够在不运行该__init__方法的情况下创建看起来像类实例的对象,请使用Mock

def test_some_method():
    mock_object = Mock(MyClass)
    MyClass.some_method(mock_object)
于 2012-09-20T13:21:50.367 回答
5

__init__实际上不能返回任何东西;如果您考虑如何使用它,这应该是显而易见的:

class Example(object):
    def __init__(self, x):
        self.x = x
        return ANYTHING

e = Example(1)   # how are you going to get ANYTHING back?

使用与initialize()分开的方法__init__似乎有点傻 - 重点是您的初始化程序应该在创建对象时自动运行,所以您的示例初始化应该看起来像

scobj = SomeClass(dep1, dep2, 'abcdef')
# scobj.initialize('abcdef')     # <= separate call not needed!

编辑:

如果您确实需要将代码放入 中__init__,我建议将其放入私有方法中并从 中调用它们__init__,如下所示:

class Example2(object):
    def __init__(self, a, b, c):
        self._init_connection(a)
        self._init_display(b)
        self.c = c

    def _init_connection(self, a):
        self.conn = make_connection(a)

    def _init_display(self, b):
        self.disp = make_display(b)

...这很好,因为

  • 所有初始化都在创建对象时完成(您不需要记住进行后续初始化调用)
  • 您不必保留一个标志来记住您的对象是否已初始化 - 如果它存在,它已被初始化。
于 2012-09-20T13:13:36.277 回答
2
class Klass:
    def __init__(self):
        initialize instance here, no return

__init__将在对象创建时自动运行,因此您无需检查它是否运行;如果你有一个实例,它就会运行!

关于从 init() 返回 self:我不会,因为它是就地操作。在这里阅读第一个答案,因为它很好地解释了这一点。

于 2012-09-20T13:05:17.843 回答
1

关于什么

class SomeClass(object):
    def __init__(self, dep1, dep2, st=None):
        self._dep1 = dep1
        self._dep2 = dep2
        if st is None:
            self._initialized = False
        else:
            self.initialize(st)

    def initialize(self, st):
        ...
        self._initialized = True
        ...

?

于 2012-09-20T13:24:02.820 回答
1

__init__()拆分成单独的方法并没有错。如果您可以以使这些方法适当命名和可重用的方式来做到这一点,那就更好了 - 例如:

class SomeClass(object):
    def __init__(self, dep1, dep2, some_string):
        self.set_dependencies(dep1, dep2, some_string)

    def set_dependencies(self, dep1, dep2, some_string):
        self._dep1 = dep1
        self._dep2 = dep2
        self._some_string = some_string

        for dep2element in self._dep2:
            dep2element.set_dep(dep1)
        self._dep1.set_some_string(some_string)

在这里,remove_dependencies()将来可以添加一个方法来将实例恢复到中性状态,然后再设置新的依赖项。该功能与初始化过程分离。

注意我已经从 中调用了它__init__(),即使我们不希望它在测试中运行。如果我们从 调用它,则__init__()使用此类的人必须set_dependencies()在实例化后调用——这对您的类的 API 来说是不必要的复杂化。

我们可以做的是将方法存根进行测试,例如:

class TestSomeClass(TestCase):
    def __init__(self):
        SomeClass._old_set_dependencies = SomeClass.set_dependencies
        SomeClass.set_dependencies = lambda *args: None

    ...

这是一种非常粗暴的方法,只是为了证明一个真正的观点 - 有一些很好的库可以更努力地做到这一点 - 请参阅这个讨论,但我也推荐Mockstar,它是Mock设计的扩展来简化语义。

于 2012-09-23T23:59:02.823 回答