fbpx
ArtykułPython

Python – zasięgi zmiennych

Python jest jednym z najpopularniejszych języków programowania, używanym głównie w dziedzinie analizy danych, uczenia maszynowego, automatyzacji zadań i tworzenia stron internetowych.

Python dla początkujących – warto?

Jedną z głównych cech Pythona jest jego czytelność – posiada czytelną i intuicyjną składnię, która ułatwia pisanie i czytanie kodu. Do tego charakteryzuje się również ogromną ilością bibliotek i modułów, które dostarczają gotowych rozwiązań do różnych zastosowań. Dzięki temu programiści Pythona mogą korzystać z bogatego ekosystemu, oszczędzając czas i wysiłek. Dzięki temu Python jest świetnym językiem zarówno dla początkujących, jak i zaawansowanych programistów.

Samodzielna nauka programowania czy jednak pod okiem trenera?

Jeśli jesteś zainteresowany nauką programowania, Python może być doskonałym wyborem. Znajomość Pythona otwiera drzwi do wielu możliwości kariery w obszarze IT. Wspomaga również rozwój umiejętności analizy danych i uczenia maszynowego.

Programowania możesz uczyć się samodzielnie lub pod okiem trenera. Możesz pójść na studia, robić kursy (zarówno darmowe, jak i płatne), czytać książki, czy buszować po internecie w poszukiwaniu innych sposobów. Jest ich wiele, więc na pewno każdy znajdzie coś dla siebie.

A teraz będzie trochę programowania! Czym są zasięgi zmiennych?

Zasięgi zmiennych – kurs programowania dla początkujących!

Pyton to jest taki zwierz, który może robić dla nas różne fajne rzeczy. Najpierw jednak musimy mu wytłumaczyć, w jaki sposób należy je robić. Jeżeli chcemy poprosić Pythona o wypisanie czegoś, to musimy mu wytłumaczyć, co ma wypisać. A co to znaczy to „coś”? To jest właśnie definicja ZMIENNYCH.

Python potrafi podstawowe rzeczy i rozumie słówka typu „print” (wypisz coś). Ale na przykład takiego słówka „string” już nie rozumie. Nie wie, co to jest. Więc należy mu wytłumaczyć, że to jest „coś” (something) i że pod tym kryje się w tym przypadku napis „Hello!”. I po tym, jak zmienna „something” zostanie zdefiniowana, to można poprosić Pythona o to, żeby taką zmienną wypisał.

Wszystko działa! Python wypisał „Hello!”. Pamiętajmy, że zadziała to dopiero w sytuacji, kiedy zostanie zdefiniowane, czyli w sytuacji, gdy Python wie, o co nam chodzi.

Jeżeli odwołanie do „something” nastąpi, zanim zostanie ono zdefiniowane, to to nie zadziała. Python powie, że nie rozumie, czym jest ta nazwa, czym jest ta zmienna, co to jest to „something”.

Jak więc to się ma do zasięgu zmiennych? Czym właściwie jest zasięg zmiennych?

O interakcji z Pythonem można myśleć jak o pewnego rodzaju rozmowie między osobą a Pythonem. Osoba najpierw tłumaczymy coś Pythonowi, by potem go o coś poprosić. Ta rozmowa może być bardzo złożona i skomplikowana, bo i program może być bardzo złożony i skomplikowany. W tej rozmowie może przewijać się kilka wątków, wiele różnego rodzaju kontekstu i tematów. Czasami zdarzy się tak, że użyte zostaną te same słowa, ale będą się one odnosić do różnych rzeczy.

Działa to na podobnej zasadzie, jak zwyczajna rozmowa z innymi ludźmi. Osoba X opowiada, jakiego ma fajnego pieska. Następnie osoba Y mówi o innym psie, który był ostatnio bardzo agresywny. Obie te rozmowy dotyczą różnych psów, a przecież w obu przypadkach mówimy „pies”. To są te same słowa, ale my, jako ludzie, używamy słów w pewnym kontekście i właśnie w tym kontekście te słowa rozumiemy.

ZASIĘG ZMIENNYCH w Pythonie należy rozumieć również w ten sposób.

Czyli zasięg zmiennych, to taki kontekst widoczności zmiennej. Jeśli „coś” zostanie wytłumaczone Pythonowi jako „Hello!”, a „pies” będzie zdefiniowane jako na przykład „Maks”, to Python odczyta zmienną w pewnym kontekście. W momencie, gdy nastąpi odwołanie do tej zmiennej, to Python zrozumie ją w zdefiniowanym kontekście. 

W skrócie: można używać tej samej nazwy w różnych kontekstach. Może ona znaczyć różne rzeczy i Python będzie wiedział, o co chodzi. Wystarczy ją wcześniej zdefiniować.

