Mô tả ngắn về các quy tắc phạm vi?


472

Có gì chính xác là các quy tắc Phạm vi Python?

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

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

Tìm xthấy ở đâu? Một số lựa chọn có thể bao gồm danh sách dưới đây:

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

Ngoài ra có bối cảnh trong khi thực hiện, khi chức năng spamđược thông qua ở một nơi khác. Và có lẽ các hàm lambda vượt qua một chút khác nhau?

Phải có một tài liệu tham khảo hoặc thuật toán đơn giản ở đâu đó. Đó là một thế giới khó hiểu cho các lập trình viên Python trung cấp.


2
Các quy tắc phạm vi được mô tả khá chặt chẽ - nhưng cũng hoàn toàn - trong tài liệu Python: docs.python.org/3/reference/ ,.
jefe2000

Câu trả lời:


420

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

Quy tắc LEGB

  • L ocal - Tên được gán theo bất kỳ cách nào trong một hàm ( defhoặc lambda) và không được khai báo toàn cục trong hàm đó

  • E nclosing chức năng - Tên giao trong phạm vi địa phương của bất kỳ và tất cả các chức năng tĩnh kèm theo ( defhoặc lambda), từ bên trong để ngoài

  • G lobal (mô-đun) - Tên được gán ở cấp cao nhất của tệp mô-đun hoặc bằng cách thực hiện một globalcâu lệnh trong deftệp.

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

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

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

Các forvòng lặp không có không gian riêng của mình. Theo thứ tự LEGB, phạm vi sẽ là

  • L: ở địa phương def spam(trong code3, code4code5)
  • E: Bất kỳ hàm kèm theo nào (nếu toàn bộ ví dụ nằm trong một hàm khác def)
  • G: Có bất kỳ xtuyên bố nào trên toàn cầu trong mô-đun (in code1) không?
  • B: Bất kỳ nội dung nào xtrong Python.

xsẽ không bao giờ được tìm thấy trong code2(ngay cả trong trường hợp bạn có thể mong đợi, hãy xem câu trả lời của Antti hoặc tại đây ).


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

12
Trên thực tế @Peter, global(var_name)về mặt cú pháp là không chính xác. Cú pháp đúng sẽ global var_namekhông có dấu ngoặc đơn. Bạn có một điểm hợp lệ mặc dù.
martineau

Nếu vậy, tại sao biến "y" của foo không hiển thị với "thanh" bên dưới: >>> def foo(x): ... y = x ... def bar(z): ... y = z ... bar(5) ... print x,y ... >>> foo(3) 3 3
Jonathan Mayer

3
@Jonathan: Bởi vì mỗi cái yđang được viết và không có global ykhai báo - xem bình luận của @ Peter.
martineau

@LakshmanPrasad Nó rơi vào "E", nhưng có một hành vi đặc biệt đáng nói: đó là một biến lớp, vì vậy nó là một "toàn cầu" trong số các đối tượng của nó. Việc gán nó sẽ dẫn đến các vấn đề không thể đoán trước và khó gỡ lỗi nếu bạn không biết bạn đang làm gì.
Ctrl-C

157

Về cơ bản, điều duy nhất trong Python giới thiệu một 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 đó mọi thứ được định nghĩa trực tiếp trong phần thân đượ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 thư rác - chứa mọi thứ được xác định 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 code1, cũng như Foo (và bất kỳ thay đổi nào sau nó)

  • Không gian tên nội dung. Một chút về trường hợp đặc biệt - phần này chứa các hàm và kiểu 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 hãy hy vọng nó có chứa các chức năng tiêu chuẩn và không có gì khác.

Nhiều phạm vi chỉ xuất hiện khi bạn giới thiệu một hàm lồng nhau (hoặc lambda) vào hình ảnh. Tuy nhiên, 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ư mọi thứ 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

Những hạn chế:

Các biến trong phạm vi khác với các biến của hàm cục bộ có thể được truy cập, nhưng không thể bật lại thành các tham số mới mà không cần thêm cú pháp. Thay vào đó, gán sẽ tạo một biến cục bộ mới thay vì ảnh hưởng đến biến trong phạm vi cha. 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 các biến toàn cục từ trong phạm vi hàm, bạn cần xác định rằng biến đó là toàn cục với từ khóa toàn cục. Ví dụ:

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

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


111

Không có câu trả lời thấu đáo về thời gian Python3, vì vậy tôi đã trả lời ở đây. Hầu hết những gì được mô tả ở đây được trình bày chi tiết trong Nghị quyết 4.2.2 về tên của tài liệu Python 3.

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

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

