Zanurkuj w Pythonie/locals i globals: Różnice pomiędzy wersjami

Usunięta treść Dodana treść
zamiana {{Nawigacja|Python|->{{Nawigacja|Zanurkuj w Pythonie| [[Python/->[[../
Piotr (dyskusja | edycje)
poprawki
Linia 1:
== <ttcode>locals</ttcode> i <ttcode>globals</ttcode> ==
{{Podświetl|py}}
 
== <tt>locals</tt> i <tt>globals</tt> ==
 
Odejdźmy teraz na minutkę od przetwarzania HTML-a. Porozmawiajmy o tym, jak Python obchodzi się ze zmiennymi. Python posiada dwie wbudowane funkcje, <ttcode>locals</ttcode> i <ttcode>globals</ttcode>, które pozwalają nam uzyskać w słownikowy sposób dostęp do zmiennych lokalnych i globalnych.
 
Pamiętasz <ttcode>locals</ttcode>? Pierwszy raz zobaczyłeśmogliśmy 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 <ttcode>locals</ttcode>. 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. Właściwie możeszMoż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 kliku 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 <ttcode>x</ttcode>, Python przeszuka wszystkie przestrzenie nazw, aby znaleść 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ą <ttcode>x</ttcode>, 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 <tt>x</tt>, Python wykorzysta ją i zakończy szukanie.
# wbudowanaglobalna przestrzeń nazw namespace -- globalnaokreślona dla wszystkichbieżącego modułówmodułu. PonieważJeśli jestmoduł todefiniuje ostatniazmienną deskalub ratunku,klasę Pythono przyjmie, żenazwie <ttcode>x</ttcode>, jestPython nazwąwykorzysta wbudowanej funkcjii lubzakończy zmiennejszukanie.
# wbudowana przestrzeń nazw -- globalna dla wszystkich modułów. Ponieważ jest to ostatnia deska ratunku, Python przyjmie, że <code>x</code> jest nazwą wbudowanej funkcji lub zmiennej.
 
Jeśli Python nie znajdzie <ttcode>x</ttcode> w żadnej z tych przestrzeni nazw, poddaje się i wyrzuca wyjątek <code>NameError</code> z wiadomością "name 'x'„There is nonot variable named 'x'”''defined", któryktórą zobaczyliśmy w ''Przykładzie[[../Deklarowanie zmiennych#przy-3.18'',21|przykładzie „Odwoływanie się do niezdefiniowanej zmiennej”3.21]], lecz nie jesteśjesteśmy w stanie ocenić, jak Python zadziała, zanim dostanieszdostaniemy ten błąd.
 
{{Uwaga|
Python 2.2korzysta wprowadziłz subtelną, a zarazem ważną zmianę, która wpływa na porządek przeszukiwaniazagnieżdżonych przestrzeni nazw: zagnieżdżone przestrzenie nazw (ang. ''nested scope''). WJeśli wersjach wcześniejszych niż 2.2, kiedy odwołamy się do zmiennejtworzymy wewnątrz zagnieżdżonejpewnej funkcji lub funkcjiinną lambdafunkcję, Python będziestworzy szukałnową tejzagnieżdżoną zmiennej w bieżącej przestrzeniprzestrzeń nazw danejdla funkcji (zagnieżdżonej lub lambda)funkcji, aktóra następniejednocześnie wbędzie przestrzenilokalną przestrzenią nazw, modułu.ale Pythonjednocześnie 2.2będziemy będziemogli szukał zmiennej wkorzystać bieżącejz przestrzeni nazw danej funkcji (zagnieżdżonej lub lambda), następnie w przestrzeniktórej nazwdaną nadrzędnejfunkcję funkcji,zagnieżdżamy ai potem wpozostałych przestrzeni nazw modułu(globalnej i wbudowanej).
}}
 
Zmieszałeś się? Nie panikuj! Jest to naprawdę coolwypaś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ę <ttcode>locals</ttcode>, a globalna (na poziomie modułu) przestrzeń nazw jest dostępna poprzez wbudowaną funkcję <ttcode>globals</ttcode>.
 
'''Przykład 8.10. Wprowadzenie do <tt>locals</tt>'''
 
{{Python/Przykład
'''Przykład |8.10. |Wprowadzenie do <ttcode>locals</ttcode>'''
|tekst=
>>> def foo(arg): #(1)
... x = 1
Linia 36:
...
>>> foo(7) #(2)
{{samp|{'arg': 7, 'x': 1}}}
>>> foo('bar') #(3)
{{samp|{'arg': 'bar', 'x': 1}}}
 
# Funkcja <ttcode>foo</ttcode> posiada dwie zmienne w swojej lokalnej przestrzeni nazw: <ttcode>arg</ttcode>, której wartość jest przekazana do funkcji, a także <ttcode>x</ttcode>, która jest zdefiniowana wewnątrz funkcji.
# <ttcode>locals</ttcode> 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ąc <ttcode>foo</ttcode> z <code>7</code>, wypiszemy słownik zawierający dwie lokalne zmienne tej funkcji, czyli <ttcode>arg</ttcode> (o wartości <ttcode>7</ttcode>) i <ttcode>x</ttcode> (o wartości <ttcode>1</ttcode>).
# Pamiętaj, Python jest dynamicznie typowany, dlatego też możemy w prosty sposób jako argument <ttcode>arg</ttcode>, przekazać napis. Funkcja (a także wywołanie <ttcode>locals</ttcode>) będą nadal działać jak należy. <ttcode>locals</ttcode> działa z wszystkimi zmiennymi dowolnych typów danych.
}}
 
To co <ttcode>locals</ttcode> robi dla lokalnej (należącej do funkcji) przestrzeni nazw, <ttcode>globals</ttcode> robi dla globalnej (modułu) przestrzeni nazw. <ttcode>globals</ttcode> jest bardziej ekscytujące, ponieważ przestrzeń nazw modułu jest bardziej pasjonująca <ref>To zdanie za wiele nie wnosi.</ref>. 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 <ttcode>from module import</ttcode>, a <ttcode>import module</ttcode>? Za pomocą <ttcode>import module</ttcode>, zaimportujeszzaimportujemy sam moduł, który zachowa własną przestrzeń nazw, a to jest przyczyną, dlaczego musiszmusimy odwołać się do nazwy modułu, aby dostać się do jakiejś funkcji lub atrybutu (pisząc <ttcode>module.function</ttcode>). Z kolei za pomocą <ttcode>from module import</ttcode> rzeczywiście importujeszimportujemy do własnej przestrzeni nazw określoną funkcje i atrybuty z innego modułu, a dzięki temu odwołujeszodwołujemy się do niego bezpośrednio, bez wskazywania modułu, z którego one pochodzą. Dzięki funkcji <ttcode>globals</ttcode> możemy zobaczyć, że rzeczywiście tak jest.
 
SpójrzSpójrzmy na poniższy blok kodu, który znajduje się na dole <tt>{{Python/Src|BaseHTMLProcessor.py</tt>:}}.
'''Przykład 8.11. Wprowadzenie do <tt>globals</tt>'''
 
Spójrz na poniższy blok kodu, który znajduje się na dole <tt>BaseHTMLProcessor.py</tt>:
 
{{Python/Przykład
'''Przykład |8.11. |Wprowadzenie do <ttcode>globals</ttcode>'''
|tekst=
if __name__ == "__main__":
for k, v in globals().items(): #(1)
print k, "=", v
 
# Na wypadek gdybyśgdyby wydawało Ci się wystraszyłto straszne, to pamiętaj, że widzieliśmy to już wcześniej. Funkcja <ttcode>globals</ttcode> zwraca słownik, po którym następnie [[../Pętla for#przy-6.10|iterujemy słownik]] wykorzystując metodę <ttcode>items</ttcode> i [[../Deklarowanie zmiennych#Wielozmienne przypisania|wielozmienne przypisanie]]. Jedyną nową rzeczą jest funkcja <ttcode>globals</ttcode>.
}}
 
Teraz, uruchamiając skrypt z linii poleceń otrzymamy takie wyjście (twoje wyjście może się nieco różnić, zależniejest zależne od tego, na jakim systemiesystemu i gdziemiejsca zainstalowałeś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 ...]
... pozostała część pominięta dla czytelności ...
 
# <ttcode>SGMLParser</ttcode> został zaimportowany z <ttcode>sgmllib</ttcode>, wykorzystując <ttcode>from module import</ttcode>. Oznacza to, że został zaimportowany bezpośrednio do przestrzeni nazw modułu i w tym też miejscu jest.
# W przeciwieństwie do <ttcode>SGMLParser</ttcode>-a, <ttcode>htmlentitydefs</ttcode> został zaimportowany wykorzystując instrukcję <ttcode>import</ttcode>. Oznacza to, że moduł <ttcode>htmlentitydefs</ttcode> sam w sobie jest przestrzenią nazw, ale zmienna <ttcode>entitydefs</ttcode> wewnątrz <ttcode>htmlentitydefs</ttcode> już nie.
# Moduł ten definiuje jedną klasę, <ttcode>BaseHTMLProcessor</ttcode> i oto onona. Dodajmy, że ta wartość jest klasą jako[[../Atrybuty klas#przy-5.17|samą w takąsobie]], a nie jakąś specyficzną instancją tej klasy.
# Pamiętasz [[../Testowanie modułów|trik <ttcode>if __name__</ttcode>]]? Kiedy uruchamiamy moduł (zamiast importować go z innego modułu), to wbudowany atrybut <ttcode>__name__</ttcode> ma specjalną wartość, <ttcode>"__main__"</ttcode>. Ponieważ uruchomiliśmy ten moduł jako skrypt z linii poleceń, wartość <ttcode>__name__</ttcode> wynosi <ttcode>"__main__"</ttcode>, dlatego też zostanie wykonany mały kod testowy, który wypisuje <ttcode>globals</ttcode>.
 
{{Infobox|
Korzystając z funkcji <ttcode>locals</ttcode> i <ttcode>globals</ttcode> możemy pobrać dynamicznie wartość dowolnej zmiennej, dzięki przekazaniu nazwy zmiennej w postaci napisu. Funkcjonalność ta jest analogiczna do <ttcode>getattr</ttcode>, która pozwala dostać się do dowolnej funkcji, dzięki przekazaniu jej nazwy w postaci napisu.
}}
 
TutajPoniżej mamypokażemy inną ważną różnicę między funkcjami <ttcode>locals</ttcode> i <ttcode>globals</ttcode>, a o której powinieneśpowinieniśmy się dowiedzieć, zanim cięnas to ukąsi. Jakkolwiek to i tak cięCiebie ukąsi, ale przynajmniej będziesz pamiętał, że siębyła o tym uczyłeśmowa w tym podręczniku.
 
'''Przykład 8.12. <tt>locals</tt> jest tylko do odczytu, a <tt>globals</tt> już nie'''
 
 
{{Python/Przykład
'''Przykład |8.12. |<ttcode>locals</ttcode> jest tylko do odczytu, a <ttcode>globals</ttcode> już nie'''
|tekst=
def foo(arg):
x = 1
Linia 94 ⟶ 97:
print "z=",z #(5)
 
# Ponieważ <ttcode>foo</ttcode> zostało wywołane z argumentem <ttcode>3</ttcode>, więc zostanie wypisane <ttcode>{'arg': 3, 'x': 1}</ttcode>. Nie powinno to być zaskoczeniem.
# <ttcode>locals</ttcode> jest funkcją zwracającą słownik i w tym miejscu zmieniamy wartość w tym słowniku. MożeszMożemy myśleć, że wartość zmiennej <ttcode>x</ttcode> zostanie zmieniona na <ttcode>2</ttcode>, jednak tak nie jestbędzie. <ttcode>locals</ttcode> 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 <ttcode>x= 1</ttcode>, a nie <ttcode>x= 2</ttcode>.
# Po tym, jak zostaliśmy poparzeni przez <ttcode>locals</ttcode>, możemy myśleć, że ta operacja nie zmieni wartości <ttcode>z</ttcode>, ale w rzeczywistości zmieni. W skutek wewnętrznych różnic implementacyjnych <ref>Nie będziemy się wdawać w szczegóły</ref>, <ttcode>globals</ttcode> zwraca aktualną, globalną przestrzeń nazw, a nie jej kopię; całkowicie odwrotne zachowanie w stosunku do <ttcode>locals</ttcode>. Tak więc dowolna zmiana, zwróconego przez <ttcode>globals</ttcode> słownika, bezpośrednio wpływa na zmienne globalne.
# Wypisze <ttcode>z= 8</ttcode>, a nie <ttcode>z= 7</ttcode>.
}}
 
'''{{Przypisy'''}}
----
<references />
 
<noinclude>
{{Nawigacja|Zanurkuj w Pythonie|
[[../Wprowadzenie do BaseHTMLProcessor.py|Wprowadzenie do <tt>BaseHTMLProcessor.py</tt>]]|
[[../Formatowanie napisów w oparciu o słowniki|Formatowanie napisów w oparciu o słowniki/]]|
}}
{{Podświetl|py}}
</noinclude>