2020-06-07

5: Resolve Python Cyclic Import Without Deforming the Structure

<The previous article in this series | The table of contents of this series | The next article in this series>

Cyclic dependency is quite natural and OK domain-model-wise. You should not be advised to deform the structure just because Python cannot handle it.

Topics


About: The Python programming language

The table of contents of this article


Starting Context


  • The reader has basic knowledge on Python.

Target Context


  • The reader will know how to resolve any cyclic import without deforming the code structure, in Python.

Main Body


1: Meeting a Cyclic Import


Hypothesizer 7
This code causes an error, "ImportError: cannot import name 'ConstructorAOriginal' from partially initialized module 'theBiasPlanet.coreUtilitiesTests.cyclicImportingTest1.ConstructorAOriginal' (most likely due to a circular import)", when is executed.

theBiasPlanet/coreUtilitiesTests/cyclicImportingTest1/ConstructorAOriginal.py

@Python Source Code
import sys
from theBiasPlanet.coreUtilitiesTests.cyclicImportingTest1.PowerCompanyAOriginal import PowerCompanyAOriginal

class ConstructorAOriginal:
	def __init__ (a_this: "ConstructorAOriginal") -> None:
		a_this.i_powerCompany: "PowerCompanyAOriginal"
	
	def initialize (a_this: "ConstructorAOriginal", a_powerCompany: "PowerCompanyAOriginal") -> None:
		a_this.i_powerCompany = a_powerCompany
	
	def selfMaintain (a_this: "ConstructorAOriginal") -> bool:
		a_this.i_powerCompany.supplyPower ()
		return True
	
	def constructBuilding (a_this: "ConstructorAOriginal") -> bool:
		sys.stdout.write ("### Performing the service of ConstructorAOriginal.\n")
		sys.stdout.flush ()
		return True

theBiasPlanet/coreUtilitiesTests/cyclicImportingTest1/PowerCompanyAOriginal.py

@Python Source Code
import sys
from theBiasPlanet.coreUtilitiesTests.cyclicImportingTest1.ConstructorAOriginal import ConstructorAOriginal

class PowerCompanyAOriginal:
	def __init__ (a_this: "PowerCompanyAOriginal") -> None:
		a_this.i_constructor: "ConstructorAOriginal"
	
	def initialize (a_this: "PowerCompanyAOriginal", a_constructor: "ConstructorAOriginal") -> None:
		a_this.i_constructor = a_constructor
	
	def selfMaintain (a_this: "PowerCompanyAOriginal") -> bool:
		a_this.i_constructor.constructBuilding ()
		return True
	
	def supplyPower (a_this: "PowerCompanyAOriginal") -> bool:
		sys.stdout.write ("### Performing the service of PowerCompanyAOriginal.\n")
		sys.stdout.flush ()
		return True

theBiasPlanet/coreUtilitiesTests/cyclicImportingTest1/Test1Test.py

@Python Source Code
from typing import List
from typing import Type
import sys
# the original test Start
from theBiasPlanet.coreUtilitiesTests.cyclicImportingTest1.ConstructorAOriginal import ConstructorAOriginal
from theBiasPlanet.coreUtilitiesTests.cyclicImportingTest1.PowerCompanyAOriginal import PowerCompanyAOriginal
# the original test End

class Test1Test:
	@staticmethod
	def main (a_arguments: List [str]) -> None:
		Test1Test.test ()
	
	@staticmethod
	def test () -> None:
		# the original test Start
		l_constructorAOriginal: "ConstructorAOriginal" = ConstructorAOriginal ()
		l_powerCompanyAOriginal: "PowerCompanyAOriginal" = PowerCompanyAOriginal ()
		l_constructorAOriginal.initialize (l_powerCompanyAOriginal)
		l_powerCompanyAOriginal.initialize (l_constructorAOriginal)
		
		l_constructorAOriginal.constructBuilding ()
		l_powerCompanyAOriginal.supplyPower ()
		# the original test End

if __name__ == "__main__":
	Test1Test.main (sys.argv)

. . . Well, I know what happened: 'Test1Test' began to import 'ConstructorAOriginal', which began to import 'PowerCompanyAOriginal', which pretended to import 'ConstructorAOriginal', but really did not, because that module was already being imported, and tried to set a variable 'ConstructorAOriginal' at the 'ConstructorAOriginal' class, but could not, because the class had not been defined yet, because only the 'import' line of 'ConstructorAOriginal' had been executed: a typical cyclic import.

