Blokir ruang lingkup dengan Python

94

Saat Anda membuat kode dalam bahasa lain, terkadang Anda akan membuat cakupan blok, seperti ini:

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

Salah satu tujuan (dari banyak) adalah untuk meningkatkan keterbacaan kode: untuk menunjukkan bahwa pernyataan tertentu membentuk unit logis atau variabel lokal tertentu hanya digunakan di blok itu.

Apakah ada cara idiomatik untuk melakukan hal yang sama dengan Python?

Johan Råde
sumber
2
One purpose (of many) is to improve code readability- Kode Python, ditulis dengan benar (yaitu, mengikuti zen python ) tidak perlu hiasan seperti itu agar dapat dibaca. Faktanya, ini adalah salah satu dari (banyak) hal yang saya suka tentang Python.
Burhan Khalid
Saya telah mencoba untuk bermain dengan __exit__dan withpernyataan, mengubah globals()tetapi saya gagal.
Ruggero Turra
1
akan sangat berguna untuk menentukan umur variabel, yang terkait dengan akuisisi sumber daya
Ruggero Turra
25
@BurhanKhalid: Itu tidak benar. Zen Python tidak mencegah Anda mencemari lingkup lokal dengan variabel sementara di sana-sini. Jika Anda mengubah setiap penggunaan variabel sementara tunggal menjadi misalnya mendefinisikan fungsi bersarang yang dipanggil segera, zen Python juga tidak akan senang. Membatasi secara eksplisit cakupan variabel adalah alat untuk meningkatkan keterbacaan, karena secara langsung menjawab "apakah pengenal ini digunakan di bawah?" - pertanyaan yang bisa muncul saat membaca kode Python yang paling elegan sekalipun.
bluenote10
18
@BurhanKhalid Tidak masalah jika tidak memiliki fitur. Tapi menyebut "zen" itu hanya menjijikkan.
Phil

Jawaban:

83

Tidak, tidak ada dukungan bahasa untuk membuat cakupan blok.

Konstruksi berikut membuat ruang lingkup:

  • modul
  • kelas
  • fungsi (termasuk lambda)
  • ekspresi generator
  • pemahaman (dict, set, list (dengan Python 3.x))
ThomasH
sumber
38

Cara idiomatik dalam Python adalah dengan membuat fungsi Anda tetap pendek. Jika Anda merasa membutuhkan ini, lakukan refactor kode Anda! :)

Python membuat ruang lingkup baru untuk setiap modul, kelas, fungsi, ekspresi generator, pemahaman dikt, pemahaman set dan di Python 3.x juga untuk setiap pemahaman daftar. Selain itu, tidak ada cakupan bersarang di dalam fungsi.

Sven Marnach
sumber
12
"Hal terpenting dalam pemrograman adalah kemampuan untuk memberi nama pada sesuatu. Hal terpenting kedua adalah tidak diminta untuk memberi nama pada sesuatu." Untuk sebagian besar, Python mengharuskan lingkup (untuk variabel, dll.) Diberi nama. Dalam hal ini, variabel Python adalah tes terpenting kedua.
Krazy Glew
19
Hal terpenting dalam pemrograman adalah kemampuan untuk mengelola dependensi aplikasi Anda dan mengelola cakupan blok kode. Pemblokiran anonim memungkinkan Anda membatasi masa aktif callback, sedangkan jika tidak, callback Anda hanya digunakan satu kali, tetapi aktif selama program berlangsung, hal ini menyebabkan kekacauan cakupan global dan merusak keterbacaan kode.
Dmitry
Saya baru saja memperhatikan bahwa variabel juga lokal untuk dikt / set pemahaman. Saya mencoba Python 2.7 dan 3.3, tetapi saya tidak yakin apakah itu tergantung pada versinya.
wjandrea
1
@wjandrea Anda benar - ditambahkan ke daftar. Seharusnya tidak ada perbedaan antara versi Python untuk ini.
Sven Marnach
4
Saya akan mengulang kalimat terakhir, karena Anda dapat membuat fungsi dengan sangat baik di dalam fungsi. Jadi ada cakupan bersarang di dalam fungsi.
ThomasH
19

Anda dapat melakukan sesuatu yang mirip dengan cakupan blok C ++ dengan Python dengan mendeklarasikan fungsi di dalam fungsi Anda dan kemudian segera memanggilnya. Sebagai contoh:

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()

Jika Anda tidak yakin mengapa Anda ingin melakukan ini, maka video ini mungkin bisa meyakinkan Anda.

Prinsip dasarnya adalah untuk mencakup semuanya seketat mungkin tanpa memasukkan 'sampah' (jenis / fungsi tambahan) ke dalam cakupan yang lebih luas daripada yang benar-benar diperlukan - Tidak ada orang lain yang ingin menggunakan do_first_thing()metode tersebut misalnya sehingga tidak boleh tercakup di luar fungsi panggilan.

Ben
sumber
Ini juga cara yang digunakan oleh pengembang Google dalam tutorial TensorFlow, seperti yang terlihat misalnya di sini
Nino Filiu
13

Saya setuju bahwa tidak ada cakupan blok. Tapi satu tempat di python 3 membuatnya Tampak seolah-olah memiliki lingkup blok.

