Phạm vi khối trong Python


93

Khi bạn viết mã bằng các ngôn ngữ khác, đôi khi bạn sẽ tạo một phạm vi khối, như sau:

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

Một mục đích (trong nhiều mục đích) là cải thiện khả năng đọc mã: để chỉ ra rằng một số câu lệnh nhất định tạo thành một đơn vị logic hoặc một số biến cục bộ nhất định chỉ được sử dụng trong khối đó.

Có một cách thành ngữ để làm điều tương tự trong Python không?


2
One purpose (of many) is to improve code readability- Mã Python, được viết chính xác (tức là theo zen của python ) sẽ không cần trang trí như vậy để có thể đọc được. Trên thực tế, nó là một trong (nhiều) điều tôi thích về Python.
Burhan Khalid

Tôi đã cố gắng chơi với __exit__withtuyên bố, thay đổi globals()nhưng tôi đã thất bại.
Ruggero Turra

1
nó sẽ rất hữu ích để xác định thời gian tồn tại biến đổi, được kết nối với việc thu thập tài nguyên
Ruggero Turra

25
@BurhanKhalid: Điều đó không đúng. Zen của Python không ngăn bạn làm ô nhiễm phạm vi cục bộ với một biến tạm thời ở đây và ở đó. Nếu bạn biến mọi cách sử dụng của một biến tạm thời đơn lẻ thành ví dụ xác định một hàm lồng nhau được gọi ngay lập tức, zen của Python cũng sẽ không vui. Giới hạn rõ ràng phạm vi của một biến công cụ để cải thiện khả năng đọc, bởi vì nó trực tiếp trả lời "những số nhận dạng này có được sử dụng bên dưới không?" - một câu hỏi có thể nảy sinh khi đọc ngay cả mã Python thanh lịch nhất.
bluenote10

18
@BurhanKhalid Không có tính năng cũng được. Nhưng gọi đó là "zen" chỉ là kinh tởm.
Phil

Câu trả lời:


81

Không, không có hỗ trợ ngôn ngữ để tạo phạm vi khối.

Các cấu trúc sau tạo phạm vi:

  • mô-đun
  • lớp học
  • hàm (bao gồm lambda)
  • biểu thức máy phát điện
  • hiểu (dict, set, list (trong Python 3.x))

38

Cách thành ngữ trong Python là giữ cho các hàm của bạn ngắn gọn. Nếu bạn nghĩ rằng bạn cần điều này, hãy cấu trúc lại mã của bạn! :)

Python tạo ra một phạm vi mới cho mỗi mô-đun, lớp, hàm, biểu thức trình tạo, hiểu chính tả, hiểu thiết lập và trong Python 3.x cũng cho mỗi hiểu danh sách. Ngoài những điều này, không có phạm vi lồng nhau nào bên trong các hàm.


12
"Điều quan trọng nhất trong lập trình là khả năng đặt tên cho một thứ gì đó. Điều quan trọng thứ hai là không bị yêu cầu đặt tên cho một thứ gì đó." Đối với hầu hết các phần, Python yêu cầu các phạm vi (đối với các biến, v.v.) phải được đặt tên. Về mặt này, Python biến thử nghiệm quan trọng thứ hai.
Krazy Glew

19
Điều quan trọng nhất trong lập trình là khả năng quản lý các phần phụ thuộc của ứng dụng của bạn và quản lý phạm vi của các khối mã. Các khối ẩn danh cho phép bạn giới hạn thời gian tồn tại của các lệnh gọi lại, ngược lại, các lệnh gọi lại của bạn chỉ được sử dụng một lần, nhưng tồn tại trong suốt thời gian của chương trình, điều này gây ra sự lộn xộn trong phạm vi toàn cầu và gây hại cho khả năng đọc của mã.
Dmitry

Tôi chỉ nhận thấy rằng các biến cũng là cục bộ để đọc / thiết lập khả năng hiểu. Tôi đã thử Python 2.7 và 3.3, nhưng tôi không chắc liệu nó có phụ thuộc vào phiên bản hay không.
wjandrea

1
@wjandrea Bạn đã đúng - đã thêm vào danh sách. Không nên có bất kỳ sự khác biệt nào giữa các phiên bản Python cho những thứ này.
Sven Marnach,

4
Tôi sẽ nói lại câu cuối cùng, vì bạn rất có thể tạo các hàm trong các hàm. Vì vậy, có các phạm vi lồng nhau bên trong các hàm.
ThomasH

18

Bạn có thể làm điều gì đó tương tự như phạm vi khối C ++ trong Python bằng cách khai báo một hàm bên trong hàm của bạn và sau đó ngay lập tức gọi nó. Ví dụ:

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

Nếu bạn không chắc tại sao bạn có thể muốn làm điều này thì video này có thể thuyết phục bạn.

Nguyên tắc cơ bản là phạm vi mọi thứ chặt chẽ nhất có thể mà không đưa bất kỳ 'rác' nào (các loại / chức năng bổ sung) vào một phạm vi rộng hơn mức hoàn toàn bắt buộc - Không có gì khác muốn sử dụng do_first_thing()phương pháp này, vì vậy nó không nên phạm vi bên ngoài chức năng gọi điện.


Đó cũng là cách mà đang được sử dụng bởi các nhà phát triển Google trong hướng dẫn TensorFlow, như đã thấy ví dụ ở đây
Nino Filiu

13

Tôi đồng ý rằng không có phạm vi khối. Nhưng một nơi trong python 3 làm cho nó SEEM như thể nó có phạm vi khối.

điều gì đã xảy ra mà cho cái nhìn này? Điều này đã hoạt động bình thường trong python 2. nhưng để ngăn rò rỉ biến trong python 3, họ đã thực hiện thủ thuật này và thay đổi này làm cho nó trông giống như thể nó có phạm vi khối ở đây.

