2008-11-15 38 views
346

Điều gì chính xác là các quy tắc phạm vi Python?Mô tả ngắn gọn về các quy tắc phạm vi?

Nếu tôi có một số mã:

code1 
class Foo: 
    code2 
    def spam..... 
     code3 
     for code4..: 
     code5 
     x() 

đâu x tìm thấy? Một số lựa chọn có thể bao gồm các danh sách trên:

  1. Trong tập tin nguồn kèm theo
  2. Trong không gian tên lớp
  3. Trong định nghĩa hàm
  4. Trong cho chỉ số vòng lặp biến
  5. Bên trong vòng lặp for

Cũng có ngữ cảnh trong quá trình thực thi, khi chức năng spam được chuyển đến một nơi khác. Và các hàm lambda có thể khác đi một chút không?

Phải có một tham chiếu hoặc thuật toán đơn giản ở đâu đó. Đó là một thế giới khó hiểu cho những người lập trình Python trung gian.

+2

người có thể ngừng làm ít "chỉnh sửa nghiệp" cho câu hỏi này xin vui lòng? –

+2

Chúng không phải là tất cả "chỉnh sửa nghiệp". Một số là vì mọi người không hiểu cách mọi thứ hoạt động. Xem [** _ Câu hỏi có nên bao gồm “thẻ” trong tiêu đề của chúng không? _ **] (https://meta.stackexchange.com/questions/19190/should-questions-include-tags-in-their-titles). – martineau

Trả lời

313

Thực ra, một quy tắc ngắn gọn cho độ phân giải Phạm vi Python, từ Learning Python, 3rd. Ed.. (Các quy tắc này dành riêng cho tên biến, chứ không phải thuộc tính. Nếu bạn tham chiếu không có dấu chấm thì các quy tắc này sẽ áp dụng)

Quy tắc LEGB.

L, Địa phương - Tên được gán theo bất kỳ cách nào trong một hàm (def hoặc lambda)) và không được khai báo chung trong hàm đó.

E, Địa phương bao gồm chức năng - Tên trong phạm vi địa phương của bất kỳ và tất cả các hàm kèm theo tĩnh (def hoặc lambda), từ trong ra ngoài.

G, Global (mô-đun) - Tên giao ở cấp cao nhất của một tập tin mô-đun, hoặc bằng cách thực hiện một tuyên bố global trong một def bên trong file.

B, Built-in (Python) - Tên gán trước ở được xây dựng trong mô-đun tên: open, range, SyntaxError ...

Vì vậy, trong trường hợp của

code1 
class Foo: 
    code2 
    def spam..... 
     code3 
     for code4..: 
     code5 
     x() 

Vòng lặp for không có vùng tên riêng. Theo thứ tự LEGB, phạm vi sẽ là

L: địa phương, trong def spam (trong code3, code 4, code5).

E: Kèm theo chức năng, bất kỳ chức năng kèm theo (nếu toàn bộ ví dụ là trong def khác)

G: Toàn cầu. Có bất kỳ x nào được khai báo trên toàn cầu trong mô-đun (code1) không?

B: Bất kỳ nội dung dựng sẵn nào x bằng Python.