But that knowledge does not make me particularly happy: is Python not just clumsy?: Java allows cyclic import, C++ allows cyclic include (although forward declarations are required), and C# allows cyclic usage; Python just does not. . . . I know that some people make an excuse that that is inevitable because Python is a dynamically typed programming language, but I just say "I have never asked it to be dynamically typed, and the excuse just proves that dynamically typed programming languages suck.".

I know that this is recommended.

theBiasPlanet/coreUtilitiesTests/cyclicImportingTest1/ConstructorANoFrom.py

@Python Source Code
import sys
import theBiasPlanet.coreUtilitiesTests.cyclicImportingTest1.PowerCompanyANoFrom

class ConstructorANoFrom:
	def __init__ (a_this: "ConstructorANoFrom") -> None:
		a_this.i_powerCompany: "theBiasPlanet.coreUtilitiesTests.cyclicImportingTest1.PowerCompanyANoFrom.PowerCompanyANoFrom"
	
	def initialize (a_this: "ConstructorANoFrom", a_powerCompany: "theBiasPlanet.coreUtilitiesTests.cyclicImportingTest1.PowerCompanyANoFrom.PowerCompanyANoFrom") -> None:
		a_this.i_powerCompany = a_powerCompany
	
	def selfMaintain (a_this: "ConstructorANoFrom") -> bool:
		a_this.i_powerCompany.supplyPower ()
		return True
	
	def constructBuilding (a_this: "ConstructorANoFrom") -> bool:
		sys.stdout.write ("### Performing the service of ConstructorANoFrom.\n")
		sys.stdout.flush ()
		return True

theBiasPlanet/coreUtilitiesTests/cyclicImportingTest1/PowerCompanyANoFrom.py

@Python Source Code
import sys
import theBiasPlanet.coreUtilitiesTests.cyclicImportingTest1.ConstructorANoFrom

class PowerCompanyANoFrom:
	def __init__ (a_this: "PowerCompanyANoFrom") -> None:
		a_this.i_constructor: "theBiasPlanet.coreUtilitiesTests.cyclicImportingTest1.ConstructorANoFrom.ConstructorANoFrom"
	
	def initialize (a_this: "PowerCompanyANoFrom", a_constructor: "theBiasPlanet.coreUtilitiesTests.cyclicImportingTest1.ConstructorANoFrom.ConstructorANoFrom") -> None:
		a_this.i_constructor = a_constructor
	
	def selfMaintain (a_this: "PowerCompanyANoFrom") -> bool:
		a_this.i_constructor.constructBuilding ()
		return True
	
	def supplyPower (a_this: "PowerCompanyANoFrom") -> bool:
		sys.stdout.write ("### Performing the service of PowerCompanyANoFrom.\n")
		sys.stdout.flush ()
		return True

theBiasPlanet/coreUtilitiesTests/cyclicImportingTest1/Test1Test.py

@Python Source Code
from typing import List
from typing import Type
import sys
# the no-from test Start
import theBiasPlanet.coreUtilitiesTests.cyclicImportingTest1.ConstructorANoFrom
import theBiasPlanet.coreUtilitiesTests.cyclicImportingTest1.PowerCompanyANoFrom
# the no-from test End

class Test1Test:
	@staticmethod
	def main (a_arguments: List [str]) -> None:
		Test1Test.test ()
	
	@staticmethod
	def test () -> None:
		# the no-from test Start
		l_constructorANoFrom: "theBiasPlanet.coreUtilitiesTests.cyclicImportingTest1.ConstructorANoFrom.ConstructorANoFrom" = theBiasPlanet.coreUtilitiesTests.cyclicImportingTest1.ConstructorANoFrom.ConstructorANoFrom ()
		l_powerCompanyANoFrom: "theBiasPlanet.coreUtilitiesTests.cyclicImportingTest1.PowerCompanyANoFrom.PowerCompanyANoFrom" = theBiasPlanet.coreUtilitiesTests.cyclicImportingTest1.PowerCompanyANoFrom.PowerCompanyANoFrom ()
		l_constructorANoFrom.initialize (l_powerCompanyANoFrom)
		l_powerCompanyANoFrom.initialize (l_constructorANoFrom)
		
		l_constructorANoFrom.constructBuilding ()
		l_powerCompanyANoFrom.supplyPower ()
		# the no-from test End

