Hello!
Nothing could be more tasty to Python than chicken and eggs. So today our Python decided to resolve an old phylosophical question: what was the first, chicken or egg. In order to do it, it has decided to create two classes and then to perform some modelling to make an ultimate answer to this question.
Ok, let's start. Our Python is a grand-pa of all pythons, and it's serial number is 2.7. To begin with, it created 3 files:
# filename: models/egg.py
import models.chicken
class Egg(object):
def __init__(self, name):
super(Egg, self).__init__()
self.name = name
def wait(self):
return models.chicken.Chicken("Chicken from [{self.name}]".format(self=self))
# filename: models/chicken.py
import models.egg
class Chicken(object):
def __init__(self, name):
super(Chicken, self).__init__()
self.name = name
def create_egg(self):
return models.egg.Egg("Egg of [{self.name}]".format(self=self))
# filename: main.py
import models.chicken
if __name__ == '__main__':
# Decision maker!
c = models.chicken.Chicken("Ryaba")
print(c.create_egg().wait().create_egg().name)
Grand-pa Python is happy with the results:
$ python2.7 main.py
Egg of [Chicken from [Egg of [Ryaba]]]
For those of you who are sceptical about the circular dependencies in Python - yes, they are supported, but only to some extent. Generally, when the interpreter sees import models.egg
it checks if this module is imported and if it is, uses the address of this module from the cache. If it is not, it immediately creates a record in the cache and then starts doing actual import. That's why if you only have import <module_name>
statements in your code you are safe.
As soon as we decide to use from <module_name> import <object>
statement, our circular dependencies will not be able to be resolved. Let's try it!
# filename: models/egg.py
from models.chicken import Chicken
class Egg(object):
def __init__(self, name):
super(Egg, self).__init__()
self.name = name
def wait(self):
return Chicken("Chicken from [{self.name}]".format(self=self))
# filename: models/chicken.py
from models.egg import Egg
class Chicken(object):
def __init__(self, name):
super(Chicken, self).__init__()
self.name = name
def create_egg(self):
return Egg("Egg of [{self.name}]".format(self=self))
$ python2.7 main.py
Traceback (most recent call last):
File "main.py", line 1, in <module>
import models.chicken
File "/data/models/chicken.py", line 1, in <module>
from models.egg import Egg
File "/data/models/egg.py", line 1, in <module>
from models.chicken import Chicken
ImportError: cannot import name Chicken
$ python3.7 main.py
Traceback (most recent call last):
File "main.py", line 1, in <module>
import models.chicken
File "/data/models/chicken.py", line 1, in <module>
from models.egg import Egg
File "/data/models/egg.py", line 1, in <module>
from models.chicken import Chicken
ImportError: cannot import name 'Chicken' from 'models.chicken' (/data/models/chicken.py)
This is more or less predictable and it is a real chicken-egg problem. In order to import Chicken
class to the egg
module you need to parse chicken
module (so, just an address of a module is not enough), and in order to completely parse chicken
module, you need to complete parsing of an egg
module. So, no way.
OK. And now I need your full attention: we are going to replace import <package>.<module>
by from <package> import <module>
.
# filename: models/egg.py
from models import chicken
class Egg(object):
def __init__(self, name):
super(Egg, self).__init__()
self.name = name
def wait(self):
return chicken.Chicken("Chicken from [{self.name}]".format(self=self))
# filename: models/chicken.py
from models import egg
class Chicken(object):
def __init__(self, name):
super(Chicken, self).__init__()
self.name = name
def create_egg(self):
return egg.Egg("Egg of [{self.name}]".format(self=self))
And the result is a bit unexpectable:
$ python2.7 main.py
Traceback (most recent call last):
File "main.py", line 1, in <module>
import models.chicken
File "/data/models/chicken.py", line 1, in <module>
from models import egg
File "/data/models/egg.py", line 1, in <module>
from models import chicken
ImportError: cannot import name chicken
$ python3.7 main.py
Egg of [Chicken from [Egg of [Ryaba]]]
So, Python3 changed the way how packages are imported, generally they are appended to the sys.modules earlier than Python2. Good to know.
And the last surprise for today. Now we are using import <package>.<module> as <alias>
syntax.
# filename: models/egg.py
import models.chicken as chicken
class Egg(object):
def __init__(self, name):
super(Egg, self).__init__()
self.name = name
def wait(self):
return chicken.Chicken("Chicken from [{self.name}]".format(self=self))
# filename: models/chicken.py
import models.egg as egg
class Chicken(object):
def __init__(self, name):
super(Chicken, self).__init__()
self.name = name
def create_egg(self):
return egg.Egg("Egg of [{self.name}]".format(self=self))
And can you predict the output?
$ python2.7 main.py
Traceback (most recent call last):
File "main.py", line 1, in <module>
import models.chicken
File "/data/models/chicken.py", line 1, in <module>
import models.egg as egg
File "/data/models/egg.py", line 1, in <module>
import models.chicken as chicken
AttributeError: 'module' object has no attribute 'chicken'
$ python3.6 main.py
Traceback (most recent call last):
File "main.py", line 1, in <module>
import models.chicken
File "/data/models/chicken.py", line 1, in <module>
import models.egg as egg
File "/data/models/egg.py", line 1, in <module>
import models.chicken as chicken
AttributeError: module 'models' has no attribute 'chicken'
$ python3.7 main.py
Egg of [Chicken from [Egg of [Ryaba]]]
Python3.7 won :) Ok. What is really happened here is that there was a bug in the import A.B.C as D
syntax which was fixed in the version 3.7.
Thanks for reading!
Top comments (0)