Reputation: 747
The key to this question is aiding unit-testing. If I have a busy __init__
(i.e. __init__
that does complex initialization), I cannot simply instantiate an object of a class, but I need to mock/stub out all methods invoked on dependencies within the __init__
.
To illustrate this problem, here is example:
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):
...
To test the fun*
functions, every test must perform the complex construction.
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')
...
I find this redundant and would like to know how this problems can be elegantly handled in python, if not by moving the work from the constructor.
SOLUTION:
As @ecatmur suggested, to test some specific function, this code should do the trick:
def test_some_method():
mobject = Mock(SomeClass)
SomeClass.fun1(mobject)
With this approach all the methods will be mocked out. If fun1
calls some other method you want executed (e.g. fun2
) you can do it like this:
def test_some_method():
mobject = Mock(SomeClass)
mobject.fun2 = SomeClass.fun2.__get__(mobject)
SomeClass.fun1(mobject)
SomeClass.fun2.__get__(mobject)
will produce instancemethod
which will provide the correct binding.
¡Viva el Python!
ORIGINAL QUESTION:
Original question was centered around moving the work done in __init__
to the separate init
method and different problems revolving that approach. My usual approach is to make this
class SomeClass(object):
def __init__(self, dep1, dep2, some_string)
self._dep1 = dep1
self._dep2 = dep2
# lots of mumbo-jumbo here...
become this
class SomeClass(object):
def __init__(self, dep1, dep2)
self._dep1 = dep1
self._dep2 = dep2
def initiate(self, some-string)
# lots of mumto-jumbo here...
General sentiment was that moving work from __init__
is not a common practice and would be meaningless to seasoned python developers.
Upvotes: 9
Views: 3868
Reputation: 4527
There's nothing wrong with splitting __init__()
out into separate methods. If you can do it in a way which makes those methods appropriately named and reusable, then all the better - e.g.:
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)
Here, a remove_dependencies()
method could be added in the future to revert the instance to a neutral state, before setting new dependencies. The functionality is decoupled from the initialisation process.
Note I've called this from __init__()
, even though we don't want this to run in testing. If we don't call this from __init__()
, someone using this class would have to call set_dependencies()
after instantiating - which is an unnecessary complication to your class's API.
What we can do is stub the method out for testing, e.g.:
class TestSomeClass(TestCase):
def __init__(self):
SomeClass._old_set_dependencies = SomeClass.set_dependencies
SomeClass.set_dependencies = lambda *args: None
...
This is a pretty brash way of stubbing out a method, just to prove a point really - there are some good libraries out there for doing it more diligently - see this discussion, but I'd also recommend Mockstar which is an extension to Mock designed to simplify the semantics.
Upvotes: 1
Reputation: 56684
__init__
can't actually return anything; if you think about how it is used, this should be obvious:
class Example(object):
def __init__(self, x):
self.x = x
return ANYTHING
e = Example(1) # how are you going to get ANYTHING back?
Using an initialize()
method, separate from __init__
, seems kind of silly - the whole point is that your initializer should automatically run when the object is created, so your example initialization should look like
scobj = SomeClass(dep1, dep2, 'abcdef')
# scobj.initialize('abcdef') # <= separate call not needed!
Edit:
If you really need to factor the code in __init__
, I suggest putting it in private methods and calling those from __init__
, like so:
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)
... this is good because
Upvotes: 5
Reputation: 91119
What about
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
...
?
Upvotes: 1
Reputation: 157464
If you write initialization functions separate from __init__
then experienced developers will certainly see your code as a kid's playground.
If you're concerned about being able to create objects that look like instances of your class without running the __init__
method, then use Mock:
def test_some_method():
mock_object = Mock(MyClass)
MyClass.some_method(mock_object)
Upvotes: 11
Reputation: 6004
class Klass:
def __init__(self):
initialize instance here, no return
__init__
will automatically run at object creation, so you don't need to check if it run; it was run if you have an instance!
About returning self from init(): I wouldn't, as it is an in-place operation. Read the first answer here, as it explains this pretty well.
Upvotes: 2