Jakie mamy zasięgi zmiennych w Pythonie i co z tego wynika? Jak możemy to wykorzystać i na co należy uważać?

W Pythonie mamy 4 zasięgi zmiennych

Zasięg lokalny – definicja zmiennej znajduje się wewnątrz funkcji. Zakresem lokalnym jest też zdefiniowanie czegoś wewnątrz klasy. W Pythonie można mieć tych funkcji wiele i w każdej funkcji można mieć tak samo nazwane zmienne, które będą odwoływać się do różnych wartości. Co ważne, te zmienne nie będą dostępne poza tą funkcją.

Wróćmy zatem do rozmów o pieskach. Jeśli osoba X z osobą Y rozmawia o piesku imieniem Maks, a potem z osobą Z o piesku imieniem Reks, to mimo że w obu przypadkach używają tego samego słowa „pies”, to osoba Z nie będzie znała Maksa. Osoba X będzie musiała jej wytłumaczyć, że mowa jest psie osoby Y.

Enclosing (nonlocal) –  to taki trochę specjalny zasięg (zakres zmiennych). Występuje on w sytuacji, gdy funkcja (a) jest zagnieżdżona w innej funkcji (b). To jest ten zasięg, który tak jakby otacza funkcję. To zmienna, która jest zdefiniowana w funkcji wewnętrznej w kontekście do funkcji zewnętrznej. Czyli w Pythonie można definiować funkcje w kontekście innych funkcji.

Zasięg globalny – zakres na poziomie pliku. To najszerszy zakres użytkownika, który możemy zdefiniować na poziomie skryptu czy naszego modułu pythonowego.

Zakres built-in – czyli potocznie mówiąc, to takie „oczywiste oczywistości”.
Czyli znowu w kontekście rozmowy: jeśli osoba X zapyta osobę Y, który mamy teraz rok, to osoba Y bez głębszego zastanowienia będzie wiedziała, który mamy teraz rok. Tak samo jest w Pythonie. W którymkolwiek miejscu Python zostanie poproszony “wypisz coś” (print), to będzie wiedział, co to znaczy „print”, bo ma to zdefiniowane w tym zakresie built-in. Jest to po prostu wbudowane w Pythona.

Jak to wygląda w kodzie, w jaki sposób one ze sobą współgrają?

Przykład 1

Funkcja zdefiniowana w Pythonie. Przyjmuje ona dwa argumenty: liczy prędkość średnią – przyjmuje argument „distance” i „time” i liczy jakiś wynik „result”. Ale wredna funkcja tego wyniku nie zwraca. Zamiast zwrócić wynik, to pisze, że nic nie zwraca. Ale wynik jest w środku dostępny.

W związku z tym sprytny programista mógłby stwierdzić, że zamiast zwracać ten wynik, to można po prostu się do niego odwołać, bo zmienna została przecież zdefiniowana (result).

PyCharm podkreśla „result” na czerwono, co sugeruje, że to się nie uda. I jeżeli skrypt zostanie uruchomiony, to widać, że „Nic nie zwracam – ha ha!” się wypisało, ale potem pojawił się błąd nazwy.

Tak więc Python nie wypisał tej zmiennej „result”, mimo że ta zmienna została wcześniej w tym skrypcie zdefiniowana.

Dlaczego nie można się do tego dostać? Dlatego, że właśnie jesteśmy w innym zakresie.
Tutaj jest zakres globalny, poziom modułu skryptu:

A ta zmienna jest widoczna tylko w zakresie lokalnym, tylko w tej jednej konwersacji. Można się do niej odwoływać tylko w ramach tej jednej funkcji.

Przykład 2

Zmienna „distance” została zdefiniowana na zewnątrz, czyli na poziomie globalnym (na poziomie modułu). Mimo że nie została ona przekazana do funkcji, to można się do niej bez problemu w tej funkcji odwołać. Więc wszystko jest w porządku.

Więc wszystko jest w porządku, działa:

Przeliczyła się prędkość średnia, mimo że zmienna „distance” nie została przekazana.

Dlaczego tak się stało, mimo że ta funkcja została zdefiniowana wcześniej, niż my ją tworzymy i tłumaczymy Pythonowi, co to znaczy?

Stało się tak, ponieważ zmienna „distance” została utworzona na poziomie globalnym, a funkcja została wywołana później, niż ta zmienna „distance” została zdefiniowana. Czyli została wywołana w tym miejscu:

