Zakres blokowy w Pythonie

93

Kiedy kodujesz w innych językach, czasami tworzysz zakres blokowy, na przykład:

statement
...
statement
{
    statement
    ...
    statement
}
statement
...
statement

Jednym z celów (z wielu) jest poprawa czytelności kodu: pokazanie, że pewne instrukcje tworzą jednostkę logiczną lub że pewne zmienne lokalne są używane tylko w tym bloku.

Czy istnieje idiomatyczny sposób zrobienia tego samego w Pythonie?

Johan Råde
źródło
2
One purpose (of many) is to improve code readability- Kod Pythona, napisany poprawnie (tj. Zgodny z zen w Pythonie ) nie potrzebuje takiego ozdobnika, aby był czytelny. W rzeczywistości jest to jedna z (wielu) rzeczy, które lubię w Pythonie.
Burhan Khalid
Próbowałem grać __exit__i withoświadczać, zmieniając, globals()ale nie udało mi się.
Ruggero Turra
1
bardzo przydatne byłoby zdefiniowanie czasu życia zmiennej, związanej z pozyskiwaniem zasobów
Ruggero Turra
25
@BurhanKhalid: To nieprawda. Zen Pythona nie zapobiega zanieczyszczaniu lokalnego zasięgu tymczasową zmienną tu i ówdzie. Jeśli każde użycie pojedynczej zmiennej tymczasowej zamienisz na np. Zdefiniowanie funkcji zagnieżdżonej, która jest wywoływana natychmiast, zen Pythona też nie będzie zadowolony. Wyraźne ograniczenie zakresu zmiennej jest narzędziem poprawiającym czytelność, ponieważ bezpośrednio odpowiada „czy te identyfikatory są używane poniżej?” - pytanie, które może się pojawić podczas czytania nawet najbardziej eleganckiego kodu Pythona.
bluenote10
18
@BurhanKhalid Dobrze jest nie mieć funkcji. Ale nazywanie tego „zen” jest po prostu obrzydliwe.
Phil

Odpowiedzi:

81

Nie, nie ma obsługi języków do tworzenia zakresu blokowego.

Następujące konstrukcje tworzą zakres:

  • moduł
  • klasa
  • funkcja (w tym lambda)
  • wyrażenie generatora
  • rozumienia (dict, set, list (w Pythonie 3.x))
ThomasH
źródło
38

Idiomatycznym sposobem w Pythonie jest zachowanie krótkich funkcji. Jeśli myślisz, że tego potrzebujesz, zrefaktoryzuj swój kod! :)

Python tworzy nowy zakres dla każdego modułu, klasy, funkcji, wyrażenia generatora, rozumienia dyktowania i zestawu, a w Pythonie 3.x także dla każdego rozumienia listy. Poza tym w funkcjach nie ma zagnieżdżonych zakresów.

Sven Marnach
źródło
12
„Najważniejszą rzeczą w programowaniu jest możliwość nadania jakiejś nazwy. Drugą najważniejszą rzeczą jest to, aby nie wymagać nadawania nazwy”. W większości Python wymaga, aby zakresy (dla zmiennych itp.) Miały nazwy. Pod tym względem Python zmienia drugi najważniejszy test.
Krazy Glew
19
Najważniejszą rzeczą w programowaniu jest możliwość zarządzania zależnościami aplikacji i zarządzania zakresami bloków kodu. Anonimowe bloki pozwalają ograniczyć czas życia wywołań zwrotnych, podczas gdy w przeciwnym razie wywołania zwrotne są używane tylko raz, ale trwają przez cały czas trwania programu, co powoduje bałagan w zakresie globalnym i szkodzi czytelności kodu.
Dmitry
Właśnie zauważyłem, że zmienne są również lokalne, aby dyktować / ustawiać wyrażenia. Próbowałem Pythona 2.7 i 3.3, ale nie jestem pewien, czy jest to zależne od wersji.
wjandrea
1
@wjandrea Masz rację - dodano do listy. Nie powinno być żadnej różnicy między wersjami Pythona.
Sven Marnach
4
Chciałbym przeformułować ostatnie zdanie, ponieważ można bardzo dobrze tworzyć funkcje w funkcjach. Więc w funkcjach są zagnieżdżone zakresy.
ThomasH,
18

