2013-09-04 48 views
9

Trong python, tôi có thể viết:Python chức năng bên trong

def func(): 
    x = 1 
    print x 
    x+=1 

    def _func(): 
     print x 
    return _func 

test = func() 
test() 

khi tôi chạy nó, kết quả là:

Như _func có truy cập vào biến "x" được xác định trong func. Ngay ...

Nhưng nếu tôi làm:

def func(): 
    x = 1 
    print x 

    def _func(): 
     x+=1 
     print x 
    return _func 

test = func() 
test() 

Sau đó, tôi nhận được một thông báo lỗi: UnboundLocalError: biến cục bộ 'x' tham chiếu trước khi phân

Trong trường hợp này, có vẻ như _func không thể "xem" biến "x"

Câu hỏi đặt ra là: Tại sao in x trong ví dụ đầu tiên "xem" biến "x", trong khi toán tử toán học x + = 1 ném một ngoại lệ?

Tôi không hiểu tại sao ...

+5

Đây là lý do tại sao python 3 thêm 'nonlocal' – FatalError

+0

Kiểm tra điều này: http://stackoverflow.com/questions/5218895/python-nested-functions-variable-scoping – Germano

+0

Tôi thường sử dụng một đối tượng để lưu trữ ngữ cảnh vì bạn có thể sửa đổi các thuộc tính của đối tượng trong phần đóng. Ví dụ: 'x = {'val': 0}' rồi 'x ['val'] + = 1' bên trong' _func'. – six8

Trả lời

8

Kiểm tra câu trả lời này: https://stackoverflow.com/a/293097/1741450

Variables in scopes other than the local function's variables can be accessed, but can't be rebound to new parameters without further syntax. Instead, assignment will create a new local variable instead of affecting the variable in the parent scope. For example:

+0

Địa chỉ này có giải thích tại sao in x vẫn hoạt động trong trường hợp đầu tiên không? – pythOnometrist

+1

yep: in chỉ truy cập x (và nó có thể) trong khi trong ví dụ thứ hai chức năng đang cố gắng sửa đổi x (và nó không thể theo báo giá của tôi). – lucasg

0

Bạn không thể rebind biến được đóng trên bằng Python 2 (bạn có thể sử dụng nonlocal bằng Python 3). Nếu tôi phải làm những gì bạn đang làm trong Python 2 sau đó tôi thực hiện giải pháp sau đây:

class Pointer(object): 
    def __init__(self, val): 
     self.val = val 

def func(): 
    px = Pointer(1) 
    print px.val 
    px.val += 1 

    def _func(): 
     print px.val 
    return _func 

test = func() 
test() 

Về cơ bản tôi đặt bất cứ điều gì giá trị tôi cần phải được biến đổi vào một đối tượng - đôi khi tôi sử dụng một danh sách với một yếu tố - và sau đó viết lại mã để tất cả những gì xảy ra là các cuộc gọi phương thức. Trên đây là thực sự tương đương với:

class Pointer(object): 
    def __init__(self, val): 
     self.val = val 

def func(): 
    px = Pointer(1) 
    print px.val 
    px.__setattr__('val', px.val + 1) 

    def _func(): 
     print px.val 
    return _func 

test = func() 
test() 
3

Xét rằng Python là một ngôn ngữ "giải thích", một cách tự nhiên giả định rằng nếu một biến đã được xác định trong một phạm vi ngoài cùng biến vẫn có thể truy cập trong phạm vi bên trong. Và thực sự trong ví dụ đầu tiên của bạn khi bên trong _func chỉ in x nó hoạt động.

Điều không rõ ràng về Python mặc dù đây không phải là cách xác định phạm vi của một biến. Phân tích Python tại thời điểm biên dịch thời gian biến nào được coi là "cục bộ" thành phạm vi dựa trên việc chúng có được gán cho phạm vi đó hay không. Trong trường hợp này, nhiệm vụ bao gồm các toán tử gán tăng cường. Vì vậy, khi Python biên dịch bên trong của bạn _func trong ví dụ thứ hai để bytecode nó thấy x += 1 và xác định rằng x phải là một biến địa phương để _func. Ngoại trừ các khóa học kể từ khi nhiệm vụ đầu tiên của nó là một bài tập tăng cường không có x biến tại địa phương để tăng thêm và bạn nhận được UnboundLocalError.

Một cách khác nhau để xem điều này có thể được để viết một cái gì đó như:

def _func(): 
    print x 
    x = 2 

đây một lần nữa vì _func chứa dòng x = 2 nó sẽ đối xử với x như là một biến địa phương trong phạm vi chức năng và không như x được xác định trong hàm ngoài. Vì vậy, print x cũng sẽ dẫn đến một số UnboundLocalError.

Bạn có thể kiểm tra này một cách chi tiết sâu hơn bằng cách sử dụng các mô-đun dis để hiển thị bytecode tạo ra cho các chức năng:

>>> dis.dis(_func) 
    2   0 LOAD_FAST    0 (x) 
       3 PRINT_ITEM   
       4 PRINT_NEWLINE  

    3   5 LOAD_CONST    1 (2) 
       8 STORE_FAST    0 (x) 
      11 LOAD_CONST    0 (None) 
      14 RETURN_VALUE 

Các opcode LOAD_FAST được cho dành cho tra cứu "nhanh" của các biến địa phương mà bỏ qua chậm hơn, tra cứu tên tổng quát hơn. Nó sử dụng một mảng con trỏ trong đó mỗi biến cục bộ (trong khung ngăn xếp hiện tại) được kết hợp với một chỉ mục trong mảng đó, thay vì đi qua tra cứu từ điển và như vậy. Trong ví dụ trên, đối số duy nhất cho mã vạch LOAD_FAST0 - trong trường hợp này là địa chỉ đầu tiên (và duy nhất).

Bạn có thể kiểm tra về chức năng riêng của mình (đặc biệt là đối tượng mã cơ bản của nó) rằng có một biến địa phương sử dụng trong mã đó, và rằng tên biến liên kết với nó là 'x':

>>> _func.__code__.co_nlocals 
1 
>>> _func.__code__.co_varnames 
('x',) 

Đó là cách dis.dis có thể báo cáo 0 LOAD_FAST 0 (x). Tương tự như vậy đối với opcode STORE_FAST sau này.

Không cần biết điều này để hiểu các phạm vi biến đổi trong Python, nhưng có thể hữu ích khi biết những gì đang xảy ra dưới mui xe.

Như đã đề cập trong một số câu trả lời khác, Python 3 đã giới thiệu từ khóa nonlocal ngăn chặn việc liên kết biên dịch tên này thành biến cục bộ dựa trên gán cho biến đó trong phạm vi cục bộ.

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