If you create new objects inside your __init__ it may be better to pass them as arguments and have a factory method instead. It separates business logic from technical details on how objects are created.
In this example __init__ accepts host and port to construct a database connection:
class Query:
def __init__(self, host, port):
self._connection = Connection(host, port)
The possible refactoring is:
class Query:
def __init__(self, connection):
self._connection = connection
@classmethod
def create(cls, host, port):
return cls(Connection(host, port))
This approach has at least these advantages:
• It makes dependency injection easy. You can do Query(FakeConnection()) in your tests.
• The class can have as many factory methods as needed; the connection may be constructed not only by host and port but also by cloning another connection, reading a config file or object, using the default, etc.
• Such factory methods can be turned into asynchronous functions; this is completely impossible for __init__.