Możesz zrobić coś podobnego do zakresu blokowego C ++ w Pythonie, deklarując funkcję wewnątrz swojej funkcji, a następnie natychmiast ją wywołując. Na przykład:

def my_func():
    shared_variable = calculate_thing()

    def do_first_thing():
        ... = shared_variable
    do_first_thing()

    def do_second_thing():
        foo(shared_variable)
        ...
    do_second_thing()

Jeśli nie masz pewności, dlaczego chcesz to zrobić, to ten film może Cię przekonać.

Podstawową zasadą jest możliwie najściślejsze określanie zakresu wszystkiego bez wprowadzania jakichkolwiek „śmieci” (dodatkowych typów / funkcji) w szerszym zakresie niż jest to absolutnie wymagane - nic innego nie chce używać do_first_thing()metody, na przykład, więc nie należy jej ograniczać poza zakres funkcja wywoływania.

Ben
źródło
Jest to również sposób, który jest używany przez programistów Google w samouczkach TensorFlow, co widać na przykład tutaj
Nino Filiu
13

Zgadzam się, że nie ma zakresu blokowego. Ale jedno miejsce w Pythonie 3 sprawia, że ​​wygląda na to, że ma zasięg blokowy.

co się stało, co dało ten wygląd? To działało poprawnie w Pythonie 2, ale aby zatrzymać wyciek zmiennych w Pythonie 3, wykonali tę sztuczkę i ta zmiana sprawia, że ​​wygląda to tak, jakby miało tutaj zasięg blokowy.

Pozwól mi wyjaśnić.


Zgodnie z ideą zakresu, gdy wprowadzamy zmienne o takich samych nazwach w tym samym zakresie, należy zmodyfikować jego wartość.

to właśnie dzieje się w Pythonie 2

>>> x = 'OLD'
>>> sample = [x for x in 'NEW']
>>> x
'W'

Ale w Pythonie 3, mimo że zmienna o tej samej nazwie jest wprowadzona, nie zastępuje, rozumienie listy z jakiegoś powodu działa jak piaskownica i wydaje się, że tworzy w niej nowy zakres.

>>> x = 'OLD'
>>> sample = [x for x in 'NEW']
>>> x
'OLD'

a ta odpowiedź jest sprzeczna ze stwierdzeniem answerer @ Thomas . Jedynym sposobem na utworzenie zakresu są funkcje, klasy lub moduły, ponieważ wygląda to jak jedno inne miejsce tworzenia nowego zakresu.

Harish Kayarohanam
źródło
0

Moduły (i pakiety) to świetny sposób w Pythonie na podzielenie programu na oddzielne przestrzenie nazw, co wydaje się być niejawnym celem tego pytania. Rzeczywiście, gdy uczyłem się podstaw Pythona, byłem sfrustrowany brakiem funkcji zakresu blokowego. Jednak gdy zrozumiałem moduły Pythona, mogłem bardziej elegancko zrealizować swoje poprzednie cele bez potrzeby stosowania zakresu blokowego.

Jako motywację i aby skierować ludzi we właściwym kierunku, myślę, że warto podać wyraźne przykłady niektórych konstrukcji określania zakresu w Pythonie. Najpierw wyjaśnię moją nieudaną próbę użycia klas Pythona do zaimplementowania zakresu blokowego. Następnie wyjaśniam, w jaki sposób osiągnąłem coś bardziej użytecznego przy użyciu modułów Pythona. Na koniec przedstawiam praktyczne zastosowanie pakietów do ładowania i filtrowania danych.