apa yang terjadi yang memberikan tampilan ini? Ini bekerja dengan baik di python 2. tetapi untuk membuat variabel kebocoran berhenti di python 3 mereka telah melakukan trik ini dan perubahan ini membuatnya tampak seolah-olah memiliki lingkup blok di sini.

Biar saya jelaskan.


Sesuai dengan gagasan ruang lingkup, ketika kami memperkenalkan variabel dengan nama yang sama di dalam lingkup yang sama, nilainya harus dimodifikasi.

inilah yang terjadi di python 2

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

Tetapi di python 3 meskipun variabel dengan nama yang sama diperkenalkan itu tidak menimpa, pemahaman daftar bertindak seperti kotak pasir untuk beberapa alasan dan sepertinya membuat ruang lingkup baru di dalamnya.

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

dan jawaban ini bertentangan dengan pernyataan penjawab @ Thomas. Satu-satunya cara untuk membuat ruang lingkup adalah fungsi, kelas, atau modul karena ini terlihat seperti satu tempat lain untuk membuat ruang lingkup baru.

Harish Kayarohanam
sumber
0

Modul (dan paket) adalah cara Pythonic yang bagus untuk membagi program Anda menjadi ruang nama terpisah, yang tampaknya menjadi tujuan implisit dari pertanyaan ini. Memang, saat saya mempelajari dasar-dasar Python, saya merasa frustrasi dengan kurangnya fitur cakupan blok. Namun begitu saya memahami modul Python, saya dapat dengan lebih elegan mewujudkan tujuan saya sebelumnya tanpa perlu cakupan blok.

Sebagai motivasi, dan untuk mengarahkan orang ke arah yang benar, menurut saya akan berguna untuk memberikan contoh eksplisit dari beberapa konstruksi pelingkupan Python. Pertama saya menjelaskan upaya saya yang gagal dalam menggunakan kelas Python untuk mengimplementasikan lingkup blok. Selanjutnya saya menjelaskan bagaimana saya mencapai sesuatu yang lebih berguna menggunakan modul Python. Di akhir saya menguraikan aplikasi praktis dari paket untuk memuat dan memfilter data.

Mencoba lingkup blok dengan kelas

Untuk beberapa saat saya berpikir bahwa saya telah mencapai lingkup blok dengan menempelkan kode di dalam deklarasi kelas:

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

Sayangnya ini rusak ketika suatu fungsi didefinisikan:

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

Itu karena fungsi yang didefinisikan dalam kelas menggunakan cakupan global. Cara termudah (meskipun bukan satu-satunya) untuk memperbaikinya adalah dengan menentukan kelas secara eksplisit:

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

Ini tidak begitu elegan karena seseorang harus menulis fungsi secara berbeda tergantung pada apakah mereka dimuat dalam sebuah kelas atau tidak.

Hasil yang lebih baik dengan modul Python

Modul sangat mirip dengan kelas statis, tetapi modul jauh lebih bersih menurut pengalaman saya. Untuk melakukan hal yang sama dengan modul, saya membuat file yang dipanggil my_module.pydi direktori kerja saat ini dengan konten berikut:

x = 10
print(x) # (A)

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

Kemudian di file utama saya atau sesi interaktif (misalnya Jupyter), saya lakukan

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

Sebagai penjelasan, setiap file Python mendefinisikan modul yang memiliki namespace global sendiri. Mengimpor modul memungkinkan Anda mengakses variabel di namespace ini dengan .sintaks.

Jika Anda bekerja dengan modul dalam sesi interaktif, Anda dapat mengeksekusi dua baris ini di awal

%load_ext autoreload
%autoreload 2

dan modul akan dimuat ulang secara otomatis saat file terkait diubah.

Paket untuk memuat dan memfilter data

Ide paket adalah sedikit perluasan dari konsep modul. Paket adalah direktori yang berisi file (mungkin kosong) __init__.py, yang dijalankan saat diimpor. Modul / paket dalam direktori ini dapat diakses dengan .sintaks.

Untuk analisis data, saya sering kali perlu membaca file data besar dan kemudian secara interaktif menerapkan berbagai filter. Membaca file membutuhkan waktu beberapa menit, jadi saya hanya ingin melakukannya sekali. Berdasarkan apa yang saya pelajari di sekolah tentang pemrograman berorientasi objek, saya dulu percaya bahwa seseorang harus menulis kode untuk memfilter dan memuat sebagai metode di kelas. Kerugian utama dari pendekatan ini adalah jika saya kemudian mendefinisikan ulang filter saya, definisi kelas saya berubah, jadi saya harus memuat ulang seluruh kelas, termasuk datanya.

Saat ini dengan Python, saya mendefinisikan sebuah paket bernama my_datayang berisi submodul bernama loaddan filter. Di dalam filter.pyI dapat melakukan impor relatif:

from .load import raw_data

Jika saya memodifikasi filter.py, maka autoreloadakan mendeteksi perubahan. Itu tidak memuat ulangload.py , jadi saya tidak perlu memuat ulang data saya. Dengan cara ini saya dapat membuat prototipe kode pemfilteran saya di notebook Jupyter, membungkusnya sebagai fungsi, dan kemudian memotong-tempel dari notebook saya langsung ke filter.py. Mengetahui hal ini merevolusi alur kerja saya, dan mengubah saya dari seorang yang skeptis menjadi penganut “Zen of Python”.

Ben Mares
sumber