Ipython: Variáveis ​​globais indefinidas no uso interativo de shell ipython incorporado

Criado em 10 mai. 2010  ·  39Comentários  ·  Fonte: ipython/ipython

Bug original do Launchpad 399627: https://bugs.launchpad.net/ipython/+bug/399627
Relatado por: h-fangohr (Hans Fangohr).

O erro pode ser reproduzido da seguinte forma:

  1. Inicie o Python e inicie a sessão de ipython incorporada. Seguindo o manual do ipython, nós fazemos
fangohr<strong i="11">@eta</strong>:~$ 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()

Na sessão ipython recém-iniciada, as variáveis ​​globais às vezes não são visíveis. Dois exemplos são:

Exemplo 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

Exemplo 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

Não haverá erro se "b = 1; (lambda: b) ()" for colocado em um script e este script for executado usando o comando "run" do ipython.

Não há erro se "b = 1; (lambda: b) ()" for executado interativamente após iniciar o ipython.

A única maneira pela qual o erro parece ocorrer é se um shell IPython incorporado for iniciado em um prompt do Python E os comandos forem executados interativamente no prompt.

Eu encontro o mesmo erro ao tentar ipython-0.9.1 (com python2.4).

O bug foi relatado por Olivier Klein para a equipe da nmag (http://nmag.soton.ac.uk).

bug

Comentários muito úteis

Conforme observado em https://github.com/inducer/pudb/issues/103 , uma solução temporária ao usar um shell incorporado é: globals().update(locals()) (após definir as variáveis ​​globais localmente, apenas).

Todos 39 comentários

[LP comentário 1 por: Fernando Perez, em 2010-04-25 23: 36: 38.673176 + 00: 00]

OK, posso confirmar o problema (até no porta-malas), mas é difícil. Por enquanto, estou garantindo que esse bug seja confirmado, então continuamos rastreando, mas não tenho certeza de como corrigi-lo.

O problema é que em shells embutidos, tentamos atualizar o namespace global para usar o escopo circundante (esse é o ponto de um shell embutido, para poder ver o que está ao seu redor). Mas isso faz com que o python falhe em resolver o namespace interativo ipython quando coisas aninhadas (como funções locais) são definidas. Veja o método mainloop () do shell embutido para detalhes.

Preciso pensar muito mais sobre como consertar isso, qualquer ideia é muito bem-vinda.

Na barra de lançamento ...

o presidente K escreveu há 20 horas:

Eu também. Além das funções, esse bug também aparece nas expressões do gerador.

O componente equivalente no tronco parece ser IPython.frontend.terminal.InteractiveShellEmbed. Mas isso está quebrado de outras maneiras e, evidentemente, não foi muito testado. Alguém sabe qual é o seu futuro?

Isso poderia ser o mesmo que # 136?

Isso agora foi corrigido:

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]: 

Alguém pode explicar um pouco mais o que aconteceu para resolver isso? Ainda encontro o problema, mas apenas quando uma camada é removida por meio de uma chamada de método e apenas se eu executar a partir de um script, não interativamente.

python 2.7.2 no OSX 10.6.8, ipython 0.11 e 0.12 exibem comportamento semelhante (usando 0.12 para este comentário)

Este é um problema com nosso projeto, que apresenta (principalmente) um shell IPython embutido.

testembed.py

from IPython import embed

def hi():
    embed()

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

Execute isso na linha de comando com python testembed.py e veja esta sessão:

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]: 

No entanto, comente a chamada para hi() e substitua-a pela chamada embed() :

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]: 

Depois de dar uma olhada, acho que tem a ver com o parâmetro stack_depth usado aqui e este bloco: https://github.com/ipython/ipython/blob/master/IPython/frontend/terminal/embed.py # L185

Pensamentos?

Isso é um pouco complicado, mas acredito que seja uma restrição no próprio Python.

No caso de falha que você mostra, time não é realmente colocado no namespace global: porque você chamou embed dentro da função hi , as novas variáveis ​​que você cria interativamente são locais para aquele função. Idealmente, tim() deve funcionar como um fechamento, fechando sobre a referência ao módulo de tempo. No entanto, os fechamentos parecem funcionar apenas quando a função que o contém é compilada de uma vez. Pelo que eu posso dizer, não há como definir um encerramento dinamicamente. Este exemplo simples falha:

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

outer()()

Isso provavelmente ocorre porque os escopos aninhados foram adicionados ao Python relativamente tarde (eles eram uma importação futura no 2.1 e sempre ativos no 2.2).

