[Python] Import vs execfile

enrico franchi enrico.franchi a gmail.com
Mer 14 Ago 2013 17:39:34 CEST


2013/8/14 Alessandro Dentella <sandro a e-den.it>:

> La mia impressione è che ci sia qualcosa nello scopo del chiamante che
> cambia il modo in cui viene interpretato il file ma non ero conscio di nulla
> di simile. Chiaramente c'è qualcosa che mi sfugge in quello che viene fatto
> prima dalla mia script, posso chiaramente cercare di ridurla all'osso per
> capire cosa influenza l'execfile, ma speravo di avere una linea di pensiero
> da seguire...

La mia impressione e' che non dovresti usare execfile e fine della fiera.

>> Beh, qualcosa di diverso succede quindi...

In particolare quello che succede e' che secondo documentazione
execfile non fa la cosiddetta "module administration". Non crea
nemmeno un nuovo modulo. Ovviamente quindi quando chiedi __file__ da
fuori da un modulo hai eccezione, quando lo chiedi da dentro un
modulo, hai quello del codice enclosing.

Quello che fa e' equivalente ad avere piazzato un exec che esegue la
stringa letta dal file. Che a naso non e' quello che vuoi.

Guarda quello che fanno quegli import:

$ cat foo.code
from os import listdir
$ python
Enthought Canopy Python 2.7.3 | 64-bit | (default, Mar 25 2013, 15:52:02)
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> listdir
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'listdir' is not defined
>>> execfile('foo.code')
>>> listdir
<built-in function listdir>


Ora osserva questo codice:

$ cat foo.code
pprint(globals())
pprint(locals())

print len(globals()), len(locals())

from os import listdir
print len(globals()), len(locals())

class A(object):
    pprint(globals())
    pprint(locals())

Nota che non sto chiamando il file .py (perche' non e' trattato come
un modulo, quindi chissene). Non solo... nota che sto usando una
funzione *non importata*. pprint.

Ora chiamo tutto dal repl.

$ python
Enthought Canopy Python 2.7.3 | 64-bit | (default, Mar 25 2013, 15:52:02)
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from pprint import pprint
>>> execfile("foo.code")
{'__builtins__': <module '__builtin__' (built-in)>,
 '__doc__': None,
 '__name__': '__main__',
 '__package__': None,
 'pprint': <function pprint at 0x100491aa0>}
{'__builtins__': <module '__builtin__' (built-in)>,
 '__doc__': None,
 '__name__': '__main__',
 '__package__': None,
 'pprint': <function pprint at 0x100491aa0>}
5 5
6 6
{'__builtins__': <module '__builtin__' (built-in)>,
 '__doc__': None,
 '__name__': '__main__',
 '__package__': None,
 'listdir': <built-in function listdir>,
 'pprint': <function pprint at 0x100491aa0>}
{'__module__': '__main__'}


Osserviamo un po' di cose. Ho importato pprint dal chiamante. Il
"chiamato" lo trova.
Ricorda: non e' parente di un modulo! E' parente di avere copia e
incollato il contenuto del file ed averlo eseguito brutalmente al
posto dell'execfile.

Osserva anche che i globals e i locals sono lo stesso oggetto. Di
fatto, execfile e' eseguito allo scope generale (e se guardi il
behavior di non passargli specifici globals e locals e' chiaro cosa
succede).

Poi import listdir e tutto bene. Quando chiedo globals e locals
*dentro* la classe, ovviamente, i locals sono cambiati. Perche' sono
quelli dentro la classe! Avessi dichiarato un attributo, ce lo avrei
trovato dentro.

>>> class A(object):
...   a = 1
...   print locals()
...
{'a': 1, '__module__': '__main__'}

Ora vediamo cosa succede chiamando tutto *dentro una funzione*!

>>> from pprint import pprint
>>> def f():
...   execfile('foo.code')
...
>>> f()
{'A': <class '__main__.A'>,
 '__builtins__': <module '__builtin__' (built-in)>,
 '__doc__': None,
 '__name__': '__main__',
 '__package__': None,
 'f': <function f at 0x10049c320>,
 'pprint': <function pprint at 0x100491aa0>}
{}
7 0
7 1
{'A': <class '__main__.A'>,
 '__builtins__': <module '__builtin__' (built-in)>,
 '__doc__': None,
 '__name__': '__main__',
 '__package__': None,
 'f': <function f at 0x10049c320>,
 'pprint': <function pprint at 0x100491aa0>}
{'__module__': '__main__'}


WOW! Supercool! Svelato l'ano^(-1).

All'inizio, uarda bene, i globals sono pieni, i locals sono vuoti!
Questo perche' siamo nel corpo di f, a tutti gli effetti!

Vedi anche che quando facciamo from os import listdir, quello che
accade e' che, siccome stiamo facendo un binding *nello scope di una
funzione*, il nome e' *locale* alla funzione. Stiamo importando roba
"come varaibili locali".

Ovviamente quando poi andiamo a definire pure una classe, i locali
*dentro la classe* sono quelli della classe, non i nomi dentro la
funzione. Da li prendi il name error.

Attenzione pero', fino ad ora ho gliassato su una leggera differenza,
che e' quella che ci da il comportamento sorprendente. execfile non e'
esattamente come "copia e incollare" il tutto.

Se lo fosse, non avremmo errore:
>>> def g():
...   from os import listdir
...   class A(object):
...     listdir
...
>>> g()

Quello che dice la documentazione, tuttavia, e'

"""
The default locals act as described for function locals() below:
modifications to the default locals dictionary should not be attempted
"""


Come vedi, execfile e' *estremamente* delicato. Dipende un sacco dal
contesto e tipicamente non ti da quello che vuoi e come lo vuoi.

Quindi, tornando alla versione breve:

<force persuade>
tu non vuoi usare execfile.
</force persuade>

-- 
.
..: -enrico-


Maggiori informazioni sulla lista Python