Hãy để tôi giải thích.


Theo ý tưởng về phạm vi, khi chúng tôi giới thiệu các biến có cùng tên trong cùng phạm vi, giá trị của nó nên được sửa đổi.

đây là những gì đang xảy ra trong python 2

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

Nhưng trong python 3, mặc dù biến có cùng tên được giới thiệu, nó không ghi đè, vì lý do nào đó, khả năng hiểu danh sách hoạt động giống như một hộp cát và có vẻ như tạo một phạm vi mới trong đó.

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

và câu trả lời này đi ngược lại với câu trả lời của người trả lời @ Thomas. Phương tiện duy nhất để tạo phạm vi là các hàm, lớp hoặc mô-đun vì điều này trông giống như một nơi khác để tạo một phạm vi mới.


0

Mô-đun (và gói) là một cách tuyệt vời của Pythonic để chia chương trình của bạn thành các không gian tên riêng biệt, đây dường như là một mục tiêu ngầm của câu hỏi này. Thật vậy, khi tôi đang học những kiến ​​thức cơ bản về Python, tôi cảm thấy thất vọng vì thiếu tính năng phạm vi khối. Tuy nhiên, một khi tôi hiểu các mô-đun Python, tôi có thể thực hiện các mục tiêu trước đây của mình một cách thanh lịch hơn mà không cần đến phạm vi khối.

Với vai trò là động lực và để hướng mọi người đi đúng hướng, tôi nghĩ sẽ hữu ích khi đưa ra các ví dụ rõ ràng về một số cấu trúc phạm vi của Python. Đầu tiên, tôi giải thích nỗ lực thất bại của mình trong việc sử dụng các lớp Python để triển khai phạm vi khối. Tiếp theo, tôi giải thích cách tôi đạt được điều gì đó hữu ích hơn bằng cách sử dụng các mô-đun Python. Ở phần cuối, tôi phác thảo một ứng dụng thực tế của các gói để tải và lọc dữ liệu.

Đang thử phạm vi khối với các lớp

Trong một vài khoảnh khắc, tôi nghĩ rằng tôi đã đạt được phạm vi khối bằng cách dán mã bên trong khai báo lớp:

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

Thật không may, điều này bị hỏng khi một hàm được xác định:

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

Đó là bởi vì các hàm được định nghĩa trong một lớp sử dụng phạm vi toàn cầu. Cách dễ nhất (mặc dù không phải là duy nhất) để khắc phục điều này là chỉ định rõ ràng lớp:

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

Điều này không quá thanh lịch vì người ta phải viết các hàm khác nhau tùy thuộc vào việc chúng có chứa trong một lớp hay không.

Kết quả tốt hơn với các mô-đun Python

Mô-đun rất giống với các lớp tĩnh, nhưng theo kinh nghiệm của tôi, các mô-đun sạch hơn nhiều. Để làm tương tự với các mô-đun, tôi tạo một tệp được gọi my_module.pytrong thư mục làm việc hiện tại với nội dung sau:

x = 10
print(x) # (A)

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

Sau đó, trong tệp chính hoặc phiên tương tác (ví dụ: Jupyter), tôi

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

Theo giải thích, mỗi tệp Python xác định một mô-đun có không gian tên chung của riêng nó. Nhập một mô-đun cho phép bạn truy cập các biến trong không gian tên này bằng .cú pháp.

Nếu bạn đang làm việc với các mô-đun trong một phiên tương tác, bạn có thể thực hiện hai dòng này ở đầu

%load_ext autoreload
%autoreload 2

và các mô-đun sẽ được tự động tải lại khi các tệp tương ứng của chúng được sửa đổi.

Các gói để tải và lọc dữ liệu

Ý tưởng về các gói là một phần mở rộng nhẹ của khái niệm mô-đun. Gói là một thư mục chứa __init__.pytệp (có thể trống) , tệp này được thực thi khi nhập. Các mô-đun / gói trong thư mục này có thể được truy cập bằng .cú pháp.

Để phân tích dữ liệu, tôi thường cần đọc một tệp dữ liệu lớn và sau đó áp dụng các bộ lọc khác nhau một cách tương tác. Đọc một tập tin mất vài phút, vì vậy tôi chỉ muốn làm điều đó một lần. Dựa trên những gì tôi học được ở trường về lập trình hướng đối tượng, tôi từng tin rằng người ta nên viết mã để lọc và tải dưới dạng các phương thức trong một lớp. Một nhược điểm lớn của phương pháp này là nếu sau đó tôi xác định lại các bộ lọc của mình, định nghĩa về lớp của tôi sẽ thay đổi, vì vậy tôi phải tải lại toàn bộ lớp, bao gồm cả dữ liệu.

Ngày nay với Python, tôi định nghĩa một gói được gọi là gói my_datachứa các mô-đun con có tên loadfilter. Inside of filter.pyI có thể thực hiện nhập tương đối:

from .load import raw_data

Nếu tôi sửa đổi filter.py, sau đó autoreloadsẽ phát hiện các thay đổi. Nó không tải lại load.py, vì vậy tôi không cần tải lại dữ liệu của mình. Bằng cách này, tôi có thể tạo nguyên mẫu mã lọc của mình trong sổ ghi chép Jupyter, bọc nó dưới dạng một hàm, sau đó cắt dán trực tiếp từ sổ ghi chép của tôi vào filter.py. Việc tìm ra điều này đã tạo ra một cuộc cách mạng trong quy trình làm việc của tôi và chuyển đổi tôi từ một người hoài nghi thành một người tin tưởng vào “Zen của Python”.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.