Ok, acho que entendi. Parece que não podemos fazer nada sobre isso então, a não ser despejar o que eu escreveria interativamente em um arquivo e depois ler esse arquivo de volta. Provavelmente complexo demais para o que queremos fazer interativamente.

Obrigado, btw.

Desculpe continuar insistindo aqui, mas isso só faz com que as sessões interativas pareçam muito desajeitadas:

Python regular:

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

IPython regular:

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

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

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

IPython incorporado:

>>> 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

Acho que não há muito que possa ser feito, mas não entendo por que podemos criar um encerramento dinâmico em python / ipython regular, mas não na versão embarcada.

Pelas esquisitices do escopo Python, no Python / IPython padrão, não estamos realmente criando um encerramento. d é uma variável global e as regras para acessá-las funcionam de maneira diferente dos encerramentos. Cada função mantém uma referência ao namespace global onde foi definida ( getkeys.func_globals ), para que possa acessar quaisquer variáveis ​​ali definidas.

Em contraste, quando você fecha, o Python anexa referências a cada variável sobre a qual foi fechado, conforme determinado pelo compilador - mas isso só funciona quando as funções internas e externas são compiladas ao mesmo tempo. Se parece com isso:

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>,)

Isso possivelmente é feito para economizar memória - se o fechamento carregasse uma referência ao escopo local onde foi definido, nenhuma das variáveis ​​desse escopo poderia ser liberada enquanto o fechamento estava ativo.

Estou usando Python 2.7.2 e IPython 0.12.1 no OSX 10.7.3 e ainda tenho esse problema. Quando executo ./manage.py shell do Django que invoca IPython.embed() , o problema ocorre. No entanto, invocando manualmente o embed() no shell Python ou a partir de um arquivo de script simples, não há problema.

Não há muito que possamos fazer diretamente sobre isso, mas provavelmente devemos encorajar terceiros a deixar de embed para usos não triviais.

@takluyver Você quer dizer que é melhor usar ipython diretamente neste caso?

É possível para o Django iniciar o IPython de uma maneira que não cause esse problema, mas não é a maneira que facilitamos atualmente. O problema ocorre quando o IPython começa com namespaces locais e globais separados. Não há razão para que o Django exija namespaces locais e globais separados, mas é isso que implica chamar embed() dentro de uma função.

Para referência, aqui está o código no Django:
https://code.djangoproject.com/browser/django/trunk/django/core/management/commands/shell.py

@takluyver Isso faz sentido, obrigado! Estou abrindo um ingresso do Django para isso.

@takluyver , agora que mesclamos embed_kernel para que todas as peças principais estejam no lugar, deseja limpar um pouco isso para facilitar um uso mais ajustado?

Vou dar uma olhada em qual interface pode fazer mais sentido.

Ainda estou tendo problemas com esse problema. Tentei limitar exatamente o que está causando isso, e o melhor que posso determinar é que isso só acontece no Ubuntu 12.04. Tentei todas as versões de lançamento mais recentes em outros servidores e funciona bem. Mas sempre que defino uma função em ipython ou uso% cpaste para colar uma função de outro arquivo, o interior dessa função não tem acesso ao escopo global. Isso torna basicamente impossível fazer qualquer coisa útil em termos de funções de escrita em tempo real.

Ainda estou tendo esse problema com o IPython 0.13 quando ele é chamado de outras ferramentas (por exemplo, o comando debugsqlshell do Django). É muito frustrante que algo tão básico como definir uma função esteja totalmente quebrado.

Eu acho que embed () é a interface errada para aqueles que estão usando. embed () é
destina-se mais a examinar o estado de um programa em execução, por isso usa
separar namespaces locais e globais. Para contornar esse problema, ipython
precisa ser iniciado com uma única interface. Desculpe, eu não tive tempo para
descobrir qual é a melhor maneira de fazer isso.

Não apenas ubuntu. debian wheezy também mostra isso.

Para sua informação, o tíquete do Django @liokm criado acima é https://code.djangoproject.com/ticket/18204 , que agora aponta para https://code.djangoproject.com/ticket/17078 , que parece ter sido corrigido no tronco . Deve pousar em 1,5.

Estou tendo o mesmo problema no Ubuntu com Ipython 0.13.2
screenshot from 2013-08-07 18 13 33

@bkvirendra que foi corrigido no django 1.6

Mas 1.6 ainda não está estável!

Mas 1.6 ainda não está estável!