Để chứng minh tính đặc thù của cơ thể giai cấ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)

Do đó, không giống như trong thân hàm, bạn có thể gán lại biến cho cùng tên trong thân lớp, để lấy biến lớp có cùng tên; Thay vào đó, tra cứu thêm về tên này giải quyết biến lớp.


Một trong những điều ngạc nhiên lớn hơn đối với nhiều người mới sử dụng Python là một forvòng lặp không tạo ra một phạm vi biến. Trong Python 2, việc hiểu danh sách cũng không tạo ra một phạm vi (trong khi các trình tạo và hiểu chính xác thì có!) Thay vào đó chúng làm rò rỉ giá trị trong hàm hoặc phạm vi toàn cục:

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

Việc hiểu có thể được sử dụng như một cách khôn ngoan (hoặc khủng khiếp nếu bạn muốn) để tạo các biến có thể sửa đổi trong các biểu thức lambda trong Python 2 - một biểu thức lambda không tạo ra một phạm vi biến, như trong defcâu lệnh, nhưng trong lambda không cho phép câu lệnh nào. Chuyển nhượng là một câu lệnh trong Python có nghĩa là không cho phép các phép gán biến trong lambda, 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ò rỉ.


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 khẩu có thể truy cập thông qua sys.modulesbiến; để có quyền truy cập vào __main__một người có thể sử dụng sys.modules['__main__'], hoặc import __main__; việc truy cập và gán các thuộc tính ở đó là hoàn toàn chấp nhận được; 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 gán cho trong phạm vi hiện tại (ngoại trừ trong phạm vi lớp), nó sẽ được coi là thuộc về phạm vi đó, nếu không, nó sẽ được coi là thuộc về bất kỳ phạm vi kèm theo nào được gán cho biến (có thể không được gán chưa, hoặc hoàn toàn không), hoặc cuối cùng là phạm vi toàn cầu. Nếu biến được coi là cục bộ, nhưng nó chưa được đặt hoặc đã bị xóa, việc đọc giá trị biến sẽ dẫn đến UnboundLocalError, đó là một lớp con của NameError.

x = 5
def foobar():
    print(x)  # causes UnboundLocalError!
    x += 1    # because assignment here makes x a local variable within the function

# call the function
foobar()

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

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

foobar() # -> 5
print(x) # -> 6

Điều này cũng có thể ngay cả khi nó bị che khuất trong phạm vi kèm theo:

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()      # -> 5 911
print(x, y) # -> 6 13

Trong python 2 không có cách dễ dàng để sửa đổi giá trị trong phạm vi kèm theo; thông thường, điều này được mô phỏng bằng cách có một giá trị có thể thay đổi, chẳng hạn như danh sách có độ 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, nonlocalgiải cứu:

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.

Các nonlocaltài liệu nói rằng

Các tên được liệt kê trong một tuyên bố không nhắm mục tiêu, không giống như các tên được liệt kê trong một tuyên bố toàn cầu, phải đề cập đến các ràng buộc tồn tại trước trong một phạm vi kèm theo (phạm vi mà một ràng buộc mới sẽ được tạo ra không thể được xác định rõ ràng).

tức là nonlocalluôn đề cập đến phạm vi không toàn cầu bên ngoài trong cùng, nơi tên đã được ràng buộc (nghĩa là được gán cho, bao gồm cả được sử dụng làm forbiến mục tiêu, trong withmệnh đề hoặc như một tham số hàm).


Bất kỳ biến nào không được coi là cục bộ trong phạm vi hiện tại hoặc bất kỳ phạm vi kèm theo nào đều là biến toàn cục. Một tên toàn cầu được tra cứu trong từ điển toàn cầu mô-đun; nếu không tìm thấy, toàn cầu sẽ được tra cứu từ mô đun dựng sẵn; tên của mô-đun đã được thay đổi từ python 2 thành python 3; trong python 2 nó đã được __builtin__và trong python 3 bây giờ nó được gọi builtins. Nếu bạn gán cho một thuộc tính của mô đun dựng sẵn, nó sẽ hiển thị sau đó cho bất kỳ mô đun nào dưới dạng biến toàn cục có thể đọc được, trừ khi mô đun đó phủ bóng 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ử rằng bạn muốn chức năng 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 printcâu lệnh. Trong Python 2.6-2.7, bạn có thể giữ printchức năng Python 3 với:

import __builtin__

print3 = __builtin__.__dict__['print']