x sẽ không bao giờ được tìm thấy trong code2 (ngay cả trong trường hợp bạn có thể mong đợi, xem Antti's answer hoặc here).

+1

U không bao gồm phạm vi của 'code2'. Đó không phải là một biến và là một thuộc tính class/instance. Có đúng không. –

+1

trực giác của tôi nói rằng code2 tồn tại bên trong phạm vi lớp của 'Foo' và có thể được truy cập từ bất kỳ phạm vi nào có quyền truy cập vào' Foo' với 'Foo.code2' hoặc từ trong' Foo' với cùng cú pháp. Nhưng tôi không chắc chắn nơi mà nằm trong LEGB nhớ. –

+30

Như là một báo trước cho truy cập toàn cầu - đọc một biến toàn cầu có thể xảy ra mà không khai báo rõ ràng, nhưng ghi vào nó mà không khai báo toàn cục (var_name) sẽ tạo một cá thể cục bộ mới. –

8

Python giải quyết các biến của bạn với - thông thường - ba không gian tên có sẵn.

Bất cứ lúc nào trong quá trình thực hiện, có ít nhất ba phạm vi lồng nhau mà namespace là truy cập trực tiếp: phạm vi trong cùng, mà là tìm kiếm đầu tiên, bao gồm các tên địa phương; các không gian tên của bất kỳ chức năng kèm theo nào, được tìm kiếm bắt đầu bằng phạm vi bao quanh gần nhất; giữa phạm vi , được tìm kiếm tiếp theo, chứa tên toàn cục của mô-đun hiện tại; và phạm vi ngoài cùng của (tìm kiếm lần cuối) là không gian tên có chứa tên được tích hợp sẵn.

Có hai chức năng: globalslocals hiển thị cho bạn nội dung hai trong số các không gian tên này.

Không gian tên được tạo bởi gói, mô-đun, lớp, đối tượng và chức năng. Không có bất kỳ hương vị nào khác của không gian tên.

Trong trường hợp này, lệnh gọi hàm có tên x phải được giải quyết trong không gian tên cục bộ hoặc không gian tên chung.

Địa phương trong trường hợp này, là phần thân của hàm phương thức Foo.spam.

Toàn cầu là - toàn cầu.

Quy tắc là tìm kiếm không gian cục bộ lồng nhau được tạo bởi hàm phương thức (và định nghĩa hàm lồng nhau), sau đó tìm kiếm toàn cục. Đó là nó.

Không có phạm vi nào khác. Câu lệnh for (và các câu lệnh ghép khác như iftry) không tạo phạm vi lồng nhau mới. Chỉ định nghĩa (gói, mô-đun, hàm, lớp và đối tượng đối tượng).)

Bên trong định nghĩa lớp, tên là một phần của không gian tên lớp. Ví dụ: code2 phải đủ điều kiện theo tên lớp. Thường là Foo.code2. Tuy nhiên, self.code2 cũng sẽ hoạt động vì các đối tượng Python nhìn vào lớp có chứa như là một sự trở lại.

Một đối tượng (một thể hiện của một lớp) có các biến mẫu. Những tên này nằm trong không gian tên của đối tượng. Họ phải có đủ điều kiện của đối tượng. (variable.instance.)

Từ trong một phương thức lớp học, bạn có người dân địa phương và hình cầu. Bạn nói self.variable để chọn cá thể làm không gian tên. Bạn sẽ lưu ý rằng self là một đối số cho mọi hàm thành viên của lớp, làm cho nó trở thành một phần của không gian tên cục bộ.

Xem Python Scope Rules, Python Scope, Variable Scope.

+4

Đây là lỗi thời. Kể từ 2.1 (7 năm trước), có nhiều hơn hai phạm vi, vì các hàm lồng nhau giới thiệu các phạm vi mới, do đó một hàm trong một hàm sẽ có quyền truy cập vào phạm vi cục bộ, phạm vi chức năng kèm theo và phạm vi toàn cục (cũng là nội trang). – Brian

+0

Tôi xin lỗi, điều này không còn xảy ra. 'Python có hai không gian tên có sẵn. Toàn cầu và địa phương-to-something.' –

5

X tìm thấy ở đâu?

x không được tìm thấy như bạn chưa xác định. :-) Nó có thể được tìm thấy trong code1 (global) hoặc code3 (local) nếu bạn đặt nó ở đó.

code2 (thành viên của lớp) không hiển thị với mã bên trong các phương thức của cùng một lớp - bạn thường sẽ truy cập chúng bằng cách sử dụng tự. code4/code5 (vòng) sống trong phạm vi tương tự như code3, vì vậy nếu bạn đã viết cho x trong đó bạn sẽ thay đổi x dụ được định nghĩa trong code3, không làm cho một x mới.

Python bị scoped tĩnh, vì vậy nếu bạn chuyển 'spam' sang một hàm khác, spam sẽ vẫn có quyền truy cập vào phần tử trong mô-đun từ (được xác định trong mã 1) và bất kỳ phạm vi nào có chứa khác (xem bên dưới). thành viên code2 một lần nữa sẽ được truy cập thông qua bản thân.

lambda không khác gì def. Nếu bạn có một lambda được sử dụng bên trong một hàm, nó giống như định nghĩa một hàm lồng nhau. Trong Python 2.2 trở đi, phạm vi lồng nhau có sẵn. Trong trường hợp này bạn có thể ràng buộc x ở bất kỳ mức độ chức năng làm tổ và Python sẽ nhặt dụ trong cùng:

x= 0 
def fun1(): 
    x= 1 
    def fun2(): 
     x= 2 
     def fun3(): 
      return x 
     return fun3() 
    return fun2() 
print fun1(), x 