Python poszukuje tej zmiennej dopiero wtedy, gdy ma wykonać ten kod, nie wcześniej. Czyli w momencie, kiedy funkcja „policz prędkość średnią” została wywołana, to Python wszedł tutaj do środka. Stało się to już po zdefiniowaniu zmiennej „distance”. I dopiero wtedy Python poszukał sobie tej zmiennej „distance”. I jak jej szukał, to wykorzystał kolejność zakresu, o której była mowa wcześniej. Czyli najpierw spróbował poszukać tej zmiennej na poziomie lokalnym i jej tam nie znalazł. Następnie poszukał jej na poziomie enclosing i też jej nie znalazł. Potem szukał jej na poziomie globalnym i właśnie tam ją znalazł. Gdyby jej jednak nie znalazł na żadnym poziomie, to wtedy by wyrzucił wyjątek, że nie rozumie tego pojęcia.

Przykład 3

Zmienna „distance”, która, bardzo podobnie jak było wcześniej, jest zdefiniowana wewnątrz innej funkcji.

I ta funkcja definiuje zmienną „distance” i tu już to nie działa. Dlaczego to nie będzie działać? Pojawił się błąd.

Nie będzie działać dlatego, że jest to poziom lokalny.

Widać tu jeden zasięg lokalny jednej funkcji (a) i jeden zasięg lokalny drugiej funkcji (b). I teraz ten zasięg lokalny funkcji (a) nie jest dostępny dla funkcji (b).

Przykład 4

W jaki sposób można manipulować tą wartością globalną? W jaki sposób możemy ją zmieniać? Można wypisywać wartość globalną w ten sposób:

Czyli wartość jest zdefiniowana na poziomie globalnym. Następnie nastąpi odwołanie do funkcji, której używa. Wszystko działa, można odwołać się do tej wartości i ją wypisać. A co by było, gdybyśmy chcieli np. zmodyfikować wartość, która nie została zdefiniowana w tej funkcji, tylko została zdefiniowana na poziomie skryptu wcześniej? Jak by to wyglądało? Można spróbować zrobić to w ten sposób:

Jak już widzicie, funkcja nazywa się filed_modify_global_name, czyli nam się to najpewniej nie uda. I wygląda to w ten sposób:

Definiujemy najpierw nazwę, na przykład imię “Mikołaj” i wypisujemy jego wartość przed wywołaniem funkcji. Następnie wywołujemy tę funkcję, która powinna przestawić tę zmienną na “Kuba”. Wypisujemy wartość wewnątrz funkcji i patrzymy, jaka jest po funkcji: czy się to zmieniło.

Przed funkcją jest „Mikołaj”, wewnątrz funkcji „Kuba” i po funkcji jest znowu „Mikołaj”. Wynika to z faktu, że jeżeli odwołujemy się do zmiennej, to Python zaczyna jej poszukiwać. Czyli najpierw szuka jej na obszarze lokalnym, następnie na obszarze globalnym i tak dalej.

Uwaga!

Jeżeli jesteśmy wewnątrz funkcji w kontekście lokalnym i Python nie znajdzie zmiennej w obszarze lokalnym i to wyrażenie, które my wykonujemy, to nie jest odczyt tej zmiennej, tylko to jest zapis do tej zmiennej, to Python nie będzie już dalej szukał tej zmiennej. Po prostu ją stworzy na obszarze lokalnym i stworzy zmienną o takiej samej nazwie. Czyli teraz są dwie zmienne o nazwie „name”, które odnoszą się do różnych wartości. W jednej jest ukryty Mikołaj, w drugiej jest ukryty „Kuba” i funkcjonują równocześnie, ponieważ jedna jest w zasięgu globalnym, a druga w zasięgu lokalnym. Powoduje to, że nazwa „name” (zmienną globalną) została przysłonięta i nie można jej niejako zmodyfikować. Ta modyfikacja się nie udała. Ta zmienna jest nietknięta.

To, że nie można modyfikować tej zmiennej na zewnątrz, jest w ogóle bardzo ważną właściwością.

Jeżeli byśmy natomiast chcieli to zrobić, to w Pythonie możemy wywołać funkcję, która zmodyfikuje tę zmienną, która jest na zewnątrz, mimo że funkcja istnieje w kontekście lokalnym. Należy tylko to Pythonowi wprost powiedzieć. Wtedy trzeba użyć jakiegoś kluczowego „global”, które mówi Pythonowi: Pythonie drogi, tej zmiennej, której „name” będziemy za chwilę używać, nie szukaj tu lokalnie, nie twórz jej lokalnie. Ona istnieje w kontekście globalnym. Weź ją sobie z tego tekstu globalnego i do niej będziemy się odwoływać. Będziemy ją zaraz modyfikować i wiemy, co robimy. To jest po to, żebyśmy powiedzieli wprost Pythonowi, że wiemy, co robimy.

Jak uruchomię ten kod, to jest przed funkcją Mikołaj, wewnątrz funkcji Kuba i po funkcji jest również Kuba. Czyli rzeczywiście ta zmienna została zmodyfikowana.