Os softwares nem sempre são estáveis ​​e entre os lançamentos ainda pode haver bugs. Mas não há nada que deva ser corrigido no IPython. Mesmo se fizermos algo aqui, a correção não estará no IPython estável antes de ser lançada ...

O problema ainda persiste no ipython == 4.2.0. Curiosamente no Windows não há problema, mas junto com as variáveis ​​globais do Ubuntu não são reconhecidas.

Caso de uso:

from ipywidgets import interact, FloatSlider, IntSlider,RadioButtons, Dropdown

@interact(sv1 = radio_1, Scenario = drop_1, sv2 = slider_2)
def update_map(sv1,sv2, Scenario):

O problema permanece. IPython 3.1.0, Debian Whezzy.

Se bem me lembro, isso foi corrigido por um tempo, e parece ter sido reintroduzido (reproduzido usando IPython 5.1.0 no macOS aqui: https://git.io/vPDrJ).

(editar: posso não estar me lembrando da correção. É possível que eu apenas joguei tudo o que precisava em um arquivo de inicialização em vez de usar incorporação)

Caso isso ajude alguém, estou usando este trecho de código como uma solução alternativa

    def fix_embed_globals(N=0):
        # Get the parent frame
        import inspect
        frame_level0 = inspect.currentframe()
        frame_cur = frame_level0
        N = 2  # I may have this value off by one. I rewrote this function to use only standard calls
        strict = False
        for _ix in range(N + 1):
            frame_next = frame_cur.f_back
            if frame_next is None:
                if strict:
                    raise AssertionError('Frame level %r is root' % _ix)
                else:
                    break
            frame_cur = frame_next
        parent_frame = frame_cur
        # Add locals to parent globals
        parent_frame.f_locals.update(parent_frame.f_globals)
        parent_frame.f_globals['_didfixipyglobals'] = True

Eu apenas chamo essa função sempre que recebo o erro de nome. Ele coloca todos os locais em seu quadro atual no dicionário global. É hacky, mas funciona na maioria das circunstâncias.

Conforme observado em https://github.com/inducer/pudb/issues/103 , uma solução temporária ao usar um shell incorporado é: globals().update(locals()) (após definir as variáveis ​​globais localmente, apenas).

Hmm, o marco desse bug pode ser alterado para uma das próximas versões?

Feito.

isso será consertado?

AFAIK, meus comentários de alguns anos atrás são:

  1. Acredito que somos limitados pela maneira como o próprio Python funciona. Fechamentos e avaliação dinâmica não funcionam bem juntos, e o IPython não pode consertar isso. As pessoas encontraram soluções alternativas para mover variáveis ​​locais para um namespace global, mas esses são hacks que podem causar outros problemas, porque estão modificando o namespace global. Não pretendo colocar essas soluções alternativas no IPython; Prefiro deixar um problema com algo que não estamos fazendo do que introduzir um tentando ser muito inteligente.

  2. Muitos lugares onde as pessoas pensaram que queriam embed() , elas deveriam usar start_ipython() vez disso e evitar esse tipo de problema.

Eu não entendo e isso me deixa com raiva.

desculpe, fiquei frustrado porque coisas que funcionariam se você digitasse em um arquivo .py vazio não funcionassem aqui

mas relendo a história, parece que este é um problema com manage.py shell do Django especificamente, 90% do tempo que estou no ipython estou fazendo isso, mas é fácil esquecer que é o caso

constrangedoramente, estou trabalhando muito em uma versão obsoleta do Django (1.4)

de acordo com o comentário anterior, versões mais recentes do shell do Django usam ipython de maneira diferente e podem não ter o problema? por exemplo, https://code.djangoproject.com/ticket/17078

desculpas pelo mal-entendido

Sim, acho que foi corrigido para Django 1.6. Se você realmente não pode atualizar, você pode querer aplicar a correção manualmente. Parece que era este: https://github.com/django/django/commit/3570ff734e93f493e023b912c9a97101f605f7f5

Encontrei uma solução alternativa hoje postada em # 10695 . Um tratamento mais simples para o caso em que o IPython não está embutido em uma função é mostrado neste tópico . Não sou um especialista, portanto, ajude a verificar a validade.

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

okomarov picture okomarov  ·  3Comentários

lewisacidic picture lewisacidic  ·  3Comentários

hexhexd picture hexhexd  ·  4Comentários

zhao-ji picture zhao-ji  ·  4Comentários

sataliulan picture sataliulan  ·  4Comentários