2 0 

fun3 thấy dụ x từ phạm vi chứa gần nhất, đó là phạm vi chức năng liên quan đến fun2. Nhưng các trường hợp x khác, được định nghĩa trong fun1 và trên toàn cầu, không bị ảnh hưởng.

Trước khi nested_scopes - bằng Python pre-2.1 và trong 2.1, trừ khi bạn yêu cầu cụ thể đối tượng địa lý sử dụng phạm vi từ tương lai - nhập - fun1 và fun2 không hiển thị cho fun3, do đó câu trả lời của S.Lott và bạn sẽ lấy toàn cầu x:

0 0 
20

Quy tắc phạm vi cho Python 2.x đã được nêu trong các câu trả lời khác. Điều duy nhất tôi sẽ thêm vào là trong Python 3.0, đó cũng là khái niệm về một phạm vi phi địa phương (được chỉ ra bởi từ khóa 'nonlocal'). Điều này cho phép bạn truy cập trực tiếp các phạm vi bên ngoài và mở ra khả năng thực hiện một số thủ thuật gọn gàng, bao gồm cả việc đóng cửa từ vựng (không có các hacks xấu xí liên quan đến các đối tượng có thể thay đổi).

EDIT: Đây là PEP có thêm thông tin về điều này.

128

Về cơ bản, điều duy nhất trong Python giới thiệu phạm vi mới là định nghĩa hàm. Các lớp là một trường hợp đặc biệt trong bất kỳ thứ gì được định nghĩa trực tiếp trong cơ thể được đặt trong không gian tên của lớp, nhưng chúng không thể truy cập trực tiếp từ bên trong các phương thức (hoặc các lớp lồng nhau) mà chúng chứa.

Trong ví dụ của bạn chỉ có 3 phạm vi trong đó x sẽ được tìm kiếm trong: phạm vi

  • spam - chứa tất cả mọi thứ được định nghĩa trong code3 và code5 (cũng như code4, biến vòng lặp của bạn)

  • Phạm vi toàn cầu - chứa mọi thứ được xác định trong mã 1, cũng như Foo (và bất kỳ thay đổi nào sau đó)

  • Không gian tên được dựng sẵn. Một chút của một trường hợp đặc biệt - nó chứa các hàm và các hàm dựng sẵn Python khác nhau như len() và str().Nói chung, điều này không nên được sửa đổi bởi bất kỳ mã người dùng nào, vì vậy, hy vọng nó sẽ chứa các hàm chuẩn và không có gì khác.

Phạm vi khác chỉ xuất hiện khi bạn giới thiệu hàm lồng nhau (hoặc lambda) vào hình ảnh. Chúng sẽ hoạt động khá nhiều như bạn mong đợi. Hàm lồng nhau có thể truy cập mọi thứ trong phạm vi cục bộ, cũng như bất kỳ thứ gì trong phạm vi của hàm kèm theo. ví dụ.

def foo(): 
    x=4 
    def bar(): 
     print x # Accesses x from foo's scope 
    bar() # Prints 4 
    x=5 
    bar() # Prints 5 

Hạn chế:

biến trong phạm vi khác hơn là biến chức năng địa phương có thể được truy cập, nhưng không thể hồi phục các thông số mới mà không cú pháp hơn nữa. Thay vào đó, nhiệm vụ sẽ tạo một biến số cục bộ thay vì ảnh hưởng đến biến trong phạm vi gốc. Ví dụ:

global_var1 = [] 
global_var2 = 1 

def func(): 
    # This is OK: It's just accessing, not rebinding 
    global_var1.append(4) 

    # This won't affect global_var2. Instead it creates a new variable 
    global_var2 = 2 

    local1 = 4 
    def embedded_func(): 
     # Again, this doen't affect func's local1 variable. It creates a 
     # new local variable also called local1 instead. 
     local1 = 5 
     print local1 

    embedded_func() # Prints 5 
    print local1 # Prints 4 

Để thực sự sửa đổi các ràng buộc của biến toàn cầu từ trong phạm vi hàm, bạn cần xác định biến là toàn cầu với từ khóa chung. Ví dụ:

global_var = 4 
def change_global(): 
    global global_var 
    global_var = global_var + 1 

Hiện nay không có cách nào để làm tương tự cho các biến trong kèm theo chức năng phạm vi, nhưng Python 3 giới thiệu một từ khóa mới, "nonlocal" mà sẽ hành động theo một cách tương tự như trên toàn cầu, nhưng đối với phạm vi chức năng lồng nhau.

