Ostatnio rozmawialiśmy sobie o bardzo ciekawej rzeczy jaką są funkcje anonimowe, zwane też lambda, dziś natomiast pociągniemy dalej temat funkcji, choć związany bardziej z czymś innym, a mianowicie – scope.
Czym jest scope? Jest to po prostu angielskie słowo, oznaczające zasięg, w tym przypadku będzie to zasięg zmiennych. Wprowadzenie koncepcji funkcji, wymaga pochylenia się nad tym tematem. Bowiem funkcje zmieniają trochę reguły gry.
Powiedzmy sobie zatem o najważniejszych kwestiach i podstawach związanych z zasięgiem zmiennych (scope) w języku programowania jakim jest Python. W innych językach programowania także występuje ta kwestia, gdyby ktoś był zaintersowany.
Zasięg zmiennych
W uproszczeniu, domyślnie możemy użyć czegoś co zwiemy zasięgiem globalnym. Jeśli utworzymy po prostu jakąś zmienną to ma ona zasięg globalny, znaczy to, że możemy używać jej w każdym miejscu tego pliku. Możemy to robić również w funkcjach.
Jak widzimy wyżej mamy po prostu zmienną. Jest to zmienna utworzona przez Nas, w żadnej funkcji czy innym zagnieżdżonym bloku kodu. Jest to więc zmienna globalna , ma ona taki właśnie zasięg (scope). W wyniku czego mają do niej dostęp wszystkie elementy Naszego programu.
Mamy tutaj jednak pewien problem. Może mieć bowiem miejsce sytuacja, gdzie niżej w Naszym kodzie coś modyfikuje wartość zmiennej globalnej. Może się zdarzyć, że jakaś Nasza funkcja, przez przypadek zmodyfikuje zmienną przykładowo o zasięgu globalnym, a nie powinna. Wyniki tego mogą być nieprzewidziane. Zatem w praktyce trzeba bardzo ostrożnie postępować ze zmiennymi mającymi scope globalny.
W praktyce, scope globalny moglibyśmy sobie zaprezentować za pomocą po prostu wyprintowania takiej zmiennej, ale możemy też takiej zmiennej użyć w każdym ifie, matchu czy innej konstrukcji sterującej przepływem programu. Możemy także takiej zmiennej użyć w funkcji.
I funkcja taka ma oczywiście dostęp do Naszej zmiennej, może to właśnie prowadzić do wspomnianego wyżej problemu. Bowiem funkcja ma dostęp do zmiennej o scope globalnym, zatem funkcje mogą modyfikować globalny stan Naszego programu. Jest to potencjalnie niebezpieczne, bo może prowadzić do nieprzewidzianych zachowań i błędów, które łatwo popełnić.
Local scope
Rozwiązaniem takiego problemu jest tak zwany zasięg lokalny. Jest to po prostu przykładowo sytuacji, gdy zmienną deklarujemy bezpośrednio w funkcji i istnieje tylko w czasie jej trwania. Znaczy to tyle, że uruchamiając daną funkcję, która deklaruje zmienną, po zakończeniu jej działania, zmienna wraz z innymi wynikami działania funkcji, zostaje usunięta. Możemy oczywiście zapisać wynik tej funkcji do jakiejś innej zmiennej, używając m.in. wspomnianego returna.
Jak można powyżej zauważyć, możemy mieć dwie zmienne. Jedna o scope globalny, a druga lokalnym (istnieje tylko w czasie działania fn). Obie zmienne nazywają się tak samo, a mimo wszystko nie są tym samym – nie kolidują ze sobą. Przypisując wartość zmiennej w funkcji, nie zmieniamy zmiennej globalnej. Gdy na końcu obie wyświetlamy, wartość globalnej jest bez zmian, a lokalnej jest taka jaką przypisaliśmy.
Zatem w sytuacji dwóch zmiennych x, linijka 7 Naszego programu wie, o którą zmienną chodzi. Dla tego printa istnieje bowiem tylko jedna zmienna x. Druga zmienna x, ta ze scope globalnym, już nie istnieje, przestała linijkę wyżej, gdy funkcja zakończyła swoje działanie.
Oczywiście jeśli zdefiniujemy inną zmienną w funkcji i spróbujemy się do niej odwołać, to nie będziemy mogli, jak widać powyżej.
Otrzymujemy błąd mówiący, że zmienna z nie jest zdefiniowana. Co jest logiczne, bowiem zmienna z jest definiowana przez funkcje. Funkcja definiuje zmienną o local scope, skutkuje zniszczeniem zmiennej po zakończeniu działania przez funkcję. Finalnie nie ma takiej zmiennej w global scope, więc otrzymujemy informacje NameError. Python nie wie o co Nam chodzi.
Uparcie się na global scope w funkcji
Domyślnie funkcja nie ma dostępu do zmiennej globalnej, możemy taki dostęp nadać używając słówka global przed nazwą zmiennej. Tak 'zadeklarowana’ zmienna w funkji, będzie tym samym co zmienna utworzona w global scope.
Tworzymy w powyższym kodzie zatem zmienną x, której przypisujemy wartość 1. Następnie deklarujemy funkcje, a w pierwszej linii mówimy, że będzie ona mogła pracować na x, które ma global scope. Później dodajemy do zmiennej x cyfrę jeden. Na końcu wyświetlamy zmienną zmienioną przez funkcję. Przed wywołaniem funkcji wyświetlamy jeszcze globalną zmienną. I widzimy wyraźnie, że wywołując funkcję, dodaliśmy jeden do zmiennej x w global scope.
Tak oto prezentują się podstawy scope, czyli zasięgu zmiennych. Jesteśmy tutaj uważni i ostrożnie korzystamy z global scope, żeby nie zrobić sobie bubu 😉