if __name__ == "__main__":
	Test1Test.main (sys.argv)

Certainly, that works, but why? . . . I mean, in the process of importing 'PowerCompanyANoFrom', that "theBiasPlanet.coreUtilitiesTests.cyclicImportingTest1.ConstructorANoFrom" in the '__init__' definition is not defined yet, which is not flagged why?

That is because pure-Python-wise, 1st, the datum type specification is just an annotation, which is just a ignorable comment and 2nd, any variable in any function definition is OK if only it is defined just before the function is really executed.

'static types checking'-wise (I use mypy), it is OK because mypy is not governed by the clumsy Python importing mechanism.

Well, is the problem solved? . . . Actually, I have a strong aversion to that approach.

Why? I find that foolish: do I have to always specify full names? . . . I try to name any class uniquely without the qualification of the module name so that the qualification should be not required in order to identify the class. For example, who names his or her own class 'str', declaring that that is OK because his or her module name distinguishes his or her class from the standard 'str'? . . . I agree that the namespacing mechanism is necessary in order to guarantee non-duplication of names and is also quite useful as a means to organize artifacts, but I do not agree that the mechanism is for enforcing all the artifacts to be always fully named in code.

A piece of Java code in which all the classes like 'java.util.ArrayList', 'java.util.LinkedHashMap', etc. always appear in the full names seems foolish to me; who writes code that way? Why is such a manner not foolish only in Python?

Someone may say that I can always shorten the qualification name using 'as', but that is not the issue: shortened or not, the qualification is being required, which is conceptually foolish. . . . Besides, I hate abbreviations in general. Why? Because having multiple names for a single object is a nuisance for me. In fact, if an abbreviation is good, why did I not make it the official name in the first place; if the abbreviation is not good, I do not want such a not-good name. . . . Practically speaking, doing abbreviations consistently is quite difficult: the same official name tends to be abbreviated differently in various places, messing the code. For example, how should 'theBiasPlanet.coreUtilitiesTests.cyclicImportingTest1.ConstructorAOriginal' be abbreviated? 'tccC'? However, 'theBiasPlanet.coreUtilitiesTests.cyclicImportingTest1.ConstructorASecondary' will cause a duplication . . .

A piece of Java code in which various abbreviated qualified names like 'ju.List', 'u.List', 'utl.List', etc. are scattered around is messy for me. Why is such code not messy only in Python?


2: A Solution in the Pure Python (Without Any Static Types Checking) World, Where I Do Not Dwell


Hypothesizer 7
In fact, if I did not use any static types checking, this would do.

theBiasPlanet/coreUtilitiesTests/cyclicImportingTest1/ConstructorAWithoutStaticTypesChecking.py

@ Source Code
import sys

class ConstructorAWithoutStaticTypesChecking:
	def __init__ (a_this):
		a_this.i_powerCompany = None
	
	def initialize (a_this, a_powerCompany):
		a_this.i_powerCompany = a_powerCompany
	
	def selfMaintain (a_this):
		a_this.i_powerCompany.supplyPower ()
		return True
	
	def constructBuilding (a_this):
		sys.stdout.write ("### Performing the service of ConstructorAWithoutStaticTypesChecking.\n")
		sys.stdout.flush ()
		return True

theBiasPlanet/coreUtilitiesTests/cyclicImportingTest1/PowerCompanyAWithoutStaticTypesChecking.py

@ Source Code
import sys

class PowerCompanyAWithoutStaticTypesChecking:
	def __init__ (a_this):
		a_this.i_constructor = None
	
	def initialize (a_this, a_constructor):
		a_this.i_constructor = a_constructor
	
	def selfMaintain (a_this):
		a_this.i_constructor.constructBuilding ()
		return True
	
	def supplyPower (a_this):
		sys.stdout.write ("### Performing the service of PowerCompanyAWithoutStaticTypesChecking.\n")
		sys.stdout.flush ()
		return True

theBiasPlanet/coreUtilitiesTests/cyclicImportingTest1/Test1Test.py

@ Source Code
from typing import List
from typing import Type
import sys
# the without static types checking test Start
from theBiasPlanet.coreUtilitiesTests.cyclicImportingTest1.ConstructorAWithoutStaticTypesChecking import ConstructorAWithoutStaticTypesChecking
from theBiasPlanet.coreUtilitiesTests.cyclicImportingTest1.PowerCompanyAWithoutStaticTypesChecking import PowerCompanyAWithoutStaticTypesChecking
# the without static types checking test End