78

Không có câu trả lời kỹ lưỡng về thời gian Python3, vì vậy tôi đã trả lời ở đây.

Như được cung cấp trong các câu trả lời khác, có 4 phạm vi cơ bản, LEGB, cho Địa phương, Bao vây, Toàn cầu và được xây dựng. Ngoài ra, có một phạm vi đặc biệt, cơ thể lớp , không bao gồm phạm vi bao quanh cho các phương thức được xác định trong lớp; bất kỳ bài tập nào trong cơ thể lớp làm cho biến từ đó bị ràng buộc trong thân lớp.

Đặc biệt, không có tuyên bố chặn, bên cạnh defclass, tạo phạm vi thay đổi. Trong Python 2 việc hiểu danh sách không tạo ra một phạm vi biến đổi, tuy nhiên trong Python 3 biến vòng lặp được tạo ra trong một phạm vi mới.

Để chứng minh tính chất riêng của cơ thể lớp

x = 0 
class X(object): 
    y = x 
    x = x + 1 # x is now a variable 
    z = x 

    def method(self): 
     print(self.x) # -> 1 
     print(x)  # -> 0, the global x 
     print(y)  # -> NameError: global name 'y' is not defined 

inst = X() 
print(inst.x, inst.y, inst.z, x) # -> (1, 0, 1, 0) 

Như vậy không giống như trong cơ quan chức năng, bạn có thể gán biến cùng tên trong cơ thể lớp, để có được một biến lớp học có cùng tên; tìm kiếm thêm về tên này sẽ giải quyết thay vì đối với biến lớp.


Một trong những bất ngờ lớn hơn đối với nhiều người mới đến Python là vòng lặp for không tạo ra phạm vi thay đổi. Trong Python 2, việc hiểu danh sách không tạo ra một phạm vi (hoặc trong khi các trình tạo và đọc hiểu dict làm!) Thay vào đó họ bị rò rỉ giá trị trong hàm hoặc phạm vi toàn cầu:

>>> [ i for i in range(5) ] 
>>> i 
4 

Các comprehensions có thể được sử dụng như một xảo quyệt (hoặc khủng khiếp nếu bạn sẽ) cách để làm cho các biến thể thay đổi được trong biểu thức lambda bằng Python 2 - một lambda biểu thức không tạo ra một phạm vi biến, như tuyên bố def sẽ, nhưng trong lambda không có câu lệnh nào được cho phép. Việc gán là một câu lệnh trong Python có nghĩa là không có phép gán biến nào trong lambda được cho phép, nhưng việc hiểu danh sách là một biểu thức ...

Hành vi này đã được sửa trong Python 3 - không có biểu thức hiểu hoặc biến rò máy phát.


Toàn cầu thực sự có nghĩa là phạm vi mô-đun; mô đun python chính là __main__; tất cả các mô-đun đã nhập có thể truy cập được thông qua biến số sys.modules; để có quyền truy cập vào __main__, bạn có thể sử dụng sys.modules['__main__'] hoặc import __main__; nó là hoàn toàn chấp nhận được để truy cập và gán các thuộc tính ở đó; chúng sẽ hiển thị dưới dạng các biến trong phạm vi toàn cầu của mô-đun chính.


Nếu một tên được bao giờ giao cho trong phạm vi hiện tại (trừ trong phạm vi lớp), nó sẽ được coi là thuộc phạm vi đó, nếu không nó sẽ được coi là thuộc về bất kỳ phạm vi kèm theo đó gán vào biến (nó có thể chưa được gán, hoặc hoàn toàn không), hoặc cuối cùng là phạm vi toàn cục. Nếu biến được coi là cục bộ, nhưng biến chưa được đặt hoặc đã bị xóa, việc đọc giá trị biến sẽ dẫn đến UnboundLocalError, là lớp con của NameError.

x = 5 
def foobar() 
    print(x) # UnboundLocalError! 
    x += 1 # assignment here makes x a local variable! 

Phạm vi có thể tuyên bố rằng nó một cách rõ ràng muốn sửa đổi các biến toàn cầu (phạm vi mô-đun), với từ khóa toàn cầu:

x = 5 
def foobar(): 
    global x 
    print(x) # -> 5 
    x += 1 

foobar() 
print(x) # -> 6 

này cũng có thể ngay cả khi nó đã được che phủ trong kèm theo phạm vi:

