Jawaban:
Fungsi dalam dapat membaca variabel nonlokal di 2.x, hanya saja tidak mengikatnya kembali. Ini menjengkelkan, tetapi Anda bisa mengatasinya. Buat saja kamus, dan simpan data Anda sebagai elemen di dalamnya. Fungsi dalam tidak dilarang untuk mengubah objek yang dirujuk oleh variabel nonlokal.
Untuk menggunakan contoh dari 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}
. Di sini, print d
membaca di luar d
sehingga menciptakan variabel nonlokal d
dalam lingkup dalam.
X = 1
hanya mengikat nama X
dengan objek tertentu ( int
dengan nilai 1
). X = 1; Y = X
mengikat dua nama ke objek yang sama persis. Bagaimanapun, beberapa objek dapat berubah dan Anda dapat mengubah nilainya.
Solusi berikut ini terinspirasi oleh jawaban Elias Zamaria , tetapi berlawanan dengan jawaban tersebut, jawaban tersebut menangani banyak panggilan dari fungsi luar dengan benar. "Variabel" inner.y
bersifat lokal untuk panggilan saat ini outer
. Hanya itu bukan variabel, karena itu dilarang, tetapi atribut objek (objek adalah fungsinya inner
sendiri). Ini sangat jelek (perhatikan bahwa atribut hanya dapat dibuat setelah inner
fungsi ditentukan) tetapi tampaknya efektif.
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()
dan dec()
kembali dari luar kenaikan dan penurunan itu sebagai penghitung bersama. Kemudian Anda harus memutuskan fungsi mana untuk melampirkan nilai counter saat ini dan mereferensikan fungsi itu dari yang lain. Yang terlihat agak aneh dan asimetris. Misal di dec()
baris seperti inc.value -= 1
.
Daripada kamus, ada lebih sedikit kekacauan di kelas nonlokal . Mengubah contoh @ ChrisB :
def outer():
class context:
y = 0
def inner():
context.y += 1
return context.y
return inner
Kemudian
f = outer()
assert f() == 1
assert f() == 2
assert f() == 3
assert f() == 4
Setiap panggilan outer () membuat kelas baru dan berbeda yang disebut konteks (bukan hanya instance baru). Jadi ini menghindari kewaspadaan @ Nathaniel tentang konteks bersama.
g = outer()
assert g() == 1
assert g() == 2
assert f() == 5
__slots__ = ()
dan membuat sebuah objek daripada menggunakan kelas, misalnya context.z = 3
akan memunculkan sebuah AttributeError
. Hal ini dimungkinkan untuk semua kelas, kecuali mereka mewarisi dari kelas yang tidak menentukan slot.
Saya pikir kuncinya di sini adalah apa yang Anda maksud dengan "akses". Seharusnya tidak ada masalah dengan membaca variabel di luar lingkup closure, misalnya,
x = 3
def outer():
def inner():
print x
inner()
outer()
harus bekerja seperti yang diharapkan (pencetakan 3). Namun, menimpa nilai x tidak akan berhasil, misalnya,
x = 3
def outer():
def inner():
x = 5
inner()
outer()
print x
akan tetap mencetak 3. Dari pemahaman saya tentang PEP-3104, inilah yang dimaksud dengan kata kunci nonlokal. Seperti yang disebutkan di PEP, Anda dapat menggunakan kelas untuk mencapai hal yang sama (agak berantakan):
class Namespace(object): pass
ns = Namespace()
ns.x = 3
def outer():
def inner():
ns.x = 5
inner()
outer()
print ns.x
def ns(): pass
diikuti oleh ns.x = 3
. Itu tidak cantik, tapi sedikit kurang jelek di mataku.
class Namespace: x = 3
?
ns
adalah objek global yang mengapa Anda dapat merujuk ns.x
pada level modul dalam print
pernyataan di bagian paling akhir .
Ada cara lain untuk mengimplementasikan variabel nonlokal dengan Python 2, jika ada jawaban di sini yang tidak diinginkan karena alasan apa pun:
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
Tidak ada gunanya menggunakan nama fungsi dalam pernyataan penugasan variabel, tetapi terlihat lebih sederhana dan lebih bersih daripada meletakkan variabel dalam kamus. Nilainya diingat dari satu panggilan ke panggilan lainnya, seperti dalam jawaban Chris B.
f = outer()
dan kemudian melakukannya g = outer()
, maka f
penghitung akan disetel ulang. Ini karena keduanya berbagi variabel tunggal outer.y
, bukan masing-masing memiliki variabel independen sendiri. Meskipun kode ini terlihat lebih estetis daripada jawaban Chris B, caranya tampaknya menjadi satu-satunya cara untuk meniru pelingkupan leksikal jika Anda ingin menelepon outer
lebih dari sekali.
outer.y
tidak melibatkan apa pun yang bersifat lokal ke pemanggilan fungsi (instance) outer()
, tetapi menetapkan ke atribut objek fungsi yang terikat ke nama outer
dalam cakupan yang melingkupinya . Dan oleh karena itu seseorang dapat juga menggunakan, secara tertulis outer.y
, nama lain selain outer
, asalkan diketahui terikat dalam ruang lingkup itu. Apakah ini benar?
outer.y
menggunakan nama inner.y
(karena inner
terikat di dalam panggilan outer()
, yang merupakan cakupan yang kita inginkan), tetapi meletakkan inisialisasi inner.y = 0
setelah definisi inner (sebagai objek harus ada saat atributnya dibuat), tapi tentu saja sebelumnya return inner
?
Berikut adalah sesuatu yang terinspirasi dari saran Alois Mahdal dalam komentarnya tentang jawaban lain :
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)
Memperbarui
Setelah melihat kembali hal ini baru-baru ini, saya terkejut dengan bagaimana dekorator-seperti itu — ketika saya sadar bahwa menerapkannya sebagai seseorang akan membuatnya lebih umum & berguna (meskipun melakukannya bisa dibilang menurunkan keterbacaannya sampai taraf tertentu).
# 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)
Perhatikan bahwa kedua versi bekerja dengan Python 2 dan 3.
Ada kutil dalam aturan pelingkupan python - tugas membuat variabel lokal ke lingkup fungsi yang langsung melingkupinya. Untuk variabel global, Anda akan menyelesaikannya dengan global
kata kunci.
Solusinya adalah dengan memperkenalkan objek yang dibagi di antara dua cakupan, yang berisi variabel yang bisa berubah, tetapi itu sendiri direferensikan melalui variabel yang tidak ditetapkan.
def outer(v):
def inner(container = [v]):
container[0] += 1
return container[0]
return inner
Alternatifnya adalah beberapa peretasan cakupan:
def outer(v):
def inner(varname = 'v', scope = locals()):
scope[varname] += 1
return scope[varname]
return inner
Anda mungkin bisa menemukan beberapa trik untuk mendapatkan nama parameter outer
, dan kemudian meneruskannya sebagai varname, tetapi tanpa bergantung pada nama outer
Anda ingin menggunakan kombinator Y.
nonlocal
. locals()
membuat kamus dari orang outer()
lokal pada saat inner()
itu ditentukan tetapi mengubah kamus itu tidak mengubah v
dalam outer()
. Ini tidak akan berfungsi lagi ketika Anda memiliki lebih banyak fungsi dalam yang ingin berbagi variabel tertutup. Ucapkan a inc()
dan dec()
itu kenaikan dan penurunan penghitung bersama.
nonlocal
adalah fitur python 3.
nonlocal
di Python 2 secara umum . Ide Anda tidak mencakup kasus umum tetapi hanya satu dengan satu fungsi batin. Lihat inti ini sebagai contoh. Kedua fungsi dalam tersebut memiliki wadahnya masing-masing. Anda memerlukan objek yang bisa berubah dalam lingkup fungsi luar, seperti jawaban lain yang sudah disarankan.
nonlocal
kata kunci yang diperkenalkan di Python 3.
Cara lain untuk melakukannya (meskipun terlalu bertele-tele):
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
Memperluas solusi elegan Martineau di atas menjadi kasus penggunaan yang praktis dan agak kurang elegan, saya dapatkan:
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)
Gunakan variabel global
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
Secara pribadi, saya tidak suka variabel global. Tapi, proposal saya didasarkan pada https://stackoverflow.com/a/19877437/1083704 jawaban
def report():
class Rank:
def __init__(self):
report.ranks += 1
rank = Rank()
report.ranks = 0
report()
di mana pengguna perlu mendeklarasikan variabel global ranks
, setiap kali Anda perlu memanggil report
. Peningkatan saya menghilangkan kebutuhan untuk menginisialisasi variabel fungsi dari pengguna.
inner
, tetapi tidak dapat menetapkannya, tetapi Anda dapat mengubah kunci dan nilainya. Ini menghindari penggunaan variabel global.