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:

  1. 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.
  2. 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.
  3. 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.
  4. 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.

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.

Przykład. Wprowadzenie do locals
>>> def foo(arg): #(1)
...     x = 1
...     print locals()
...     
>>> foo(7)        #(2)
{'arg': 7, 'x': 1}
>>> foo('bar')    #(3)
{'arg': 'bar', 'x': 1}
  1. Funkcja foo posiada dwie zmienne w swojej lokalnej przestrzeni nazw: arg, której wartość jest przekazana do funkcji, a także x, która jest zdefiniowana wewnątrz funkcji.
  2. 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ąc foo z 7, wypiszemy słownik zawierający dwie lokalne zmienne tej funkcji, czyli arg (o wartości 7) i x (o wartości 1).
  3. Pamiętaj, Python jest dynamicznie typowany, dlatego też możemy w prosty sposób jako argument arg przekazać napis. Funkcja (a także wywołanie locals) 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.

Przykład. Wprowadzenie do globals
 if __name__ == "__main__":
     for k, v in globals().items():             #(1)
         print k, "=", v
  1. 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 funkcja globals.

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 ...]
  1. SGMLParser został zaimportowany z sgmllib, wykorzystując from module import. Oznacza to, że został zaimportowany bezpośrednio do przestrzeni nazw modułu i w tym też miejscu jest.
  2. W przeciwieństwie do SGMLParsera, htmlentitydefs został zaimportowany wykorzystując instrukcję import. Oznacza to, że moduł htmlentitydefs sam w sobie jest przestrzenią nazw, ale zmienna entitydefs wewnątrz htmlentitydefs już nie.
  3. 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.
  4. 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 wypisuje globals.

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.

Przykład. 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)
  1. Ponieważ foo zostało wywołane z argumentem 3, więc zostanie wypisane {'arg': 3, 'x': 1}. Nie powinno to być zaskoczeniem.
  2. locals jest funkcją zwracającą słownik i w tym miejscu zmieniamy wartość w tym słowniku. Możemy myśleć, że wartość zmiennej x zostanie zmieniona na 2, 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.
  3. Zostanie wypisane x= 1, a nie x= 2.
  4. Po tym, jak zostaliśmy poparzeni przez locals, możemy myśleć, że ta operacja nie zmieni wartości z, 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 do locals. Tak więc dowolna zmiana zwróconego przez globals słownika bezpośrednio wpływa na zmienne globalne.
  5. Wypisze z= 8, a nie z= 7.

Przypisy

  1. To zdanie za wiele nie wnosi.
  2. Nie będziemy się wdawać w szczegóły