Created on 2004-07-16 15:09 by dcjim, last changed 2013-04-14 07:59 by ncoghlan. This issue is now closed.
|eek.zip||dcjim, 2004-07-16 15:09||zip of demonstration package|
|msg21663 - (view)||Author: Jim Fulton (dcjim)||Date: 2004-07-16 15:09|
This bug applied to 2.3 and 2.4. It probably applies to earlier versions, but who cares? :) Under some circumstances, code like: import eek.foo.baz y = eek.foo.baz.y fails with an attribute error for "foo" if foo is still being imported. I've attached a zip file of a demo package "eek" that demonstrates the problem. If you unzip the package and: import eek.foo you'll get the attribute error described above. I think the problem is that eek's foo attribute isn't set until the import of foo is finished. This is too late.
|msg21664 - (view)||Author: Jeremy Hylton (jhylton)||Date: 2004-11-07 15:30|
Logged In: YES user_id=31392 Are the semantics of import clear enough to confirm that this is a bug? Specifically, this seems to revolve around circular imports and code in __init__. I agree that this behavior is confusing, but I don't know if that has more to do with the nature of circular imports than any bug.
|msg21665 - (view)||Author: Nick Coghlan (ncoghlan) *||Date: 2004-11-15 11:08|
Logged In: YES user_id=1038590 If the semantics aren't clear, then isn't that a bug in itself? If the behaviour of Jim's code is officially undefined, then the docs need to say so. A simple rule would seem to be "a package should not try to import itself or any subpackages with an absolute import in its __init__.py file". Given the nature of __all__ and __path__ for packages, I can't see a way to safely set eek's foo attribute before foo's __init__.py has been processed.
|msg21666 - (view)||Author: Tim Peters (tim.peters) *||Date: 2004-11-16 02:03|
Logged In: YES user_id=31435 Nick, the semantics of circular imports aren't clear even if no packages are involved. Note that the Reference manual is silent about such cases. In effect, it's defined by the implementation, and some people think they know how that works -- although nobody has credibly claimed to fully understand the implementation consequences since Gordon McMillan vanished <0.5 wink>. While I expect this bug report to sit here for years (it's hard to imagine anyone caring enough to devote the time needed to untangle this subsystem), I'll note in passing that this case "works" if bar.py uses relative import instead; i.e., if it begins with import baz y = baz.y instead of with import eek.foo.baz y = eek.foo.baz.y Then it stops referencing attributes that don't exist before the whole chain of imports completes.
|msg21667 - (view)||Author: Martin v. Löwis (loewis) *||Date: 2006-07-03 12:38|
Logged In: YES user_id=21627 Lowering the priority, as this apparently is not a high-priority issue.
|msg65198 - (view)||Author: Nick Coghlan (ncoghlan) *||Date: 2008-04-08 17:09|
I think the lowered priority got lost somewhere along the line.
|msg65337 - (view)||Author: Torsten Bronger (bronger)||Date: 2008-04-11 06:32|
I have a very similar issue (maybe the same?) at the moment. Assume the follwing package structure: main.py package/ __init__.py [empty] moduleX.py moduleY.py main.py says: from package import moduleX moduleX.py says: from . import moduleY and moduleY.py says: from . import moduleX However, this doesn't work: bronger@wilson:~/temp/packages-test$ python main.py Traceback (most recent call last): File "main.py", line 1, in <module> from package import moduleX File "/home/bronger/temp/packages-test/package/moduleX.py", line 1, in <module> from . import moduleY File "/home/bronger/temp/packages-test/package/moduleY.py", line 1, in <module> from . import moduleX ImportError: cannot import name moduleX If I turn the relative imports to absolutes ones, it works. But I'd prefer the relative notation for intra-package imports. That's their purpose after all. If you split a large module into chunks, cyclic imports are hardly avoidable (and there's nothing bad about it; it worked fine before PEP 328). Note that "import absolute.path.to.module as short" doesn't work either. So currently, in presence of cyclic imports in a package, the only remedy is to use the full absolute paths everywhere in the source code, which is really awkward in my opinion.
|msg65436 - (view)||Author: Nick Coghlan (ncoghlan) *||Date: 2008-04-13 05:57|
This is actually a pretty tough problem - fixing it would involve some fairly subtle changes to the way imports from packages are handled. Given that I'm of the opinion that permitting circular imports in a code base is an extraordinarily bad coding practice (if I'm ever tempted to create a circular import, I consider it a sign that I need to separate out some of the common code into a utility module), I'm not personally going to be putting any effort into solving it (although I wouldn't necessarily oppose anyone else trying to fix it). However, I'll give a detailed description of the problem and a possible solution in case anyone else wants to tackle it (since I believe there's already a similar trick done for the sys.modules cache). At the moment, when resolving an import chain __import__ only sets the parent package's attribute for the submodule after the submodule import is complete. This is what Jim describes in his original post, and is the cause of the failure to resolve the name. Deferring the lookups solves the problem because it means the package attributes are checked only after the whole import chain is complete, instead of trying to get access to a half-executed module during the import itself. The most likely solution to the problem would be to change the attribute on the parent package to be handled in a fashion closer to the way the sys.modules cache is handled: set the attribute on the parent package *before* executing the module's code, and delete that attribute if a problem is encountered with the import. The consequence of this would be that circular imports would be permitted, although the module's imported in this fashion would be seen in a half constructed state. So instead of the import failing with an exception (which is what happens now), you would instead get a module which you can't actually use (since most its attributes won't actually be filled in yet, as the circular imports will normally occur near the top of the file). Attempts to use methods, attributes and functions from the module may or may not work depending on the order in which the original module does things. A clean failure indicating "You have a circular import, get rid of it" seems better to me than possible hard to diagnose bugs due to being able to retrieve things from a half-constructed module, but opinions obviously differ on that. However, I really don't see this changing without a PEP.
|msg65439 - (view)||Author: Torsten Bronger (bronger)||Date: 2008-04-13 08:15|
I dare to make a follow-up although I have no idea at all about the internal processes in the Python interpreter. But I've experimented with circular imports a lot recently. Just two points: First, I think that circular imports don't necessarily exhibit a sub-opimal programming style. I had a large parser module which I just wanted to split in order to get handy file sizes. However, the parser parses human documents, and layout element A defined in module A may contain element B from module B and vice versa. In a language with declarations, you just include a big header file but in Python, you end up with circular imports. Or, you must stay with large files. So, while I think that this clean error message Nick suggests is a good provisional solution, it should not make the impression that the circular import is a flaw by itself. And secondly, the problem with modules that are not yet populated with objects is how circular imports have worked in Python anyway. You can easily cope with it by not referencing the imported module's objects in the top-level code of the importing module (but only in functions and methods).
|msg84767 - (view)||Author: Nick Coghlan (ncoghlan) *||Date: 2009-03-31 12:25|
This came up on python-dev again recently: http://mail.python.org/pipermail/python-dev/2009-March/087955.html
|msg84799 - (view)||Author: Guido van Rossum (gvanrossum) *||Date: 2009-03-31 15:25|
Good sleuthing Nick! It's clearly the same bug that Fredrik found. I tried to test if using Brett' importlib has the same problem, but it can import neither p.a nor p.b, so that's not helpful as to sorting out the import semantics. I believe that at some point many of the details of importlib should be seen as the reference documentation for the darkest corners of import semantics. But it seems we aren't there yet.
|msg84803 - (view)||Author: Guido van Rossum (gvanrossum) *||Date: 2009-03-31 15:35|
Sorry, never mind about the importlib bug, that was my mistake. importlib actually behaves exactly the same way as the built-in import. I conclude that this is probably the best semantics of import that we can hope for in this corner case. I propose to close this as "works as intended" -- and perhaps document it somewhere.
|msg84844 - (view)||Author: Torsten Bronger (bronger)||Date: 2009-03-31 17:22|
Maybe it's better to leave it open, waiting for someone to pick it up, even if this is some time in the future? In my opinion, this is suprising behaviour without an actual rationale, and a current implementation feature. I'd be a pitty for me to see it becoming an official part of the language. What bothers me most is that from . import moduleX doesn't work but import package.moduleX does work. So the circular import itself works without problems, however, not with a handy identifier. This is would be an odd asymmetry, I think.
|msg84922 - (view)||Author: Nick Coghlan (ncoghlan) *||Date: 2009-03-31 21:42|
I just had a thought: we may be able to eliminate this behaviour without mucking about in the package globals. What if the import semantics were adjusted so that, as a last gasp effort before bailing out with an ImportError, the import process checked sys.modules again with the full module name? Not a fully fleshed out idea at this point (and possibly symptomatic of not being fully awake yet), but I'll bring it up in the current python-dev thread anyway.
|msg84966 - (view)||Author: Guido van Rossum (gvanrossum) *||Date: 2009-04-01 02:49|
I'm sorely tempted to apply the Van Lindberg clause to the last two responses by Torsten and Nick. If there was an easy solution it wouldn't have been open for five years. If you don't believe me, post a fix. I'll even accept a fix for the importlib package, which should lower the bar quite a bit compared to a fix for import.c.
|msg84993 - (view)||Author: Nick Coghlan (ncoghlan) *||Date: 2009-04-01 10:38|
No argument from me that my suggestion is a mere glimmering of an idea, rather than a fully worked out definitely viable solution. It was just an angle of attack I hadn't seen suggested before, so I figured it was worth mentioning - the fact that a module is allowed to exist in sys.modules while only half constructed is the reason "import a.b.c" can work while "from a.b import c" or an explicit relative import will fail - the first approach gets a hit in sys.modules and succeeds, while the latter two approaches fail because the a.b package doesn't have a 'c' attribute yet. Figuring out a way to set the attribute in the parent package and then roll it back later if the import fails is still likely to be the more robust approach.
|msg92114 - (view)||Author: Adam Olsen (Rhamphoryncus)||Date: 2009-08-31 21:44|
The key distinction between this and a "bad" circular import is that this is lazy. You may list the import at the top of your module, but you never touch it until after you've finished importing yourself (and they feel the same about you.) An ugly fix could be done today for module imports by creating a proxy that triggers the import upon the first attribute access. A more general solution could be done with a lazyimport statement, triggered when the target module finishes importing; only problem there is the confusing error messages and other oddities if you reassign that name.
|msg92115 - (view)||Author: Brett Cannon (brett.cannon) *||Date: 2009-08-31 21:51|
I have done a lazy importer like you describe, Adam, and it does help solve this issue. And it does have the problem of import errors being triggered rather late and in an odd spot.
|msg92116 - (view)||Author: Adam Olsen (Rhamphoryncus)||Date: 2009-08-31 21:57|
It'd probably be sufficient if we raised "NameError: lazy import 'foo' not yet complete". That should require a set of what names this module is lazy importing, which is checked in the failure paths of module attribute lookup and global/builtin lookup.
|msg145614 - (view)||Author: Nick Coghlan (ncoghlan) *||Date: 2011-10-16 06:48|
Changed the issue title to state clearly that the core issue is with circular imports that attempt to reference module contents at import time, regardless of the syntactic form used. All of the following module level code can fail due to this problem: from . import a from package import submodule from module import a import module module.a
|msg175592 - (view)||Author: Richard Oudkerk (sbt) *||Date: 2012-11-14 21:21|
In Torsten's example from . import moduleX can be replaced with moduleX = importlib.import_module('.moduleX', __package__) (*) or moduleX = importlib.import_module('package.moduleX') If that is not pretty enough then perhaps the new syntax import .moduleX could be introduced and made equivalent to (*).
|msg186889 - (view)||Author: Nick Coghlan (ncoghlan) *||Date: 2013-04-14 07:59|
The implementation of issue #17636 (making IMPORT_FROM fall back to sys.modules when appropriate) will make "import x.y" and "from x import y" equivalent for resolution purposes during import. That covers off the subset of circular references that we want to allow, so I'm closing this one in favour of the more precisely defined proposal.
|2013-04-14 07:59:21||ncoghlan||set||status: open -> closed|
superseder: Modify IMPORT_FROM to fallback on sys.modules
messages: + msg186889
messages: + msg175592
|2012-09-25 05:47:33||ncoghlan||link||issue16031 superseder|
|2011-10-19 17:44:27||flox||set||stage: needs patch|
versions: + Python 3.3, - Python 3.2
title: attribute error after non-from import -> attribute error due to circular import
|2011-10-16 06:44:55||ncoghlan||link||issue13187 superseder|
|2010-08-19 15:25:21||BreamoreBoy||set||versions: + Python 3.2, - Python 3.1, Python 2.7|
|2009-08-31 21:57:15||Rhamphoryncus||set||messages: + msg92116|
|2009-08-31 21:51:20||brett.cannon||set||messages: + msg92115|
messages: + msg92114
|2009-04-02 03:56:02||brett.cannon||link||issue966431 superseder|
|2009-04-01 18:41:02||brett.cannon||set||assignee: brett.cannon ->|
|2009-04-01 10:38:24||ncoghlan||set||messages: + msg84993|
|2009-04-01 02:49:57||gvanrossum||set||messages: + msg84966|
|2009-03-31 21:42:44||ncoghlan||set||messages: + msg84922|
|2009-03-31 17:22:33||bronger||set||messages: + msg84844|
|2009-03-31 15:35:26||gvanrossum||set||messages: + msg84803|
messages: + msg84799
|2009-03-31 12:25:11||ncoghlan||set||messages: + msg84767|
|2009-02-11 02:55:04||ajaksu2||set||assignee: brett.cannon|
versions: + Python 3.1, Python 2.7, - Python 2.3
nosy: + brett.cannon
|2008-04-13 08:15:36||bronger||set||messages: + msg65439|
|2008-04-13 05:57:41||ncoghlan||set||assignee: ncoghlan -> (no value)|
messages: + msg65436
|2008-04-12 21:55:50||georg.brandl||set||priority: low -> normal|
messages: + msg65337
|2008-04-08 17:09:24||ncoghlan||set||priority: normal -> low|
messages: + msg65198