-
-
Notifications
You must be signed in to change notification settings - Fork 30.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Modify IMPORT_FROM to fallback on sys.modules #61836
Comments
To help with circular imports, IMPORT_FROM should be modified to check sys.modules if a getattr() check fails. http://mail.python.org/pipermail/python-dev/2013-April/125123.html |
Like I mentioned on python-dev, it worries me a bit that this could be considered "unexpected", i.e. when there is a conflict between a legitimage attribute member of a module, and a submodule of the same name. Also, I wonder if this isn't a bigger change to the import mechanism, than simply: |
It won't conflict with attributes. Only if the attribute does not exist on the module already will it fall back to sys.modules. If the import finished then any attribute created from the import will already be there and thus not be an issue. But to make sure this isn't a problem we can make sure there is a test in this instance. |
While I'm happy that this is being ackowledged as a problem, I'm not sure changing the "import x from y" semantics is necessarily a good idea. I mean, it is obvious to me that it means "import x, then getattr(x, "y")". I presume that is the meaning most people associate with it. Certainly, "y" can be any old module attribute. To change it to actually fall back to a submodule, well. I suppose if you could explain it roughly like "y = getattr(x, 'y', x.y)" then it would be ok. But I can think of contrived examples where this could break things: #b.py
from . import a
from .util import util Because of the circular import order, b.util will not exist as an attribute on b when a does its import. So a.util will end up being util instead of util.util as one might expect. I'm basically saying that it is possible that the fallback to submodule will occur, where the successful getattr would occur later and return something different than the submodule. Possible. But perhaps very much an edge case :) |
By declaring what the semantics will be to make the case even possible we are not breaking anything but making something possible. IOW it isn't even an edge case to me since there is no working case to compare against. =) |
On Fri, Apr 5, 2013 at 9:07 AM, Kristján Valur Jónsson
Not quite. It will only do this if the '.b.util' module is *in In fact, this example is kind of useful for proving the change Consider that for any module x.y, x must be in sys.modules before x.y
The only cases in which the behavior changes from today are cases 2 So let's consider case 2, which would have to be written something like: #a.py
from .b import util
#b.py
from .util import util
#b/util.py
from .. import a
def util(): pass
#__main__.py
import b So, import b leads to starting the load of b.util, which leads to But, because of the circularity, this will *also* happen if you import So case 2 does not lead to a hard-to-debug ordering dependency, it The tl;dr version of the above is that you will only receive a module |
Actually, after a little reflection, I can see that there are more So, this note is just to satisfy myself that the change doesn't (No wonder Nick said nobody wanted to touch this... the analysis |
...and I thought of *one* more way to trigger the changed behavior, #b.py
from .b import util
import .a
util = util.util
#b/util.py
def util(): pass (with the other files the same as before). I'm including it only for completeness' sake, because my original It's also hard to argue that a case written this way isn't getting Final (hopefully) conclusion: this change replaces the FAQ of "Don't |
Wow, Good analysis Phillip. Now... should we consider the current behavious to be a bug? 2.7 sure could do with some fixing :) |
I don't care much one way or the other about it being considered a bug Either way, though, the language reference for "from import" should |
It is not a bug but a change in semantics to accommodate a use-case so this will only be going into Python 3.4. |
Thanks for working through that Phillip - falling back to sys.modules when the expected attribute was missing is actually something I suggested as a possibility years ago, and Guido's response at the time was "If it was that easy, someone would have done it already". Your analysis is one of the pieces that was missing, along with Brett's insight that the code that needs the fallback is the IMPORT_FROM bytecode rather than the import implementation. I'm going to close the original circular import bug as being superseded by this one. |
On Sun, Apr 14, 2013 at 3:51 AM, Nick Coghlan <report@bugs.python.org> wrote:
Unfortunately, I just noticed it's actually incorrect in a pretty That means there *is* an ordering dependency, and an ambiguity like |
I have uploaded a patch with failing tests that should work after this is all said and done. Philip, please make sure I covered your tests as expected (I tweaked one because it already was working the way I did it). This way we at least know what we are aiming for in terms of results. |
It looks like maybe basic2 should be importing basic, not basic2. It also looks as though like the rebinding test doesn't actually test |
Don't be distracted when trying to write tests is the lesson learned. Fixed basic and rebinding and just deleted indirect. |
One question. #module foo.B
A = __import__(".", fromlist=["A"]).A
#module foo.A
B = __import__(".", fromlist=["B"]).B Clearly the above won't work. Can we extend __import__ to allow a full path, including relative? The objection about which name to bind to is no longer valid, since the binding is explicit. #module foo.B
A = __import__(".A")
#module foo.A
B = __import__(".B") ? |
sorry, the last example really needs to be: to invoke the "return final module" behaviour. |
You use importlib.import_module to do programmatic imports (and FYI dots never go into the first argument of __import__). |
Interesting. Still, it seems odd that a whole "importlib" is requried ot resolve the relative import, and that it doesn"t work with __import__, given how "supposedly" the import statement is supposed to translate to a __import__ call internally. One would think that __import__ had access to the relative path machinery somehow. |
If you look at the importlib source code in 2.7 it's actually really small and simply translates the call into an __import__ call under the hood. So __import__ can completely handle relative imports, but you were not using the level argument to __import__ nor passing the necessary globals to make the relative name computation work (see the importlib source if you want to see how it works). |
With PEP-451 implemented, note that I have reopened bpo-992389 - the patch to set the parent module attribute at the same time as setting the sys.module attribute is actually pretty trivial for the PEP-451 loader case, and that now includes all the standard loaders. I also think that approach will have fewer weird edge cases than this version. |
Unfortunately, this is not quite true: the weird edge cases for that approach are simply *different*, and harder to diagnose. ;-) The approach here can only affect execution paths that would currently raise ImportError; that one can break execution paths that are *currently working*. As Brett said above, "By declaring what the semantics will be to make the case even possible we are not breaking anything but making something possible." That is not the case for the approach proposed in the other issue. |
This is Brett's tests patch updated against default branch. |
And this is a full patch. |
(full patch is actually the latest upload: circrelimports.patch. Sorry for the glitch) |
By the way, is there any documentation that would need to be updated for this? |
The language reference - at least the section on the import statement, and potentially the section on the overall operation of the import system. I don't *think* it would affect anywhere in the tutorial or the importlib docs, but they may be worth skimming to see if anything leaps out. |
By the way, my patch is currently using the package's __name__ attribute to build the fully-qualified name. Should it use the package's __package__ attribute instead? |
If I understand your question correctly, then yes, __package__ should be To write a test, run a submodule that needs the new fallback feature via |
Le 10/10/2014 00:11, Nick Coghlan a écrit :
I'm not sure it would. __package__ is not looked up on the module (since |
Actually, looking up __package__ would be wrong. Say I have: from pack.module import foo and "foo" doesn't exist in pack.module but exists in pack. |
New changeset fded07a2d616 by Antoine Pitrou in branch 'default': |
New changeset 3a35638bce66 by Ned Deily in branch 'default': |
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
The text was updated successfully, but these errors were encountered: