10000 Global variables undefined in interactive use of embedded ipython shell · Issue #62 · ipython/ipython · GitHub
[go: up one dir, main page]

Skip to content

Global variables undefined in interactive use of embedded ipython shell #62

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

Open
ipython opened this issue May 10, 2010 · 50 comments
Open
Labels

Comments

@ipython
Copy link
Collaborator
ipython commented May 10, 2010

Original Launchpad bug 399627: https://bugs.launchpad.net/ipython/+bug/399627
Reported by: h-fangohr (Hans Fangohr).

The error can be reproduced as follows:

  1. Start Python, and start embedded ipython session. Following ipython's manual, we do
fangohr@eta:~$ python
Python 2.4.3 (#1, Jun  8 2009, 14:09:06) 
[GCC 4.1.2 20061115 (prerelease) (Debian 4.1.1-21)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from IPython.Shell import IPShellEmbed
>>> ipshell=IPShellEmbed()
>>> ipshell()

Within the just started ipython session, global variables are sometimes not visible. Two examples are:

Example 1:

In [1]: a=1

In [2]: def f(x):
   ...:     return a*x
   ...: 

In [3]: f(2)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)

/home/fangohr/<ipython console> 

/home/fangohr/<ipython console> in f(x)

NameError: global name 'a' is not defined

Example 2:

In [4]: b=1

In [5]: (lambda :b)()
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)

/home/fangohr/<ipython console> 

/home/fangohr/<ipython console> in <lambda>()

NameError: global name 'b' is not defined

There is no error if "b=1; (lambda :b)()" is put in a script, and this script is executed using ipython's "run" command.

There is no error if "b=1; (lambda :b)()" is executed interactively after having started ipython.

The only way the error seems comes up is if an embedded IPython shell is started from a Python prompt AND the commands are executed interactively at the prompt.

I find the same error when trying ipython-0.9.1 (with python2.4).

The bug was reported by Olivier Klein to the nmag team (http://nmag.soton.ac.uk).

@ipython
Copy link
Collaborator Author
ipython commented May 10, 2010

[ LP comment 1 by: Fernando Perez, on 2010-04-25 23:36:38.673176+00:00 ]

OK, I can confirm the problem (even on trunk), but it's hard. I am just for now making sure this bug is confirmed so we keep tracking it, but I'm not sure how to fix it.

The issue is that in embedded shells, we try to update the global namespace to use the surrounding scope (that's the point of an embedded shell, to be able to see what's around you). But this then causes python to fail to resolve the ipython interactive namespace when nested things (like local functions) are defined. See the mainloop() method of the embedded shell for details.

I need to think a lot more about how to fix this one, any ideas very much welcome.

@fperez
Copy link
Member
fperez commented Dec 4, 2010

Over at launchpad...

chairmanK wrote 20 hours ago:

Me Too. In addition to functions, this bug also appears in generator expressions.

@takluyver
Copy link
Member

The equivalent component in trunk seems to be IPython.frontend.terminal.InteractiveShellEmbed. But that's broken in other ways, and evidently hasn't been tested much. Does anyone know what its future is?

@takluyver
Copy link
Member

Could this be the same issue as #136?

@fperez
Copy link
Member
fperez commented Nov 28, 2011

This has been now fixed:

amirbar[ipython]> python
Python 2.7.2+ (default, Oct  4 2011, 20:06:09) 
[GCC 4.6.1] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from IPython import embed
>>> embed()
Python 2.7.2+ (default, Oct  4 2011, 20:06:09) 
Type "copyright", "credits" or "license" for more information.

IPython 0.12.dev -- An enhanced Interactive Python.
?         -> Introduction and overview of IPython's features.
%quickref -> Quick reference.
help      -> Python's own help system.
object?   -> Details about 'object', use 'object??' for extra details.

In [1]: a = 1

In [2]: def f(x):
   ...:     return a*x
   ...: 

In [3]: f(3)
Out[3]: 3

In [4]: b = 1

In [5]: (lambda : b)()
Out[5]: 1

In [6]: 

@fperez fperez closed this as completed Nov 28, 2011
@daf
Copy link
daf commented Feb 7, 2012

Can someone explain a little more what went into the fix of this? I still encounter the issue, but only when I'm one layer removed via a method call, and only if I run from a script, not interactively.

python 2.7.2 on OSX 10.6.8, ipython 0.11 and 0.12 both exhibit similar behavior (using 0.12 for this comment)

This is a problem with our project which features (prominently) an embedded IPython shell.

testembed.py

from IPython import embed

def hi():
    embed()

if __name__ == '__main__':
    #embed()
    hi()

Run this on the command line with python testembed.py and see this session:

Python 2.7.2 (default, Aug 29 2011, 12:33:18) 
Type "copyright", "credits" or "license" for more information.

IPython 0.12 -- An enhanced Interactive Python.
?         -> Introduction and overview of IPython's features.
%quickref -> Quick reference.
help      -> Python's own help system.
object?   -> Details about 'object', use 'object??' for extra details.

In [1]: import time

In [2]: def tim():
   ...:     print time.time()
   ...:     

In [3]: tim()
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
[snip] in <module>()
----> 1 tim()

[snip] in tim()
      1 def tim():
----> 2     print time.time()
      3 

NameError: global name 'time' is not defined

In [4]: 

However, comment out the call to hi() and replace it with the embed() call:

Python 2.7.2 (default, Aug 29 2011, 12:33:18) 
Type "copyright", "credits" or "license" for more information.

IPython 0.12 -- An enhanced Interactive Python.
?         -> Introduction and overview of IPython's features.
%quickref -> Quick reference.
help      -> Python's own help system.
object?   -> Details about 'object', use 'object??' for extra details.

In [1]: import time

In [2]: def tim():
    print time.time()
   ...:     

In [3]: tim()
1328639444.29

In [4]: 

After poking around, I think it has to do with the stack_depth parameter used here and this block: https://github.com/ipython/ipython/blob/master/IPython/frontend/terminal/embed.py#L185

Thoughts?

@takluyver
Copy link
Member

This is somewhat complicated, but I believe that's a restriction in Python itself.

In the failing case you show, time is not actually put into the global namespace: because you called embed inside the hi function, new variables you create interactively are local to that function. Ideally, tim() should work as a closure, closing over the reference to the time module. However, closures only appear to work when the containing function is compiled in one go. As far as I can tell, there is no way to define a closure dynamically. This simple example fails:

def outer():
    import time
    exec("def inner(): return time.time()")
    return inner

outer()()

This is probably because nested scopes were added to Python relatively late (they were a future import in 2.1, and always on in 2.2).

@daf
Copy link
daf commented Feb 7, 2012

Ok, I think I understand. Looks like we can't really do anything about this then, other than dumping what I would write interactively to a file and then reading that file back in. Probably too complex for what we want to be able to do interactively.

Thanks, btw.

@daf
Copy link
daf commented Feb 7, 2012

Sorry to keep harping on here, but it just makes interactive sessions feel very clunky:

Regular python:

>>> d={'one':1, 'two':2}
>>> getkeys=lambda: d.keys()
>>> getkeys()
['two', 'one']

Regular IPython:

In [1]: d={'one':1, 'two':2}

In [2]: getkeys=lambda: d.keys()

In [3]: getkeys()
Out[3]: ['two', 'one']

Embedded IPython:

>>> from IPython import embed
>>> embed()
Python 2.7.2 (default, Aug 29 2011, 12:33:18) 
Type "copyright", "credits" or "license" for more information.

IPython 0.12.dev -- An enhanced Interactive Python.
?         -> Introduction and overview of IPython's features.
%quickref -> Quick reference.
help      -> Python's own help system.
object?   -> Details about 'object', use 'object??' for extra details.

In [1]: d={'one':1, 'two':2}

In [2]: getkeys=lambda: d.keys()

In [3]: getkeys()
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
/Users/asadeveloper/Documents/Dev/code/pyon/bin/python in <module>()
----> 1 getkeys()

/Users/asadeveloper/Documents/Dev/code/pyon/bin/python in <lambda>()
----> 1 getkeys=lambda: d.keys()

NameError: global name 'd' is not defined

I guess there isn't much that can be done, but I don't get why we can create a dynamic closure in regular python/ipython but not the embedded version.

@takluyver
Copy link
Member

By the oddities of Python scoping, in standard Python/IPython, we're not actually creating a closure. d is a global variable, and the rules for accessing those work differently from closures. Each function keeps a reference to the global namespace where it was defined (getkeys.func_globals), so it can access any variables defined there.

In contrast, when you make a closure, Python attaches references to each variable over which it has closed, as determined by the compiler - but that only works when the inner and outer functions are compiled at the same time. It looks like this:

In [8]: def outer():
    a = 1
    def inner():
        return a
    return inner
   ...: 

In [9]: f = outer()

In [10]: f
Out[10]: <function __main__.inner>

In [11]: f.func_closure
Out[11]: (<cell at 0x9f5e344: int object at 0x9a830b0>,)

This is possibly done to save memory - if the closure carried a reference to the local scope where it was defined, none of the variables from that scope could be freed while the closure was live.

@liokm
Copy link
liokm commented Apr 24, 2012

I'm using Python 2.7.2 and IPython 0.12.1 on OSX 10.7.3 and having this issue still. When I run ./manage.py shell of Django which invokes IPython.embed(), the issue occurs. However, manually invoking the embed() in Python shell or from simple script file, there is no problem.

@takluyver
Copy link
Member

There's not a lot we can directly do about that, but we should probably encourage third parties away from embed for non-trivial uses.

@liokm
Copy link
liokm commented Apr 24, 2012

@takluyver Do you mean that better to use ipython directly in this case?

@takluyver
Copy link
Member

It's possible for Django to start IPython in a way that won't cause this problem, but that's not the way we currently make easy. The problem occurs when IPython starts with separate local and global namespaces. There's no reason that Django requires separate local and global namespaces, but that's what calling embed() inside a function implies.

For reference, here's the code in Django:
https://code.djangoproject.com/browser/django/trunk/django/core/management/commands/shell.py

@liokm
Copy link
liokm commented Apr 25, 2012

@takluyver That makes sense, thank! I'm opening a Django ticket for this.

@fperez
Copy link
Member
fperez commented Apr 25, 2012

@takluyver, now that we've merged embed_kernel so all the main pieces are in place, do you want to tackle a bit a cleanup of this to make slightly more fine-tuned uses easier?

@takluyver
Copy link
Member

I'll have a look at what interface might make most sense.

@dellis23
Copy link

I am still having trouble with this issue. I've tried narrowing down exactly what's causing it, and the best I can determine is that it only happens on Ubuntu 12.04. I've tried all of the latest release versions on other servers, and it works fine. But any time I define a function in ipython or use %cpaste to paste in a function from another file, the inside of that function has no access to the global scope. It makes it basically impossible to do anything useful in terms of writing functions on the fly.

@fletom
Copy link
fletom commented Sep 28, 2012

I am still having this problem with IPython 0.13 when it is called from other tools (e.g. Django's debugsqlshell command). It's very frustrating that something as basic as defining a function is totally broken.

@takluyver
Copy link
Member

I think embed() is the wrong interface for those to be using. embed() is
intended more for examining the state of a running program, so it uses
separate local and global namespaces. To get round this issue, ipython
needs to be started with a single interface. Sorry, I haven't had time to
work out what the best way to do it is.

@JoeZ99
Copy link
JoeZ99 commented Nov 20, 2012

Not only ubuntu. debian wheezy also shows that.

@reverie
Copy link
reverie commented Jan 25, 2013

FYI, the Django ticket @liokm created above is https://code.djangoproject.com/ticket/18204, which now points to https://code.djangoproject.com/ticket/17078, which looks to have been fixed in trunk. It should land in 1.5.

@bkvirendra
Copy link

I m having the same issue on Ubuntu with Ipython 0.13.2
screenshot from 2013-08-07 18 13 33

@minrk
Copy link
Member
minrk commented Aug 7, 2013

@bkvirendra that is fixed in django 1.6

@bkvirendra
Copy link

But 1.6 is not even stable yet!

@Carreau
Copy link
Member
Carreau commented Aug 7, 2013

But 1.6 is not even stable yet!

Software are not always stable, and between releases there might still be bugs. But there is nothing that should be fixed in IPython. Even if we do something here, the fix would not be in IPython stable before it is released either...

@anentropic
Copy link

will this ever get fixed?

@takluyver
Copy link
Member

AFAIK, my comments from a few years ago stand:

  1. I believe we are limited by the way Python itself works. Closures and dynamic evaluation don't play well together, and IPython can't fix it. People have found workarounds to move local variables to a global namespace, but those are hacks which can cause other problems, because they're modifying the global namespace. I don't plan on putting such workarounds into IPython; I'd rather leave an issue with something we're not doing than introduce one by trying to be too clever.

  2. Many places where people thought they wanted embed(), they should actually use start_ipython() instead and avoid this kind of issue.

@dellis23
Copy link

I don't understand, and it makes me angry.

@anentropic
Copy link
anentropic commented May 15, 2018

sorry, I was frustrated that things which would work if you typed them into an empty .py file didn't work here

but re-reading the history, it seems this is an issue with Django's manage.py shell specifically, 90% of the time I'm in ipython I'm doing that but it's easy to forget that's the case

embarrassingly I'm working a lot in obsolete version of Django (1.4)

according to earlier comment, newer versions of Django shell use ipython differently and may not have the problem? eg https://code.djangoproject.com/ticket/17078

apologies for the misunderstanding

@takluyver
Copy link
Member

Yup, I think it was fixed for Django 1.6. If you really can't upgrade, you might want to apply the fix manually. It looks like this was it: django/django@3570ff7

@liushapku
Copy link

I found a workaround today that is posted at #10695. A simpler treatment for the case where the IPython is not embedded inside a function is shown at this thread. I am not an expert so please help to check the validity.

@Carreau Carreau removed this from the 7.0 milestone Aug 28, 2018
kemitche added a commit to kemitche/baseplate.py that referenced this issue Oct 28, 2020
Embedding IPython results in issues where import statements and other
globals changes in the shell environment don't work as expected.[1]
As an example, the following commands result in a NameError:

In [1]: import time

In [2]: def foo():
   ...:     print(time.time())
   ...:

In [3]: foo()
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-3-c19b6d9633cf> in <module>
----> 1 foo()

<ipython-input-2-a038e7f91c93> in foo()
      1 def foo():
----> 2     print(time.time())
      3

NameError: name 'time' is not defined

By switching to use "start_ipython()" instead, we get an environment
more similar to what we get by running `ipython` directly.

[1] ipython/ipython#62
kemitche added a commit to kemitche/baseplate.py that referenced this issue Oct 28, 2020
Embedding IPython results in issues where import statements and other
globals changes in the shell environment don't work as expected.[1]
As an example, the following commands result in a NameError:

In [1]: import time

In [2]: def foo():
   ...:     print(time.time())
   ...:

In [3]: foo()
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-3-c19b6d9633cf> in <module>
----> 1 foo()

<ipython-input-2-a038e7f91c93> in foo()
      1 def foo():
----> 2     print(time.time())
      3

NameError: name 'time' is not defined

By switching to use "start_ipython()" instead, we get an environment
more similar to what we get by running `ipython` directly.

[1] ipython/ipython#62
kemitche added a commit to kemitche/baseplate.py that referenced this issue Oct 28, 2020
Embedding IPython results in issues where import statements and other
globals changes in the shell environment don't work as expected.[1]
As an example, the following commands result in a NameError:

In [1]: import time

In [2]: def foo():
   ...:     print(time.time())
   ...:

In [3]: foo()
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-3-c19b6d9633cf> in <module>
----> 1 foo()

<ipython-input-2-a038e7f91c93> in foo()
      1 def foo():
----> 2     print(time.time())
      3

NameError: name 'time' is not defined

By switching to use "start_ipython()" instead, we get an environment
more similar to what we get by running `ipython` directly.

[1] ipython/ipython#62
kemitche added a commit to reddit/baseplate.py that referenced this issue Oct 29, 2020
Embedding IPython results in issues where import statements and other
globals changes in the shell environment don't work as expected.[1]
As an example, the following commands result in a NameError:

In [1]: import time

In [2]: def foo():
   ...:     print(time.time())
   ...:

In [3]: foo()
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-3-c19b6d9633cf> in <module>
----> 1 foo()

<ipython-input-2-a038e7f91c93> in foo()
      1 def foo():
----> 2     print(time.time())
      3

NameError: name 'time' is not defined

By switching to use "start_ipython()" instead, we get an environment
more similar to what we get by running `ipython` directly.

[1] ipython/ipython#62
facebook-github-bot pushed a commit to facebook/sapling that referenced this issue Apr 7, 2023
Summary: This fixes issues like ipython/ipython#62.

Reviewed By: zzl0

Differential Revision: D44759311

fbshipit-source-id: 5ce94040cef09ac67962c147e86711971c545f91
@limwz01
Copy link
limwz01 commented Jun 14, 2024

I have a workaround posted at https://gist.github.com/limwz01/7e06cacf31d4ed83aa74134971030967 which is not as limited as the current approaches, but it has several caveats.

Allowing closures over local variables is done by using a transformer added
to shell.ast_transformers that creates an outer function scope wrapping the
cell code with parameters from local variables and which finally updates the
locals() and returns the last expression. For illustration, we have the
following generated when the code has statement0; statement1; last_expression and the local variables are x, y, z:

def _ipy_magic(_ipy_magic, x, y, z):
    statement0
    statement1
    return (last_expression, _ipy_magic.update(locals()))[0]
_ipy_magic(locals(), x, y, z)
  • exceptions raised in the shell further trigger a weird exception
    executing.executing.NotOneValueFound during traceback exception formatting
    and this disables pretty tracebacks
  • direct changes to local variables are only visible to code within the
    shell session
  • outer scope variables must already have had a closure referencing it to be
    visible (nonlocal does not work) and they become local variables (and
    hence above restriction applies)
  • local variables named _ipy_magic will be overwritten
  • return statements without an enclosing function does not give an error and
    results in unpredictable behaviour

What this means is that you can just paste in code as if you are really inside the function you had embedded in, and it should work most of the time. This is useful to test small snippets of code as it is or in the event a program throws an exception, check if a modified piece of code works without re-running the whole program.

Borrowing some of the above examples, these work:

In [1]: import time

In [2]: def tim():
   ...:     print(time.time())
   ...:

In [3]: tim()
1718358164.5522225

In [4]: a = 1

In [5]: (lambda: a)()
Out[5]: 1

@Erotemic
Copy link
Contributor

I'm wondering if there is any change in Python or IPython that could provide a first-class work around for this issue. It even confuses and annoys @3b1b: https://youtu.be/rbu7Zu5X1zI?si=3gcC1r2k3WrfZu-I&t=1734

@limwz01
Copy link
limwz01 commented Oct 13, 2024

My workaround at https://github.com/limwz01/ipython_utils is clunky but works.. and I've been using it for a long while now.

10000

@Carreau
Copy link
Member
Carreau commented Oct 14, 2024

Yeah, I'm in the process of watching @3b1b video, and I know this is an ipython limitation. I'd love a PR, I unfortunately dont have the time to dive into it.

My guess is that it's the same issue that affects ipdb where list comprehension don't work.

@limwz01, if you think your utils can be ported to be default in IPython, please send a PR.
Worse case we'll find some issues and that can be reverted, but at least we'll move forward on our comprehension of what it happening.

@limwz01
Copy link
limwz01 commented Oct 27, 2024

@Carreau I've packaged it as a separate pip installable package ipython_utils as I really need more people to test it in real world usage first. It does a lot of complicated stuff behind the scenes and right now it messes up the stack trace a little and I've had some minor problems with restoring the excepthook. You're welcome to try it out and give me some feedback

@limwz01
Copy link
limwz01 commented Nov 6, 2024

@gaogaotiantian recently pushed a similar solution to pdb in cpython itself: python/cpython#111094

His solution is a little too simplistic and did not handle all the edge cases, so it may not be suitable here. However, it shows that such a solution is actually acceptable.

@gaogaotiantian
Copy link

His solution is a little too simplistic and did not handle all the edge cases, so it may not be suitable here.

Feel free to submit an issue about the edge cases that pdb can't handle. We might not be able to fix all of those because we need to make the pdb code as maintainable as possible (so playing with ast might not be an option), which means we need to balance the code complexity and the capability, but it would be good for us to at least know what are the cases that we can't handle.

@limwz01
Copy link
limwz01 commented Nov 18, 2024

@gaogaotiantian I use the following to illustrate the problems:

#!/usr/bin/env python3.13
g0 = 0
def main():
    import pdb
    pdb.Pdb().set_trace()
if __name__ == "__main__":
    main()

The following are the problems:

  • not re-using the same Cells for each local (this is really the biggest issue)
(Pdb) t = 1
(Pdb) f = lambda: t
(Pdb) t = 2
(Pdb) f()
1
  • not obtaining the last expression (a pdb test suggests otherwise, but it is actually wrong)
(Pdb) t=10; (lambda: t)()
<no output>

(it does return a value for global g; g=1; g. but I'm at a loss as to where that value comes from)

  • giving a weird result with shadowed variables (actually because it bails out):
(Pdb) g0 = 1
(Pdb) global g0; t=g0; t=(lambda: t)()
*** NameError: name 't' is not defined
  • not removing annotations
(Pdb) t:int = 1; (lambda: t)()
*** NameError: name 't' is not defined
* using indentation to set up block scope

(Pdb) print((lambda:len("""
... """))())
7

* not hoisting globals (this is arguable):

(Pdb) global g; g=1
(Pdb) t=2
(Pdb) g=(lambda: t)()
(Pdb) global g; g
1

@limwz01
Copy link
limwz01 commented Nov 19, 2024

I can't seem to edit my post to correct some typos and cross-reference the issue, but anyway the issue has been submitted here: python/cpython#126958

@ur001
Copy link
ur001 commented Dec 20, 2024

Use user_ns. It may be some dict, or locals() | globals(), for example.

from IPython import start_ipython
start_ipython(user_ns={})

or

from IPython.terminal.embed import InteractiveShellEmbed
InteractiveShellEmbed(user_ns={})()

or

import IPython
IPython.embed(user_ns={})

Result:

In [1]: a = 1

In [2]: def f(x):
   ...:     return a * x
   ...:

In [3]: f(2)
Out[3]: 2

@limwz01
Copy link
limwz01 commented Dec 21, 2024 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

0