class Test1Test:
	@staticmethod
	def main (a_arguments):
		Test1Test.test ()
	
	@staticmethod
	def test ():
		# the without static types checking test Start
		l_constructorAWithoutStaticTypesChecking = ConstructorAWithoutStaticTypesChecking ()
		l_powerCompanyAWithoutStaticTypesChecking = PowerCompanyAWithoutStaticTypesChecking ()
		l_constructorAWithoutStaticTypesChecking.initialize (l_powerCompanyAWithoutStaticTypesChecking)
		l_powerCompanyAWithoutStaticTypesChecking.initialize (l_constructorAWithoutStaticTypesChecking)
		
		l_constructorAWithoutStaticTypesChecking.constructBuilding ()
		l_powerCompanyAWithoutStaticTypesChecking.supplyPower ()

if __name__ == "__main__":
	Test1Test.main (sys.argv)

Pure-Python-wise, there is no necessity for 'ConstructorAWithoutStaticTypesChecking' to import 'PowerCompanyAWithoutStaticTypesChecking' or for 'PowerCompanyAWithoutStaticTypesChecking' to import 'ConstructorAWithoutStaticTypesChecking'.

However, I am not any dweller of the world.


3: Cyclic Dependency Is Quite Natural, Domain-Model-Wise, or So I Say for the Time Being


Hypothesizer 7
A prevalent judgment to someone who has asked advice on how to resolve a cyclic import is, "Your design must be bad!" . . .

Well, even if that judgment is not outright wrong, that is very misleading.

I declare that domain-model-wise, cyclic dependency is quite natural.

In the above example, the constructor takes service from the power company (Why not? The constructor (or almost any company) quite naturally uses some electricity, which is usually taken from a power company, which should not be the specific power company why?); the power company takes service from the constructor (Why not? The power company (or almost any industrial unit) has to quite naturally have some facilities, which are usually built by a constructor, which should not be the specific constructor why?).

Mutual aid is rather a standard, not a problem.

The example is about the relationship between classes (in fact, each class happens to be in its own module in the example), but the case is no different for relationships between modules: class or module is just a matter of granularity: while mutual aid between entities at the class level is a standard, why does mutual aid between entities at the module level suddenly become evil? Why should a collections-handling module and a streams-handling module not aid each other?

Note that when a module is not used by the other modules, it means that the module is useless for the other modules. So, being used is a good thing. . . . Imagine that you create a useful module using a useful module by someone; then, why should that someone not use your useful module in his or her module? Because cyclic dependency is evil?


4: Is Cyclic Dependency Really Bad, Code-Wise?


Hypothesizer 7
Then, is cyclic dependency bad, only code-wise?

In my opinion, faithfully realizing the domain-model structure in code is ideal, and if a programming language has problems with the structure, the programming language is a regret.

I have many times seen a claim that cyclic dependency means a tight coupling, but I do not understand why decent mutual aid is a tighter coupling than strong unilateral dependence. Besides, even if it is a tighter coupling, so what? If the domain model requires the mutual aid, why should I deform the relationship? . . . In fact, the most cyclic-dependency-proof structure is a huge single monolithic class (because there is no other class than the class, it has nothing to depend on), which is better than . . . what?

As a conclusion, I do not deem that cyclic dependency in code should be bad, but if the programming language cannot handle cyclic dependency, that is a pity, and I have no option, but to eliminate cyclic dependency.


5: About Advice Like "Combine Modules Together" and "Move Things Around Modules"


Hypothesizer 7
A prevalent piece of advice is, "Just combine the 2 (or more) modules" . . .

Really? Is that what you call a "good design"?. . . That would, certainly, suppress the immediate error, but would deform the code structure that has come from the domain model. . . . I do not agree to just jumble things up just because a regrettable programming language cannot handle cyclic import.

Besides, combining the modules tends to evoke another cyclic import. For example, 'ClassA' -> 'ClassB' -> 'ClassC' was fine when 'ClassA', 'ClassB', 'ClassC' were in 'moduleA', 'moduleB', and 'modueC', respectively, but is not fine now because 'ClassA' and 'ClassC' are in the combined 'mnoduleAAndC'. . . So, what now? Of course, I should combine also 'moduleB' and 'moduleAAndC'? . . . Well, the ultimate will be "Just jumble all the things together in a single module!". . . . Is that what you call a "good design"?

I frequently see also a piece of advice like "Just move things around modules." . . .

Also that would deform the code structure . . .

I do not detect much conscience in such advice.


6: Summing Up Why Cyclic Dependencies Appear or Not Appear and Choosing the Right Direction to Go


Hypothesizer 7
The worst code, in which all the things are jumbled up, cannot cause any cyclic dependency.

Making the code better, by dividing things according to the domain model, causes some cyclic dependencies to appear.

So, what will one do, if those cyclic dependencies are problems?

Some people makes the code worse (or even the worst) again, by jumbling some things up again and/or sending some things around, which is some prevalent advice recommend.

There is another option: to make the code better by sophisticating the domain model, and accordingly, the code structure.

Huh? Am I nuts?

I said that cyclic dependency in domain model was natural, but in fact, cyclic dependencies can be (at least usually) eliminated by a technique.

That will the right direction to go.

When somebody says "Cyclic dependencies appear because the designs are bad", if he or she means "The designs are not sophisticated enough to eliminate the cyclic dependencies", I am probably on the same page with him or her, but if his or her succeeding piece of advice is "Combine the modules" or "Move some things around the modules", I will have to conclude that he or she means another thing.


7: The Single Class Per Module Policy Is Supposed Hereafter, but . . .


Hypothesizer 7
I do not put multiple classes (except inner classes) into any single module, because the single class per module policy is the easiest for organizing things (at least for me) and accords with the ways of the other programming languages I use (especially, Java) and is also beneficial for avoiding cyclic imports: 'ClassA' -> 'ClassB' -> 'ClassC' becomes a cyclic import only because 'ClassA' and 'ClassC' are put into a single module.

So, hereafter, it is supposed that only one class (except inner classes) is put into any single module.

However, that does not mean that the technique introduced hereafter is only for that policy.

The technique hereafter is for resolving any cyclic dependency among classes, but with the cyclic dependencies among classes resolved, cyclic imports can be resolved by appropriately organizing those classes in modules.


8: Resolving a Cyclic Dependency by Using Abstraction and Injection


Hypothesizer 7
Usually, a cyclic dependency can be resolved by using abstraction.

Let me rethink of the above constructor-power-company example.

The concrete 'ConstructorAOriginal' and 'PowerCompanyAOriginal' certainly depends on each other, but when 'ConstructorAOriginal' takes service of 'PowerCompanyAOriginal', what 'PowerCompanyAOriginal' does for self-maintenance does not matter: it is just fine if only the power company supplies power well ("well" includes the charge rate, etc.). In fact, the power company does not have to be 'PowerCompanyAOriginal' if another company's service is better.

So, a strategy is to think of any power company in general, which is an abstraction. . . . How is that related with the cyclic dependency? The abstract power company just supplies power without any complication that a concrete power company has (especially, without any dependency on a constructor); so if 'ConstructorAOriginal' depends on only the abstract power company, the cyclic dependency will disappear.

Doing the likewise abstraction for constructors, the improved code is this, using '~Modified' instead of '~Original'.

theBiasPlanet/coreUtilitiesTests/cyclicImportingTest1/Constructor.py

@Python Source Code
from abc import ABCMeta
from abc import abstractmethod

class Constructor (metaclass=ABCMeta):
	@abstractmethod
	def constructBuilding (a_this: "Constructor") -> bool:
		return True

theBiasPlanet/coreUtilitiesTests/cyclicImportingTest1/PowerCompany.py

@Python Source Code
from abc import ABCMeta
from abc import abstractmethod

class PowerCompany (metaclass=ABCMeta):
	@abstractmethod
	def supplyPower (a_this: "PowerCompany") -> bool:
		return True

theBiasPlanet/coreUtilitiesTests/cyclicImportingTest1/ConstructorAModified.py

@Python Source Code
import sys
from theBiasPlanet.coreUtilitiesTests.cyclicImportingTest1.Constructor import Constructor
from theBiasPlanet.coreUtilitiesTests.cyclicImportingTest1.PowerCompany import PowerCompany

