Zanurkuj w Pythonie/locals i globals
locals
i globals
edytuj
Odejdźmy teraz na minutkę od przetwarzania HTML-a. Porozmawiajmy o tym, jak Python obchodzi się ze zmiennymi. Python posiada dwie wbudowane funkcje, locals
i globals
, które pozwalają nam uzyskać w słownikowy sposób dostęp do zmiennych lokalnych i globalnych.
Pamiętasz locals
? Pierwszy raz mogliśmy ją zobaczyć tutaj:
def unknown_starttag(self, tag, attrs):
strattrs = "".join([' %s="%s"' % (key, value) for key, value in attrs])
self.pieces.append("<%(tag)s%(strattrs)s>" % locals())
Nie, czekaj, nie możesz jeszcze się uczyć o locals
. Najpierw, musisz nauczyć się, czym są przestrzenie nazw. Przedstawimy teraz trochę suchego materiału, lecz ważnego, dlatego też zachowaj uwagę.
Python korzysta z czegoś, co się nazywa przestrzenią nazw (ang. namespace), aby śledzić zmienne. Przestrzeń nazw jest właściwie słownikiem, gdzie kluczami są nazwy zmiennych, a wartościami słownika są wartości tych zmiennych. Możemy dostać się do przestrzeni nazw, jak do Pythonowego słownika, co zresztą zobaczymy za chwilkę.
Z dowolnego miejsca Pythonowego programu mamy dostęp do kilku przestrzeni nazw. Każda funkcja posiada własną przestrzeń nazw, nazywaną lokalną przestrzenią nazw, a która śledzi zmienne funkcji, włączając w to jej argumenty i lokalnie zdefiniowane zmienne. Każdy moduł posiada własną przestrzeń nazw, nazwaną globalną przestrzenią nazw, a która śledzi zmienne modułu, włączając w to funkcje, klasy i inne zaimportowane moduły, a także zmienne zdefiniowane w tym module i stałe. Jest także wbudowana przestrzeń nazw, dostępna z każdego modułu, a która przechowuje funkcje wbudowane i wyjątki.
Kiedy pewna linia kodu pyta się o wartość zmiennej x
, Python przeszuka wszystkie przestrzenie nazw, aby ją znaleźć, w poniższym porządku:
- lokalna przestrzeń nazw -- określona dla bieżącej funkcji lub metody pewnej klasy. Jeśli funkcja definiuje jakąś lokalną zmienną
x
, Python wykorzysta ją i zakończy szukanie. - przestrzeni nazw, w której dana funkcja została zagnieżdżona i przestrzeniach nazw, które znajdują się wyżej w "zagnieżdżonej" hierarchii.
- globalna przestrzeń nazw -- określona dla bieżącego modułu. Jeśli moduł definiuje zmienną lub klasę o nazwie
x
, Python wykorzysta ją i zakończy szukanie. - wbudowana przestrzeń nazw -- globalna dla wszystkich modułów. Ponieważ jest to ostatnia deska ratunku, Python przyjmie, że
x
jest nazwą wbudowanej funkcji lub zmiennej.
Jeśli Python nie znajdzie x
w żadnej z tych przestrzeni nazw, poddaje się i wyrzuca wyjątek NameError
z wiadomością "name 'x' is not defined", którą zobaczyliśmy w przykładzie 3.21, lecz nie jesteśmy w stanie ocenić, jak Python zadziała, zanim dostaniemy ten błąd.
Uwaga!
Python korzysta z zagnieżdżonych przestrzeni nazw (ang. nested scope). Jeśli tworzymy wewnątrz pewnej funkcji inną funkcję, Python stworzy nową zagnieżdżoną przestrzeń nazw dla zagnieżdżonej funkcji, która jednocześnie będzie lokalną przestrzenią nazw, ale jednocześnie będziemy mogli korzystać z przestrzeni nazw funkcji, w której daną funkcję zagnieżdżamy i pozostałych przestrzeni nazw (globalnej i wbudowanej). |
Zmieszałeś się? Nie panikuj! Jest to naprawdę wypaśne. Podobnie, jak wiele rzeczy w Pythonie, przestrzenie nazw są bezpośrednio dostępne podczas wykonywania programu. Jak? Do lokalnej przestrzeni nazw mamy dostęp poprzez wbudowaną funkcję locals
, a globalna (na poziomie modułu) przestrzeń nazw jest dostępna poprzez wbudowaną funkcję globals
.
locals
>>> def foo(arg): #(1) ... x = 1 ... print locals() ... >>> foo(7) #(2) {'arg': 7, 'x': 1} >>> foo('bar') #(3) {'arg': 'bar', 'x': 1}
- Funkcja
foo
posiada dwie zmienne w swojej lokalnej przestrzeni nazw:arg
, której wartość jest przekazana do funkcji, a takżex
, która jest zdefiniowana wewnątrz funkcji. locals
zwraca słownik par nazwa/wartość. Kluczami słownika są nazwy zmiennych w postaci napisów. Wartościami słownika są bieżące wartości tych zmiennych. Zatem wywołującfoo
z7
, wypiszemy słownik zawierający dwie lokalne zmienne tej funkcji, czyliarg
(o wartości7
) ix
(o wartości1
).- Pamiętaj, Python jest dynamicznie typowany, dlatego też możemy w prosty sposób jako argument
arg
przekazać napis. Funkcja (a także wywołanielocals
) będą nadal działać jak należy.locals
działa z wszystkimi zmiennymi dowolnych typów danych.
To co locals
robi dla lokalnej (należącej do funkcji) przestrzeni nazw, globals
robi dla globalnej (modułu) przestrzeni nazw. globals
jest bardziej ekscytujące, ponieważ przestrzeń nazw modułu jest bardziej pasjonująca [1]. Przestrzeń nazw modułu nie tylko przechowuje zmienne i stałe na poziomie tego modułu, lecz także funkcje i klasy zdefiniowane w tym module. Ponadto dołączone do tego jest cokolwiek, co zostało zaimportowane do tego modułu.
Pamiętasz różnicę między from module import
, a import module
? Za pomocą import module
, zaimportujemy sam moduł, który zachowa własną przestrzeń nazw, a to jest przyczyną, dlaczego musimy odwołać się do nazwy modułu, aby dostać się do jakiejś funkcji lub atrybutu (pisząc module.function
). Z kolei za pomocą from module import
rzeczywiście importujemy do własnej przestrzeni nazw określoną funkcje i atrybuty z innego modułu, a dzięki temu odwołujemy się do niego bezpośrednio, bez wskazywania modułu, z którego one pochodzą. Dzięki funkcji globals
możemy zobaczyć, że rzeczywiście tak jest.
Spójrzmy na poniższy blok kodu, który znajduje się na dole BaseHTMLProcessor.py.
globals
if __name__ == "__main__":
for k, v in globals().items(): #(1)
print k, "=", v
- Na wypadek gdyby wydawało Ci się to straszne, to pamiętaj, że widzieliśmy to już wcześniej. Funkcja
globals
zwraca słownik, następnie iterujemy go wykorzystując metodęitems
i wielozmienne przypisanie. Jedyną nową rzeczą jest funkcjaglobals
.
Teraz, uruchamiając skrypt z linii poleceń otrzymamy takie wyjście (twoje wyjście może się nieco różnić, jest zależne od systemu i miejsca instalacji Pythona):
c:\docbook\dip\py> python BaseHTMLProcessor.py SGMLParser = sgmllib.SGMLParser #(1) htmlentitydefs = <module 'htmlentitydefs' from 'C:\Python23\lib\htmlentitydefs.py'> #(2) BaseHTMLProcessor = __main__.BaseHTMLProcessor #(3) __name__ = __main__ #(4) [...ciach ...]
SGMLParser
został zaimportowany zsgmllib
, wykorzystującfrom module import
. Oznacza to, że został zaimportowany bezpośrednio do przestrzeni nazw modułu i w tym też miejscu jest.- W przeciwieństwie do
SGMLParser
a,htmlentitydefs
został zaimportowany wykorzystując instrukcjęimport
. Oznacza to, że modułhtmlentitydefs
sam w sobie jest przestrzenią nazw, ale zmiennaentitydefs
wewnątrzhtmlentitydefs
już nie. - Moduł ten definiuje jedną klasę,
BaseHTMLProcessor
i oto ona. Dodajmy, że ta wartość jest klasą samą w sobie, a nie jakąś specyficzną instancją tej klasy. - Pamiętasz trik
if __name__
? Kiedy uruchamiamy moduł (zamiast importować go z innego modułu), to wbudowany atrybut__name__
ma specjalną wartość,"__main__"
. Ponieważ uruchomiliśmy ten moduł jako skrypt z linii poleceń, wartość__name__
wynosi"__main__"
, dlatego też zostanie wykonany mały kod testowy, który wypisujeglobals
.
Korzystając z funkcji locals i globals możemy pobrać dynamicznie wartość dowolnej zmiennej, dzięki przekazaniu nazwy zmiennej w postaci napisu. Funkcjonalność ta jest analogiczna do getattr , która pozwala dostać się do dowolnej funkcji, dzięki przekazaniu jej nazwy w postaci napisu.
|
Poniżej pokażemy inną ważną różnicę między funkcjami locals
i globals
, a o której powinniśmy się dowiedzieć, zanim nas to ukąsi. Jakkolwiek to i tak Ciebie ukąsi, ale przynajmniej będziesz pamiętał, że była o tym mowa w tym podręczniku.
locals
jest tylko do odczytu, a globals
już nie
def foo(arg):
x = 1
print locals() #(1)
locals()["x"] = 2 #(2)
print "x=",x #(3)
z = 7
print "z=",z
foo(3)
globals()["z"] = 8 #(4)
print "z=",z #(5)
- Ponieważ
foo
zostało wywołane z argumentem3
, więc zostanie wypisane{'arg': 3, 'x': 1}
. Nie powinno to być zaskoczeniem. locals
jest funkcją zwracającą słownik i w tym miejscu zmieniamy wartość w tym słowniku. Możemy myśleć, że wartość zmiennejx
zostanie zmieniona na2
, jednak tak nie będzie.locals
właściwie nie zwraca lokalnej przestrzeni nazw, zwraca jego kopię. Zatem zmieniając ją, nie zmieniamy wartości zmiennych w lokalnej przestrzeni nazw.- Zostanie wypisane
x= 1
, a niex= 2
. - Po tym, jak zostaliśmy poparzeni przez
locals
, możemy myśleć, że ta operacja nie zmieni wartościz
, ale w rzeczywistości zmieni. W skutek wewnętrznych różnic implementacyjnych [2],globals
zwraca aktualną, globalną przestrzeń nazw, a nie jej kopię; całkowicie odwrotne zachowanie w stosunku dolocals
. Tak więc dowolna zmiana zwróconego przezglobals
słownika bezpośrednio wpływa na zmienne globalne. - Wypisze
z= 8
, a niez= 7
.
Przypisy