Câu trả lời:
Các hàm bên trong có thể đọc các biến phi địa phương trong 2.x, chỉ cần không lặp lại chúng. Điều này thật khó chịu, nhưng bạn có thể giải quyết nó. Chỉ cần tạo một từ điển và lưu trữ dữ liệu của bạn dưới dạng các phần tử trong đó. Các hàm bên trong không bị cấm thay đổi các đối tượng mà các biến phi địa phương tham chiếu đến.
Để sử dụng ví dụ từ Wikipedia:
def outer():
d = {'y' : 0}
def inner():
d['y'] += 1
return d['y']
return inner
f = outer()
print(f(), f(), f()) #prints 1 2 3
def inner(): print d; d = {'y': 1}
. Ở đây, print d
đọc bên ngoài d
do đó tạo biến phi địa phương d
trong phạm vi bên trong.
X = 1
chỉ đơn giản là liên kết tên X
với một đối tượng cụ thể (an int
với giá trị 1
). X = 1; Y = X
liên kết hai tên với cùng một đối tượng chính xác. Dù sao, một số đối tượng có thể thay đổi và bạn có thể thay đổi giá trị của chúng.
Giải pháp sau đây được lấy cảm hứng từ câu trả lời của Elias Zamaria , nhưng trái ngược với câu trả lời đó xử lý nhiều lệnh gọi của hàm ngoài một cách chính xác. "Biến" inner.y
là cục bộ cho cuộc gọi hiện tại của outer
. Chỉ nó không phải là một biến, vì điều đó bị cấm, mà là một thuộc tính đối tượng (đối tượng là inner
chính hàm ). Điều này rất xấu (lưu ý rằng thuộc tính chỉ có thể được tạo sau khi inner
hàm được xác định) nhưng có vẻ hiệu quả.
def outer():
def inner():
inner.y += 1
return inner.y
inner.y = 0
return inner
f = outer()
g = outer()
print(f(), f(), g(), f(), g()) #prints (1, 2, 1, 3, 2)
inc()
và a dec()
trả về từ bên ngoài mà tăng và giảm một bộ đếm dùng chung. Sau đó, bạn phải quyết định chức năng nào để gắn giá trị bộ đếm hiện tại và tham chiếu chức năng đó từ (các) chức năng khác. Trông hơi lạ và không đối xứng. Ví dụ: trong dec()
một dòng như thế inc.value -= 1
.
Thay vì một từ điển, có ít sự lộn xộn hơn đối với một lớp phi địa phương . Sửa đổi ví dụ của @ ChrisB :
def outer():
class context:
y = 0
def inner():
context.y += 1
return context.y
return inner
Sau đó
f = outer()
assert f() == 1
assert f() == 2
assert f() == 3
assert f() == 4
Mỗi lệnh gọi bên ngoài () tạo ra một lớp mới và riêng biệt được gọi là ngữ cảnh (không chỉ đơn thuần là một thể hiện mới). Vì vậy, nó tránh được @ Nathaniel cẩn thận về ngữ cảnh được chia sẻ.
g = outer()
assert g() == 1
assert g() == 2
assert f() == 5
__slots__ = ()
và tạo một đối tượng thay vì sử dụng lớp, ví dụ: context.z = 3
sẽ nâng cao một AttributeError
. Điều này có thể xảy ra cho tất cả các lớp, trừ khi chúng kế thừa từ một lớp không xác định vị trí.
Tôi nghĩ rằng chìa khóa ở đây là những gì bạn có nghĩa là "truy cập". Sẽ không có vấn đề gì khi đọc một biến bên ngoài phạm vi đóng, ví dụ:
x = 3
def outer():
def inner():
print x
inner()
outer()
sẽ hoạt động như mong đợi (in 3). Tuy nhiên, ghi đè giá trị của x không hoạt động, ví dụ:
x = 3
def outer():
def inner():
x = 5
inner()
outer()
print x
sẽ vẫn in 3. Theo hiểu biết của tôi về PEP-3104, đây là những gì từ khóa phi địa phương có nghĩa là bao gồm. Như đã đề cập trong PEP, bạn có thể sử dụng một lớp để thực hiện điều tương tự (hơi lộn xộn):
class Namespace(object): pass
ns = Namespace()
ns.x = 3
def outer():
def inner():
ns.x = 5
inner()
outer()
print ns.x
def ns(): pass
theo sau là ns.x = 3
. Nó không đẹp, nhưng nó hơi xấu đối với mắt tôi.
class Namespace: x = 3
thì sao?
ns
là một đối tượng toàn cục, đó là lý do tại sao bạn có thể tham chiếu ns.x
ở cấp mô-đun trong print
câu lệnh ở cuối .
Có một cách khác để triển khai các biến phi địa phương trong Python 2, trong trường hợp bất kỳ câu trả lời nào ở đây là không mong muốn vì bất kỳ lý do gì:
def outer():
outer.y = 0
def inner():
outer.y += 1
return outer.y
return inner
f = outer()
print(f(), f(), f()) #prints 1 2 3
Việc sử dụng tên của hàm trong câu lệnh gán của biến là hơi thừa, nhưng đối với tôi thì nó trông đơn giản và gọn gàng hơn là đặt biến vào từ điển. Giá trị được ghi nhớ từ lệnh gọi này đến lệnh gọi khác, giống như trong câu trả lời của Chris B.
f = outer()
và sau đó làm g = outer()
, thì f
bộ đếm của sẽ được đặt lại. Điều này là do cả hai đều chia sẻ một biến duy nhất outer.y
, thay vì mỗi biến có một biến độc lập của riêng mình. Mặc dù mã này trông có vẻ thẩm mỹ hơn câu trả lời của Chris B, nhưng cách của anh ấy dường như là cách duy nhất để mô phỏng phạm vi từ vựng nếu bạn muốn gọi outer
nhiều lần.
outer.y
không liên quan đến bất kỳ thứ gì cục bộ đối với lời gọi hàm (phiên bản) outer()
, nhưng gán cho một thuộc tính của đối tượng hàm được liên kết với tên outer
trong phạm vi bao quanh của nó . Và do đó, người ta có thể sử dụng, bằng văn bản outer.y
, bất kỳ tên nào khác thay thế outer
, miễn là nó được biết là bị ràng buộc trong phạm vi đó. Điều này có chính xác?
outer.y
sử dụng tên inner.y
(vì inner
được ràng buộc bên trong cuộc gọi outer()
, đó chính xác là phạm vi chúng tôi muốn), nhưng đặt khởi tạo inner.y = 0
sau khi định nghĩa bên trong (vì đối tượng phải tồn tại khi thuộc tính của nó được tạo), nhưng tất nhiên là trước return inner
?
Đây là điều gì đó được truyền cảm hứng từ một gợi ý mà Alois Mahdal đưa ra trong một bình luận về một câu trả lời khác :
class Nonlocal(object):
""" Helper to implement nonlocal names in Python 2.x """
def __init__(self, **kwargs):
self.__dict__.update(kwargs)
def outer():
nl = Nonlocal(y=0)
def inner():
nl.y += 1
return nl.y
return inner
f = outer()
print(f(), f(), f()) # -> (1 2 3)
Cập nhật
Sau khi nhìn lại điều này gần đây, tôi đã bị ấn tượng bởi nó giống như một nhà trang trí - khi nó cho tôi thấy rằng việc triển khai nó như một người sẽ làm cho nó chung chung và hữu ích hơn (mặc dù làm như vậy có thể làm giảm khả năng đọc của nó ở một mức độ nào đó).
# Implemented as a decorator.
class Nonlocal(object):
""" Decorator class to help implement nonlocal names in Python 2.x """
def __init__(self, **kwargs):
self._vars = kwargs
def __call__(self, func):
for k, v in self._vars.items():
setattr(func, k, v)
return func
@Nonlocal(y=0)
def outer():
def inner():
outer.y += 1
return outer.y
return inner
f = outer()
print(f(), f(), f()) # -> (1 2 3)
Lưu ý rằng cả hai phiên bản đều hoạt động trên cả Python 2 và 3.
Có một vấn đề trong quy tắc xác định phạm vi của python - phép gán làm cho một biến cục bộ trở thành phạm vi hàm bao quanh ngay lập tức của nó. Đối với một biến toàn cục, bạn sẽ giải quyết điều này bằng global
từ khóa.
Giải pháp là giới thiệu một đối tượng được chia sẻ giữa hai phạm vi, có chứa các biến có thể thay đổi, nhưng chính nó được tham chiếu thông qua một biến không được gán.
def outer(v):
def inner(container = [v]):
container[0] += 1
return container[0]
return inner
Một giải pháp thay thế là một số hackery phạm vi:
def outer(v):
def inner(varname = 'v', scope = locals()):
scope[varname] += 1
return scope[varname]
return inner
Bạn có thể tìm ra một số thủ thuật để lấy tên của tham số outer
, sau đó chuyển nó dưới dạng varname, nhưng không dựa vào tên outer
bạn muốn, bạn cần sử dụng bộ tổ hợp Y.
nonlocal
. locals()
tạo một từ điển của outer()
các địa phương tại thời điểm inner()
được xác định nhưng việc thay đổi từ điển đó không thay đổi v
trong outer()
. Điều này sẽ không hoạt động nữa khi bạn có nhiều hàm bên trong muốn chia sẻ một biến đóng trên. Nói a inc()
và dec()
tăng và giảm một bộ đếm dùng chung.
nonlocal
là một tính năng của python 3.
nonlocal
trong Python 2 nói chung . Ý tưởng của bạn không bao gồm trường hợp chung mà chỉ là trường hợp có một chức năng bên trong. Hãy xem ý chính này để làm ví dụ. Cả hai chức năng bên trong đều có hộp đựng riêng. Bạn cần một đối tượng có thể thay đổi trong phạm vi của hàm bên ngoài, như các câu trả lời khác đã đề xuất.
nonlocal
từ khóa được giới thiệu trong Python 3.
Một cách khác để làm điều đó (mặc dù nó quá dài dòng):
import ctypes
def outer():
y = 0
def inner():
ctypes.pythonapi.PyCell_Set(id(inner.func_closure[0]), id(y + 1))
return y
return inner
x = outer()
x()
>> 1
x()
>> 2
y = outer()
y()
>> 1
x()
>> 3
Mở rộng giải pháp thanh lịch Martineau ở trên sang một trường hợp sử dụng thực tế và hơi kém thanh lịch mà tôi nhận được:
class nonlocals(object):
""" Helper to implement nonlocal names in Python 2.x.
Usage example:
def outer():
nl = nonlocals( n=0, m=1 )
def inner():
nl.n += 1
inner() # will increment nl.n
or...
sums = nonlocals( { k:v for k,v in locals().iteritems() if k.startswith('tot_') } )
"""
def __init__(self, **kwargs):
self.__dict__.update(kwargs)
def __init__(self, a_dict):
self.__dict__.update(a_dict)
Sử dụng một biến toàn cục
def outer():
global y # import1
y = 0
def inner():
global y # import2 - requires import1
y += 1
return y
return inner
f = outer()
print(f(), f(), f()) #prints 1 2 3
Cá nhân tôi không thích các biến toàn cục. Tuy nhiên, đề xuất của tôi dựa trên câu trả lời https://stackoverflow.com/a/19877437/1083704
def report():
class Rank:
def __init__(self):
report.ranks += 1
rank = Rank()
report.ranks = 0
report()
nơi người dùng cần khai báo một biến toàn cục ranks
, mỗi khi bạn cần gọi report
. Cải tiến của tôi loại bỏ nhu cầu khởi tạo các biến hàm từ người dùng.
inner
, nhưng không thể gán cho nó, nhưng bạn có thể sửa đổi các khóa và giá trị của nó. Điều này tránh sử dụng các biến toàn cục.