Thực from __future__ import print_functiontế không nhập printhàm 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 printcâu lệnh trong mô-đun hiện tại, xử lý printnhư bất kỳ định danh biến nào khác và do đó cho phép printhàm được tra cứu trong các nội trang.


23

Các quy tắc phạm vi cho Python 2.x đã được phác thảo trong các câu trả lời khác. Điều duy nhất tôi muốn thêm là trong Python 3.0, cũng có khái niệm về phạm vi không cục bộ (được biểu thị bằng từ khóa 'không nhắm mục tiêu'). Điều này cho phép bạn truy cập trực tiếp 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 đóng cửa từ vựng (không có hack xấu xí liên quan đến các đối tượng có thể thay đổi).

EDIT: Đây là PEP với nhiều thông tin hơn về điều này.


23

Một ví dụ đầy đủ hơn một chút 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

6
Đâ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 methodmethod_local_refnên được làm nổi bật. methodcó thể truy cập vào biến toàn cục và in nó như trong 5. Global x. Nhưng method_local_refkhô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 x = 200dòng và xem sự khác biệt
kiril

@brianray: Thế còn z?
Malik A. Rumi

@kiril Tôi đã thêm một lưu ý về điều đó
brianray

@ MalikA.Rumi Tôi đã xóa z vì nó không thú vị
brianray

Đáng ngạc nhiên, đây là lời giải thích rõ ràng duy nhất về phạm vi Python, mà tôi có thể tìm thấy trên tất cả các SO. Đơn giản chỉ cần sử dụng một ví dụ rất cơ bản. Cảm ơn!
not2qubit

13

Python giải quyết các biến của bạn bằng - nói chung - ba không gian tên có sẵn.

Bất cứ lúc nào trong khi thực hiện, có ít nhất ba phạm vi lồng nhau mà không gian tên có thể truy cập trực tiếp: phạm vi trong cùng, được tìm kiếm trước, chứa các tên cục bộ; 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 với phạm vi bao quanh gần nhất; phạm vi giữa, được tìm kiếm tiếp theo, chứa các tên toàn cầu của mô-đun hiện tại; và phạm vi ngoài cùng (tìm kiếm cuối cùng) là không gian tên chứa các tên dựng sẵn.

Có hai chức năng: globalslocals cho bạn thấy 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 các gói, mô-đun, lớp, xây dựng đối tượng và các chức năng. Không có bất kỳ hương vị khác của không gian tên.

Trong trường hợp này, cuộc gọi đến một chức năng 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 toàn cục.

Đị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à - tốt - toàn cầu.

Quy tắc là tìm kiếm các không gian cục bộ lồng nhau được tạo bởi các hàm phương thức (và các đị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 khác. Câu forlệnh (và các câu lệnh ghép khác như iftry ) không tạo ra phạm vi lồng nhau mới. Chỉ các định nghĩa (gói, mô-đun, chức năng, lớp và đối tượng.)

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

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

Từ trong một phương thức lớp, bạn có người địa phương và toàn cầu. Bạn nói self.variableđể chọn thể hiện làm không gian tên. Bạn sẽ lưu ý rằng đó selflà một đối số cho mọi hàm thành viên 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 Phạm vi quy , Python Phạm vi , Phạm vi biến .


5
Điều này là hết hạn. Vì 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 hàm sẽ có quyền truy cập vào phạm vi cục bộ của nó, phạm vi hàm kèm theo và phạm vi toàn cầu (cũng là các hàm dựng).
Brian

Tôi xin lỗi, đây không còn là trường hợp nữa. Python has two namespaces available. Global and local-to-something.
Rizwan Kassim

9

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

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

code2 (thành viên lớp) không thể nhìn thấy 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 lặp) sống trong cùng phạm vi với code3, vì vậy nếu bạn đã viết cho x trong đó, bạn sẽ thay đổi thể hiện x được xác định trong code3, không tạo x mới.

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

lambda không khác gì def. Nếu bạn có lambda được sử dụng bên trong một hàm, thì nó cũng giống như xác định 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ể liên kết x ở bất kỳ mức lồng nhau nào và Python sẽ chọn phiên bản 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 thể hiện x từ phạm vi chứa gần nhất, đó là phạm vi chức năng được liên kết với fun2. Nhưng các trường hợp x khác, được xác định trong fun1 và toàn cầu, không bị ảnh hưởng.

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

0 0

1

Trong Python

bất kỳ biến nào được gán một giá trị là cục bộ cho khối trong đó phép gán xuất hiện.

Nếu không thể tìm thấy một biến trong phạm vi hiện tại, vui lòng tham khảo thứ tự LEGB.

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.