class ConstructorAModified (Constructor):
	def __init__ (a_this: "ConstructorAModified") -> None:
		a_this.i_powerCompany: "PowerCompany"
	
	def initialize (a_this: "ConstructorAModified", a_powerCompany: "PowerCompany") -> None:
		a_this.i_powerCompany = a_powerCompany
	
	def selfMaintain (a_this: "ConstructorAModified") -> bool:
		a_this.i_powerCompany.supplyPower ()
		return True
	
	def constructBuilding (a_this: "ConstructorAModified") -> bool:
		sys.stdout.write ("### Performing the service of ConstructorAModified.\n")
		sys.stdout.flush ()
		return True

theBiasPlanet/coreUtilitiesTests/cyclicImportingTest1/PowerCompanyAModified.py

@Python Source Code
import sys
from theBiasPlanet.coreUtilitiesTests.cyclicImportingTest1.Constructor import Constructor
from theBiasPlanet.coreUtilitiesTests.cyclicImportingTest1.PowerCompany import PowerCompany

class PowerCompanyAModified (PowerCompany):
	def __init__ (a_this: "PowerCompanyAModified") -> None:
		a_this.i_constructor: "Constructor"
	
	def initialize (a_this: "PowerCompanyAModified", a_constructor: "Constructor") -> None:
		a_this.i_constructor = a_constructor
	
	def selfMaintain (a_this: "PowerCompanyAModified") -> bool:
		a_this.i_constructor.constructBuilding ()
		return True
	
	def supplyPower (a_this: "PowerCompanyAModified") -> bool:
		sys.stdout.write ("### Performing the service of PowerCompanyAModified.\n")
		sys.stdout.flush ()
		return True

theBiasPlanet/coreUtilitiesTests/cyclicImportingTest1/Test1Test.py

@Python Source Code
from typing import List
from typing import Type
import sys
# the modified test Start
from theBiasPlanet.coreUtilitiesTests.cyclicImportingTest1.ConstructorAModified import ConstructorAModified
from theBiasPlanet.coreUtilitiesTests.cyclicImportingTest1.PowerCompanyAModified import PowerCompanyAModified
# the modified test End

class Test1Test:
	@staticmethod
	def main (a_arguments: List [str]) -> None:
		Test1Test.test ()
	
	@staticmethod
	def test () -> None:
		# The modified test Start
		l_constructorAModified: "ConstructorAModified" = ConstructorAModified ()
		l_powerCompanyAModified: "PowerCompanyAModified" = PowerCompanyAModified ()
		l_constructorAModified.initialize (l_powerCompanyAModified)
		l_powerCompanyAModified.initialize (l_constructorAModified)
		
		l_constructorAModified.constructBuilding ()
		l_powerCompanyAModified.supplyPower ()
		# The modified test End

if __name__ == "__main__":
	Test1Test.main (sys.argv)

'ConstructorAModified' is concerned with the abstract 'PowerCompany', not the concrete 'PowerCompanyAModified'; 'PowerCompanyAModified' is concerned with the abstract 'Constructor', not the concrete 'ConstructorAModified'. So, the cyclic dependency is gone!

That way, the domain model is not deformed at all, but is made more adaptable, allowing introducing other constructors and power companies in a consistent way.

Note that it is important that the concrete power company instance is injected into the 'ConstructorAModified' instance: if the 'ConstructorAModified' tried to instantiate the concrete power company, it would have to use the concrete class (using a factory would not solve the problem, because the factory would use the concrete class anyway, so 'ConstructorAModified' would use the concrete class even if indirectly). . . . Certainly, the 'ConstructorAModified' instance uses a 'PowerCompanyAModified' instance, but class-wise, 'ConstructorAModified' is not concerned with the concrete power company class at all.

In fact, there is another way of abstraction for the above example, like this.

theBiasPlanet/coreUtilitiesTests/cyclicImportingTest1/Company.py

@Python Source Code
from abc import ABCMeta
from abc import abstractmethod

class Company (metaclass=ABCMeta):
	@abstractmethod
	def performService (a_this: "Company") -> bool:
		return True

theBiasPlanet/coreUtilitiesTests/cyclicImportingTest1/ConstructorAModifiedAgain.py

@Python Source Code
import sys
from theBiasPlanet.coreUtilitiesTests.cyclicImportingTest1.Company import Company