Próba zakresu blokowego z klasami

Przez kilka chwil myślałem, że osiągnąłem zakres blokowy, wklejając kod wewnątrz deklaracji klasy:

x = 5
class BlockScopeAttempt:
    x = 10
    print(x) # Output: 10
print(x) # Output: 5

Niestety to się psuje, gdy funkcja jest zdefiniowana:

x = 5 
class BlockScopeAttempt: 
    x = 10
    print(x) # Output: 10
    def printx2(): 
        print(x) 
    printx2() # Output: 5!!!

Dzieje się tak, ponieważ funkcje zdefiniowane w klasie używają zasięgu globalnego. Najłatwiejszym (choć nie jedynym) sposobem rozwiązania tego problemu jest jawne określenie klasy:

x = 5 
class BlockScopeAttempt: 
    x = 10
    print(x) # Output: 10
    def printx2(): 
        print(BlockScopeAttempt.x)  # Added class name
    printx2() # Output: 10

Nie jest to takie eleganckie, ponieważ trzeba różnie pisać funkcje w zależności od tego, czy są zawarte w klasie.

Lepsze wyniki dzięki modułom Pythona

Moduły są bardzo podobne do klas statycznych, ale z mojego doświadczenia wynika, że ​​moduły są znacznie czystsze. Aby zrobić to samo z modułami, tworzę plik wywołany my_module.pyw bieżącym katalogu roboczym z następującą zawartością:

x = 10
print(x) # (A)

def printx():
    global x
    print(x) # (B)

Następnie w moim głównym pliku lub sesji interaktywnej (np. Jupyter) robię

x = 5
import my_module # Output: 10 from (A)
my_module.printx() # Output: 10 from (B)
print(x) # Output: 5

Jako wyjaśnienie, każdy plik Pythona definiuje moduł, który ma własną globalną przestrzeń nazw. Importowanie modułu umożliwia dostęp do zmiennych w tej przestrzeni nazw za pomocą .składni.

Jeśli pracujesz z modułami w sesji interaktywnej, możesz wykonać te dwie linie na początku

%load_ext autoreload
%autoreload 2

a moduły zostaną automatycznie załadowane ponownie, gdy odpowiednie pliki zostaną zmodyfikowane.

Pakiety do ładowania i filtrowania danych

Idea pakietów jest niewielkim rozszerzeniem koncepcji modułów. Pakiet to katalog zawierający (prawdopodobnie pusty) __init__.pyplik, który jest wykonywany podczas importu. Dostęp do modułów / pakietów w tym katalogu można uzyskać za pomocą .składni.

Do analizy danych często potrzebuję odczytać duży plik danych, a następnie interaktywnie zastosować różne filtry. Odczytanie pliku zajmuje kilka minut, więc chcę to zrobić tylko raz. Na podstawie tego, czego nauczyłem się w szkole o programowaniu obiektowym, uważałem, że kod do filtrowania i ładowania należy pisać jako metody w klasie. Główną wadą tego podejścia jest to, że jeśli następnie ponownie zdefiniuję moje filtry, definicja mojej klasy ulegnie zmianie, więc muszę ponownie załadować całą klasę, w tym dane.

Obecnie w Pythonie definiuję pakiet o nazwie, my_dataktóry zawiera podmoduły o nazwach loadi filter. Wewnątrz filter.pymogę zrobić względny import:

from .load import raw_data

Jeśli zmodyfikuję filter.py, to autoreloadwykryję zmiany. Nie ładuje się ponownie load.py, więc nie muszę ponownie ładować moich danych. W ten sposób mogę prototypować mój kod filtrujący w notatniku Jupyter, zawijać go jako funkcję, a następnie wycinać i wklejać z mojego notatnika bezpośrednio do filter.py. Zrozumienie tego zrewolucjonizowało mój przepływ pracy i przekształciło mnie ze sceptyka w wierzącego w „Zen Pythona”.

Ben Mares
źródło