ฉันพยายามใช้การปิดใน Python 2.6 และฉันต้องการเข้าถึงตัวแปร nonlocal แต่ดูเหมือนว่าคีย์เวิร์ดนี้จะไม่มีใน python 2.x วิธีหนึ่งจะเข้าถึงตัวแปร nonlocal ในการปิดใน python เวอร์ชันเหล่านี้ได้อย่างไร
ฉันพยายามใช้การปิดใน Python 2.6 และฉันต้องการเข้าถึงตัวแปร nonlocal แต่ดูเหมือนว่าคีย์เวิร์ดนี้จะไม่มีใน python 2.x วิธีหนึ่งจะเข้าถึงตัวแปร nonlocal ในการปิดใน python เวอร์ชันเหล่านี้ได้อย่างไร
คำตอบ:
ฟังก์ชันภายในสามารถอ่านตัวแปร nonlocal ได้ใน 2.x เพียง แต่ไม่สามารถย้อนกลับได้ สิ่งนี้น่ารำคาญ แต่คุณสามารถแก้ไขได้ เพียงสร้างพจนานุกรมและจัดเก็บข้อมูลของคุณเป็นองค์ประกอบในนั้น ฟังก์ชันภายในไม่ได้รับอนุญาตจากการกลายพันธุ์ของวัตถุที่ตัวแปรที่ไม่ใช่โลคัลอ้างถึง
ในการใช้ตัวอย่างจาก 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}
UnboundLocalError: ที่นี่print d
อ่านด้านนอกd
จึงสร้างตัวแปร nonlocal d
ในขอบเขตภายใน
X = 1
จะผูกชื่อX
กับออบเจ็กต์เฉพาะ (an int
with the value 1
) X = 1; Y = X
ผูกชื่อสองชื่อเข้ากับวัตถุเดียวกัน อย่างไรก็ตามวัตถุบางอย่างไม่แน่นอนและคุณสามารถเปลี่ยนค่าได้
วิธีแก้ปัญหาต่อไปนี้ได้รับแรงบันดาลใจจากคำตอบของ Elias Zamariaแต่ตรงกันข้ามกับคำตอบนั้นจัดการการเรียกใช้ฟังก์ชันภายนอกได้หลายสายอย่างถูกต้อง ว่า "ตัวแปร" เป็นท้องถิ่นที่จะสายปัจจุบันของinner.y
outer
เพียง แต่ไม่ใช่ตัวแปรเนื่องจากเป็นสิ่งต้องห้าม แต่เป็นแอตทริบิวต์ของวัตถุ (วัตถุที่เป็นฟังก์ชันinner
นั้นเอง) สิ่งนี้น่าเกลียดมาก (โปรดทราบว่าแอตทริบิวต์สามารถสร้างได้หลังจากกำหนดinner
ฟังก์ชันแล้วเท่านั้น) แต่ดูเหมือนจะได้ผล
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()
และdec()
ส่งกลับจากภายนอกที่เพิ่มและลดตัวนับที่ใช้ร่วมกัน จากนั้นคุณต้องตัดสินใจว่าฟังก์ชันใดที่จะแนบค่าตัวนับปัจจุบันและอ้างอิงฟังก์ชันนั้นจากฟังก์ชันอื่น ซึ่งดูแปลกตาและสมส่วนอยู่บ้าง เช่นในdec()
บรรทัดเช่นinc.value -= 1
.
แทนที่จะเป็นพจนานุกรมมีความยุ่งเหยิงน้อยกว่าสำหรับคลาสที่ไม่ใช่คนในท้องถิ่น การปรับเปลี่ยนตัวอย่างของ @ ChrisB :
def outer():
class context:
y = 0
def inner():
context.y += 1
return context.y
return inner
แล้วก็
f = outer()
assert f() == 1
assert f() == 2
assert f() == 3
assert f() == 4
การเรียกภายนอก () แต่ละครั้งจะสร้างคลาสใหม่ที่แตกต่างกันซึ่งเรียกว่าบริบท (ไม่ใช่แค่อินสแตนซ์ใหม่) ดังนั้นจึงหลีกเลี่ยง@ Nathaniel ระวังบริบทที่ใช้ร่วมกัน
g = outer()
assert g() == 1
assert g() == 2
assert f() == 5
__slots__ = ()
และสร้างวัตถุแทนการใช้คลาสเช่นcontext.z = 3
จะเพิ่มAttributeError
ไฟล์. เป็นไปได้สำหรับทุกคลาสเว้นแต่จะสืบทอดมาจากคลาสที่ไม่ได้กำหนดสล็อต
ฉันคิดว่ากุญแจสำคัญในที่นี้คือความหมายของ "การเข้าถึง" ไม่ควรมีปัญหาในการอ่านตัวแปรนอกขอบเขตการปิดเช่น
x = 3
def outer():
def inner():
print x
inner()
outer()
ควรทำงานตามที่คาดไว้ (การพิมพ์ 3) อย่างไรก็ตามการลบล้างค่าของ x ไม่ได้ผลเช่น
x = 3
def outer():
def inner():
x = 5
inner()
outer()
print x
จะยังคงพิมพ์ 3 จากความเข้าใจของฉันเกี่ยวกับPEP-3104นี่คือสิ่งที่คำหลักที่ไม่ใช่คนท้องถิ่นหมายถึง ดังที่ได้กล่าวไว้ใน PEP คุณสามารถใช้คลาสเพื่อทำสิ่งเดียวกันให้สำเร็จ (แบบยุ่ง ๆ ):
class Namespace(object): pass
ns = Namespace()
ns.x = 3
def outer():
def inner():
ns.x = 5
inner()
outer()
print ns.x
def ns(): pass
ns.x = 3
มันไม่สวย แต่ตาของฉันน่าเกลียดน้อยกว่าเล็กน้อย
class Namespace: x = 3
?
ns
เป็นวัตถุระดับโลกซึ่งเป็นเหตุผลที่คุณสามารถอ้างอิงns.x
ในระดับโมดูลในprint
คำสั่งในตอนท้าย .
มีอีกวิธีหนึ่งในการใช้ตัวแปร nonlocal ใน Python 2 ในกรณีที่คำตอบใด ๆ ที่นี่ไม่เป็นที่ต้องการไม่ว่าด้วยเหตุผลใดก็ตาม:
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
การใช้ชื่อของฟังก์ชันในคำสั่งมอบหมายของตัวแปรนั้นซ้ำซ้อน แต่มันดูง่ายและสะอาดกว่าสำหรับฉันมากกว่าการใส่ตัวแปรในพจนานุกรม ค่าจะถูกจดจำจากสายหนึ่งไปยังอีกสายหนึ่งเช่นเดียวกับคำตอบของ Chris B.
f = outer()
และหลังจากนั้นก็ทำg = outer()
แล้วf
's เคาน์เตอร์จะถูกรีเซ็ต เนื่องจากทั้งสองแชร์ตัวแปรเดียว outer.y
แทนที่จะมีตัวแปรอิสระของตัวเอง แม้ว่ารหัสนี้จะดูสวยงามกว่าคำตอบของ Chris B แต่วิธีของเขาดูเหมือนจะเป็นวิธีเดียวที่จะเลียนแบบการกำหนดขอบเขตคำศัพท์หากคุณต้องการโทรouter
มากกว่าหนึ่งครั้ง
outer.y
ไม่เกี่ยวข้องกับการเรียกใช้ฟังก์ชันในระบบ (อินสแตนซ์) outer()
แต่กำหนดให้กับแอตทริบิวต์ของอ็อบเจ็กต์ฟังก์ชันที่ผูกไว้กับชื่อouter
ในขอบเขตการปิดล้อม และดังนั้นจึงหนึ่งได้อย่างเท่าเทียมกันทั้งได้ใช้ในการเขียนouter.y
, อื่น ๆชื่อแทนouter
ให้มันเป็นที่รู้จักกันที่จะผูกพันอยู่ในขอบเขตที่ ถูกต้องหรือไม่
outer.y
ใช้ชื่อinner.y
(เนื่องจากinner
ถูกผูกไว้ในการโทรouter()
ซึ่งเป็นขอบเขตที่เราต้องการ) แต่การวาง การเริ่มต้นinner.y = 0
หลังจากนิยามภายใน (เนื่องจากวัตถุต้องมีอยู่เมื่อสร้างแอตทริบิวต์) แต่ก่อนหน้านี้แน่นอนreturn inner
?
นี่คือสิ่งที่ได้รับแรงบันดาลใจจากคำแนะนำ Alois Mahdal ที่แสดงความคิดเห็นเกี่ยวกับคำตอบอื่น:
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)
ปรับปรุง
หลังจากมองย้อนกลับไปเมื่อไม่นานมานี้ฉันรู้สึกทึ่งกับความเหมือนของมัณฑนากร - เมื่อฉันเริ่มคิดว่าการใช้มันเป็นสิ่งที่จะทำให้มันเป็นเรื่องธรรมดาและมีประโยชน์มากขึ้น (แม้ว่าการทำเช่นนั้นจะทำให้ความสามารถในการอ่านลดลงไปบ้างก็ตาม)
# 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)
โปรดทราบว่าทั้งสองเวอร์ชันทำงานได้ทั้งใน Python 2 และ 3
มีหูดอยู่ในกฎการกำหนดขอบเขตของไพ ธ อน - การกำหนดทำให้ตัวแปรในเครื่องไปยังขอบเขตฟังก์ชันที่ปิดทันที สำหรับตัวแปรส่วนกลางคุณจะแก้ปัญหานี้ด้วยglobal
คำหลัก
วิธีแก้ปัญหาคือการแนะนำออบเจ็กต์ที่ใช้ร่วมกันระหว่างสองขอบเขตซึ่งมีตัวแปรที่ไม่แน่นอน แต่อ้างอิงตัวเองผ่านตัวแปรที่ไม่ได้กำหนด
def outer(v):
def inner(container = [v]):
container[0] += 1
return container[0]
return inner
อีกทางเลือกหนึ่งคือแฮ็กเกอร์ขอบเขตบางส่วน:
def outer(v):
def inner(varname = 'v', scope = locals()):
scope[varname] += 1
return scope[varname]
return inner
คุณอาจสามารถหากลอุบายบางอย่างเพื่อให้ได้ชื่อของพารามิเตอร์ไปouter
แล้วส่งเป็น varname แต่ไม่ต้องอาศัยชื่อที่outer
คุณต้องการต้องใช้ตัวผสม Y
nonlocal
ได้ locals()
สร้างพจนานุกรมouter()
ของชาวบ้านในเวลาที่inner()
ถูกกำหนดไว้ แต่เปลี่ยนพจนานุกรมที่ไม่ได้เปลี่ยนในv
outer()
สิ่งนี้จะไม่ทำงานอีกต่อไปเมื่อคุณมีฟังก์ชันภายในเพิ่มเติมที่ต้องการแชร์ตัวแปรปิด พูดว่าinc()
และdec()
เพิ่มและลดตัวนับที่ใช้ร่วมกัน
nonlocal
เป็นฟีเจอร์python 3
nonlocal
ในหลาม 2 ในทั่วไป ความคิดของคุณจะไม่ครอบคลุมกรณีทั่วไป แต่เพียงอย่างใดอย่างหนึ่งกับหนึ่งฟังก์ชั่นภายใน ดูส่วนสำคัญนี้เป็นตัวอย่าง ฟังก์ชันภายในทั้งสองมีภาชนะของตัวเอง คุณต้องมีวัตถุที่เปลี่ยนแปลงได้ในขอบเขตของฟังก์ชันภายนอกตามที่คำตอบอื่น ๆ แนะนำไปแล้ว
nonlocal
คีย์เวิร์ดที่แนะนำใน Python 3
อีกวิธีในการทำ (แม้ว่าจะดูละเอียดเกินไป):
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
การขยายโซลูชันที่หรูหราของ Martineau ด้านบนไปสู่กรณีการใช้งานที่ใช้งานได้จริงและค่อนข้างหรูหราฉันได้รับ:
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)
ใช้ตัวแปรส่วนกลาง
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
โดยส่วนตัวแล้วฉันไม่ชอบตัวแปรระดับโลก แต่ข้อเสนอของฉันอ้างอิงจากhttps://stackoverflow.com/a/19877437/1083704คำตอบ
def report():
class Rank:
def __init__(self):
report.ranks += 1
rank = Rank()
report.ranks = 0
report()
โดยที่ผู้ใช้ต้องประกาศตัวแปรส่วนกลางranks
ทุกครั้งที่คุณต้องเรียกใช้report
. การปรับปรุงของฉันทำให้ไม่จำเป็นต้องเริ่มต้นตัวแปรฟังก์ชันจากผู้ใช้
inner
แต่ไม่สามารถกำหนดให้กับอินสแตนซ์ได้ แต่คุณสามารถแก้ไขคีย์และค่าได้ สิ่งนี้หลีกเลี่ยงการใช้ตัวแปรส่วนกลาง