class ConstructorAModifiedAgain (Company):
	def __init__ (a_this: "ConstructorAModifiedAgain") -> None:
		a_this.i_powerCompany: "Company"
	
	def initialize (a_this: "ConstructorAModifiedAgain", a_powerCompany: "Company") -> None:
		a_this.i_powerCompany = a_powerCompany
	
	def selfMaintain (a_this: "ConstructorAModifiedAgain") -> bool:
		a_this.i_powerCompany.performService ()
		return True
	
	def performService (a_this: "ConstructorAModifiedAgain") -> bool:
		sys.stdout.write ("### Performing the service of ConstructorAModifiedAgain.\n")
		sys.stdout.flush ()
		return True

theBiasPlanet/coreUtilitiesTests/cyclicImportingTest1/PowerCompanyAModifiedAgain.py

@Python Source Code
import sys
from theBiasPlanet.coreUtilitiesTests.cyclicImportingTest1.Company import Company

class PowerCompanyAModifiedAgain (Company):
	def __init__ (a_this: "PowerCompanyAModifiedAgain") -> None:
		a_this.i_constructor: "Company"
	
	def initialize (a_this: "PowerCompanyAModifiedAgain", a_constructor: "Company") -> None:
		a_this.i_constructor = a_constructor
	
	def selfMaintain (a_this: "PowerCompanyAModifiedAgain") -> bool:
		a_this.i_constructor.performService ()
		return True
	
	def performService (a_this: "PowerCompanyAModifiedAgain") -> bool:
		sys.stdout.write ("### Performing the service of PowerCompanyAModifiedAgain.\n")
		sys.stdout.flush ()
		return True

theBiasPlanet/coreUtilitiesTests/cyclicImportingTest1/Test1Test.py

@Python Source Code
from typing import List
from typing import Type
import sys
# the modified-again test Start
from theBiasPlanet.coreUtilitiesTests.cyclicImportingTest1.ConstructorAModifiedAgain import ConstructorAModifiedAgain
from theBiasPlanet.coreUtilitiesTests.cyclicImportingTest1.PowerCompanyAModifiedAgain import PowerCompanyAModifiedAgain
# the modified-again test End

class Test1Test:
	@staticmethod
	def main (a_arguments: List [str]) -> None:
		Test1Test.test ()
	
	@staticmethod
	def test () -> None:
		# The modified-again test Start
		l_constructorAModifiedAgain: "ConstructorAModifiedAgain" = ConstructorAModifiedAgain ()
		l_powerCompanyAModifiedAgain: "PowerCompanyAModifiedAgain" = PowerCompanyAModifiedAgain ()
		l_constructorAModifiedAgain.initialize (l_powerCompanyAModifiedAgain)
		l_powerCompanyAModifiedAgain.initialize (l_constructorAModifiedAgain)
		
		l_constructorAModifiedAgain.performService ()
		l_powerCompanyAModifiedAgain.performService ()
		# The modified-again test End

if __name__ == "__main__":
	Test1Test.main (sys.argv)

Both 'ConstructorAModifiedAgain' and 'PowerCompanyAModifiedAgain' are abstracted as just a company that provides a kind of service.

The latter way seems better than the former way in this case, but I have introduced the former way because the latter way is possible only because of a specific nature of this case.

I know that live-for-the-moment people will avert the right way because it would tend to force them to write longer code, but source code length is mostly an emotional matter: the major part of programming is thinking, not typing; so lessening the typing amount is not really so important. Writing well-structured code is more important in the long run, . . . but I know that most of people who deem "Duck Typing" neat will be just happy if only they can write shorter code.


9: The Conclusion and Beyond


Hypothesizer 7
Cyclic dependency is quite natural and no-evil, domain-model-wise.

But the cyclic dependencies in the domain model can be usually eliminated by making the domain model more adaptable, using abstraction.

That will be the right way to resolve the cyclic dependencies, not jumbling things up or sending things around.

The solution is not limited to Python, but applicable to any typical programming language, although I have discussed it in the context of Python, which is clumsy in handling cyclic dependencies.

I admit that Python has some merits, but also admit that it has some clumsiness (no-overload, having-to-qualify-every-instance-member-appearance-with-"self", and Global Interpreter Lock (which is rather critical than being just clumsy) immediately come to my mind). As I am no advertiser of Python, I will not rationalize such clumsiness, but will plainly speak about it, in future articles.


References


<The previous article in this series | The table of contents of this series | The next article in this series>