x = 5 
y = 13 
def make_closure(): 
    x = 42 
    y = 911 
    def func(): 
     global x # sees the global value 
     print(x, y) 
     x += 1 

    return func 

func = make_closure() 
func()  # -> print 5 911 
print(x, y) # -> 6 13 

Trong python 2 không có cách nào dễ dàng để sửa đổi giá trị trong phạm vi kèm theo; thường này được mô phỏng bằng cách có một giá trị có thể thay đổi, chẳng hạn như một danh sách với chiều dài 1:

def make_closure(): 
    value = [0] 
    def get_next_value(): 
     value[0] += 1 
     return value[0] 

    return get_next_value 

get_next = make_closure() 
print(get_next()) # -> 1 
print(get_next()) # -> 2 

Tuy nhiên trong python 3, nonlocal đến để giải thoát:

def make_closure(): 
    value = 0 
    def get_next_value(): 
     nonlocal value 
     value += 1 
     return value 
    return get_next_value 

get_next = make_closure() # identical behavior to the previous example. 

Bất kỳ biến mà không được coi là cục bộ với phạm vi hiện tại, hoặc bất kỳ phạm vi kèm theo nào, là một biến toàn cầu. Một tên toàn cục được tra cứu trong từ điển toàn cục của mô-đun; nếu không tìm thấy, toàn cầu sau đó được tra cứu từ mô đun nội trang; tên của mô-đun đã được thay đổi từ python 2 thành python 3; trong python 2 nó là __builtin__ và trong python 3 nó bây giờ được gọi là builtins. Nếu bạn gán cho một thuộc tính của mô đun nội trang, nó sẽ được hiển thị sau đó cho bất kỳ mô-đun nào dưới dạng một biến toàn cầu có thể đọc được, trừ khi mô-đun đó che chúng bằng biến toàn cục của chính nó có cùng tên.


Đọc mô đun dựng sẵn cũng có thể hữu ích; giả sử bạn muốn hàm in kiểu python 3 trong một số phần của tệp, nhưng các phần khác của tệp vẫn sử dụng câu lệnh print, nếu phiên bản python của bạn là> = 2.6, bạn có thể nhận được các chức năng phong cách mới như:

import __builtin__ 

print3 = __builtin__.__dict__['print'] 

Các from __future__ import print_function thực sự không nhập print chức năng bất cứ nơi nào trong Python 2 - thay vào đó nó chỉ vô hiệu hóa các quy tắc phân tích cú pháp cho print tuyên bố trong các mô-đun hiện, xử lý print như bất kỳ biến số nhận dạng biến nào khác, và do đó cho phép hàm print tra cứu trong nội trang dựng sẵn.

18

Một hơi hoàn chỉnh hơn ví dụ về phạm vi:

from __future__ import print_function # for python 2 support 

x = 100 
print("1. Global x:", x) 
class Test(object): 
    y = x 
    print("2. Enclosed y:", y) 
    x = x + 1 
    print("3. Enclosed x:", x) 

    def method(self): 
     print("4. Enclosed self.x", self.x) 
     print("5. Global x", x) 
     try: 
      print(y) 
     except NameError as e: 
      print("6.", e) 

    def method_local_ref(self): 
     try: 
      print(x) 
     except UnboundLocalError as e: 
      print("7.", e) 
     x = 200 # causing 7 because has same name 
     print("8. Local x", x) 

inst = Test() 
inst.method() 
inst.method_local_ref() 

đầu ra:

1. Global x: 100 
2. Enclosed y: 100 
3. Enclosed x: 101 
4. Enclosed self.x 101 
5. Global x 100 
6. global name 'y' is not defined 
7. local variable 'x' referenced before assignment 
8. Local x 200 
+3

Đây là câu trả lời tuyệt vời. Tuy nhiên, tôi nghĩ rằng sự khác biệt giữa 'method' và' method_local_ref' sẽ được đánh dấu. 'phương thức' có thể truy cập biến toàn cầu và in nó như trong '5. Toàn cầu x'. Nhưng 'method_local_ref' không thể vì sau này nó định nghĩa một biến cục bộ có cùng tên đó. Bạn có thể kiểm tra điều này bằng cách xóa dòng 'x = 200' và thấy sự khác biệt – kiril

+0

@brianray: Còn z thì sao? –

+0

@kiril Tôi đã thêm ghi chú về điều đó – brianray

Các vấn đề liên quan