Pamiętajcie! Takie zachowanie jest zazwyczaj niechciane. Używanie tego rodzaju konstrukcji, w której definiujemy sobie zmienną globalną i potem ją modyfikujemy, jest czymś, czego nie chcemy mieć w kodzie. W ogóle stosowania zmiennych globalnych powinniśmy unikać.

Dlaczego?

Jeżeli program będzie bardzo złożony, albo choć średnio złożony, w którym jest dużo zmiennych globalnych, i w którym każdy kawałek programu może te zmienne globalne modyfikować, i co więcej – będzie je modyfikować, to my nigdy nie będziemy wiedzieć, co tam się tak naprawdę dzieje, która rzecz ma jaką wartość. Nie będzie takiego liniowego, ładnego przejścia przez algorytm, aby móc go przeanalizować, jako ludzie. Zamiast tego zmienne globalne będą się nadpisywać. Więc tak naprawdę stosowanie zmiennych globalnych należy ograniczyć tylko do jednego konkretnego przypadku.

Oczywiście są pewnie jakieś uzasadnione przypadki, gdzie można tego użyć, może jakieś proste skrypty itd. Ale lepiej tego nie praktykować. Warto, jako taką regułę bezpieczną, stosować to, że nie używamy zmiennych globalnych poza jednym konkretnym przypadkiem, (o tym za moment).

Do tego przypadku nie jest potrzebne słówko „global”. Sytuacja, w której konieczne jest użycie „global”, jest niezwykle rzadka. Jeśli już użyte jest „global”, to najpewniej można to zrobić ładniej i bezpieczniej. Na przykład wprost przekazując jakąś wartość, zamiast dodawać jakąś globalną stałą, która jest ogólnie niebezpieczna.

Przy małych programach można nad tym zapanować. Jednak programy mają to do siebie, że są rozbudowywane, a im większy program, tym łatwiej stracić kontrolę i napotkać sporo nieprzewidzianych efektów. No i, co się z tym wiąże, dużo problemów.

W jakiej sytuacji można używać zmiennych globalnych?

Otóż jest to sytuacja, kiedy mamy zmienną globalną jako stałą, przy której znajdują się jakieś rzeczy, zdefiniowane jako stałe.

Tutaj jest przykład z π (PI). W Pythonie π jest w pakiecie Math, więc nie trzeba go ustawiać. Po prostu można zaimportować π, czy jakąkolwiek inną stałą, która nie jest zdefiniowana w Pythonie.

Żeby zaznaczyć to, że jest to stała i nie powinno się jej modyfikować, należy wpisać ją wielkimi literami.

Jeżeli mamy jakąś stałą, której nie będziemy modyfikować, to może być ona globalna i można jej używać w różnych miejscach. To jest stała i będzie tylko do odczytu, więc nie ma tego ryzyka, że ktoś ją przestawi. Zdecydowanie nie należy tego robić. Jest to po prostu bezpieczne. To jest jakaś liczba, jakaś wartość. Jeśli została zdefiniowana i mamy taki „worek” z wartościami i jakąś konfiguracją, to można się do niego odwołać. Nie chcemy przekazywać tej wartości przez 6 wywołań funkcji w dół. Po prostu niech ona tam będzie globalna, a my sobie zaimportujemy tylko ten moduł globalny i będziemy mieli do tego dostęp.

To jest ten jeden przypadek, kiedy można sobie bezpiecznie użyć tych zmiennych globalnych. Na pewno są jeszcze jakieś takie przypadki brzegowe, że to też byłoby w porządku, nie chcemy być zero-jedynkowi. Ale jak zaczynamy przygodę z programowaniem, to dobrą regułą jest to, żeby jednak tego „globala” unikać.

Przykłady z kontekstem nonlocal.

Występuje w sytuacji, w której mamy funkcję wrapującą i funkcję wewnętrzną. I ta zmienna jest w kontekście do tej funkcji.

I tutaj będzie „nonlocal” dlatego, że działa analogicznie do global. One mają bardzo podobną relację względem siebie i zamiast „global” jest słówko kluczowe „nonlocal”. Pozwala zmodyfikować zmienną, zamiast przysłaniać ją na poziomie „enclosing”.
Przy czym znowu dokładnie ta sama historia jest z „global”. Definiowanie tutaj jest jak najbardziej w porządku. Przyjęcie jakiegoś argumentu czy jakiejś wartości, która będzie odczytywana poniżej, jest jak najbardziej w porządku, ale modyfikowanie tej wartości jest już kwestią dyskusyjną. Są jakieś przypadki, gdzie można by to robić, ale trzeba uważać, bo wprowadzamy sobie dużą złożoność i potencjalne ryzyko problemów naszego programu.

Zainteresowały Cię zasięgi zmiennych?

Wpis powstał na podstawie rozmowy z trenerem infoShare Academy
Mikołajem Lewandowskim.

Back to top button