स्कोप और म्युटेबल डिफ़ॉल्ट्स
एक नाम कहाँ रहता है और उसे कौन देख सकता है, बाहरी नाम को पढ़ने और लिखने का अंतर, UnboundLocalError, और पायथन का सबसे प्रसिद्ध जाल — डिफ़ॉल्ट के रूप में खाली लिस्ट।
- 1समस्या
- 2समझें
- 3उदाहरण
- 4अनुमान
- 5स्वयं करें
- 6चुनौती
वह समस्या जिसे हम हल कर रहे हैं
अध्याय 19 के "जब यह टूटता है" खंड में एक पंक्ति अधूरी रह गई थी: "फ़ंक्शन के अंदर का वेरिएबल बाहर दिखाई नहीं देता — यह अपेक्षित है, और इसका कारण अध्याय 21 का विषय है।"
यह वही अध्याय है।
def add_tax(price):
tax = price * 0.15
return price + tax
print(add_tax(100))
print(tax)115.0
NameError: name 'tax' is not defined. Did you mean: 'max'?पहली पंक्ति ने काम किया, इसलिए tax अस्तित्व में रहा होगा। और बाहर, यह मौजूद नहीं है।
यह एक सीमा नहीं बल्कि सुरक्षा है। पचास फ़ंक्शंस वाले प्रोग्राम में, यदि प्रत्येक आंतरिक नाम बाहर लीक हो जाता, तो दो फ़ंक्शंस के total वेरिएबल एक-दूसरे को ओवरराइट कर देते — और किसी को पता भी नहीं चलता। फ़ंक्शन के अंदर के नाम अंदर ही रहते हैं, इसलिए एक फ़ंक्शन लिखने के लिए अन्य उनचास के नामों को जानने की आवश्यकता नहीं होती है।
यह अध्याय उसी नियम के बारे में है, और इसका एक परिणाम पायथन का सबसे प्रसिद्ध जाल है।
इस मिशन के अंत तक आप सीखेंगे
- यह बताना कि कोई नाम कहाँ "रहता है" और उसे कौन देख सकता है
- फ़ंक्शन के अंदर से किसी बाहरी नाम को पढ़ना, और यह समझना कि आप बाहरी नाम में लिख क्यों नहीं सकते
UnboundLocalErrorको पढ़ना और यह समझना कि क्या हुआ- यह समझाना कि फ़ंक्शन में पास की गई सूची बाहर बदली हुई क्यों वापस आ सकती है
- पायथन के सबसे प्रसिद्ध जाल को पहचानना और उससे बचना: म्युटेबल डिफ़ॉल्ट (mutable default)
पूर्वापेक्षाएँ: फ़ंक्शन के तर्क.
अंदर की चीज़ अंदर रहती है
फ़ंक्शन के अंदर बनाया गया प्रत्येक नाम — जिसमें पैरामीटर भी शामिल हैं — केवल उस कॉल के लिए जीवित रहता है। जब फ़ंक्शन समाप्त होता है, तो वे नाम समाप्त हो जाते हैं।
इसे स्कोप (Scope) कहा जाता है: वह क्षेत्र जिसमें कोई नाम पहचाना जाता है।
किसी बाहरी नाम को पढ़ना काम करता है
RATE = 0.15
def add_tax(price):
return price + price * RATE
print(add_tax(100))115.0फ़ंक्शन के अंदर कोई RATE नहीं है, इसलिए पायथन बाहर देखता है और उसे एक मिल जाता है। अंदर से किसी बाहरी नाम को पढ़ना पूरी तरह ठीक है।
सभी बड़े अक्षरों वाला नाम (ALL_CAPS) एक परंपरा है — यह पाठक को बताता है "यह एक स्थिरांक (constant) है, यह बदलता नहीं है"।
लेकिन बाहरी नाम में लिखना काम नहीं करता
count = 10
def bump():
count = 99
return count
print(bump())
print(count)99
10आंतरिक count = 99 ने कभी भी बाहरी count को नहीं छुआ। इसने एक नया, स्थानीय (local) नाम बनाया जो केवल फ़ंक्शन के अंदर रहता है और उस दौरान बाहरी नाम को छुपा देता है।
और इसी से एक अजीब सी दिखने वाली त्रुटि सामने आती है:
count = 10
def bump():
count = count + 1
return count
print(bump())UnboundLocalError: cannot access local variable 'count' where it is not associated with a valueपंक्ति ऐसी लगती है जैसे यह बाहरी count लेती है और एक जोड़ती है। लेकिन पायथन फ़ंक्शन को चलाने से पहले उसका पूरा निरीक्षण करता है, देखता है कि कुछ count को असाइन कर रहा है, और निर्णय लेता है कि count शुरू से अंत तक इस फ़ंक्शन के लिए लोकल है।
फिर, दाईं ओर count + 1 का मूल्यांकन करते समय, यह लोकल count की तलाश करता है, जिसे अभी तक कोई मान नहीं दिया गया है — और रुक जाता है।
इसका उपाय लगभग हमेशा एक ही होता है: मान को एक तर्क के रूप में लें और परिणाम को return करें।
def bump(count):
return count + 1
count = bump(count)लेकिन जो वस्तु आपने पास की है वह बदल सकती है
स्कोप नामों (names) की रक्षा करता है, वस्तुओं (objects) की नहीं।
def add_item(items):
items.append("pen")
return items
basket = []
add_item(basket)
add_item(basket)
print(basket)['pen', 'pen']बाहरी basket बदल गया, भले ही कोई return संग्रहीत न किया गया हो।
कारण अध्याय 12 वाला ही है: items नाम नया है, लेकिन यह उसी सूची की ओर इशारा करता है। append उस सूची को संशोधित करता है, और दोनों नाम परिवर्तन दिखाते हैं।
तुलना करें:
def replace(items):
items = ["new"]
return items
basket = ["old"]
result = replace(basket)
print(basket)
print(result)['old']
['new']यहाँ items = [...] ने एक नया मान असाइन किया, लोकल नाम को एक अलग सूची की ओर मोड़ दिया। बाहरी basket वहीं है जहाँ वह था।
एक पंक्ति में नियम: वस्तु को संशोधित करना (append, sort, [i] =) बाहर दिखाई देता है; नाम को किसी अन्य वस्तु की ओर इंगित करना (=) बाहर दिखाई नहीं देता।
पायथन का सबसे प्रसिद्ध जाल
def add_item(item, basket=[]):
basket.append(item)
return basket
print(add_item("pen"))
print(add_item("bag"))
print(add_item("ink"))['pen']
['pen', 'bag']
['pen', 'bag', 'ink']तीन अलग-अलग कॉल्स, और प्रत्येक कॉल वह रखती है जो पिछली वाली ने छोड़ा था।
कारण को समझें: डिफ़ॉल्ट मान का मूल्यांकन केवल एक बार किया जाता है, जब def पंक्ति चलती है — तब नहीं जब फ़ंक्शन को कॉल किया जाता है। वह खाली सूची फ़ंक्शन से जुड़ी एक ही सूची है, और प्रत्येक कॉल इसे साझा करती है।
समाधान सरल और पारंपरिक है:
def add_item(item, basket=None):
if basket is None:
basket = []
basket.append(item)
return basket
print(add_item("pen"))
print(add_item("bag"))['pen']
['bag']None अपरिवर्तनीय है, इसलिए इसे साझा करने में कोई हर्ज नहीं है, और नई सूची प्रत्येक कॉल पर ताज़ा बनाई जाती है।
एक नियम के रूप में: कभी भी डिफ़ॉल्ट मान के रूप में लिस्ट, डिक्शनरी या सेट न लिखें।
एक संपूर्ण उदाहरण
basket.py:
# एक नाम कहाँ रहता है यह तय करता है कि उसे कौन देख सकता है और कौन बदल सकता है
TAX_RATE = 0.15
def line_total(price, quantity):
"""Pure: मान लेता है, मान लौटाता है, बाहर कुछ भी नहीं छूता।"""
return round(price * quantity * (1 + TAX_RATE), 2)
def add_line(basket, name, price, quantity):
"""जानबूझकर impure: यह उस बास्केट को बदलता है जो इसे सौंपी गई थी।"""
basket.append({"name": name, "total": line_total(price, quantity)})
return basket
def summarise(basket, note=None):
"""डिफ़ॉल्ट के रूप में `None`, ताकि कॉल्स के बीच कोई सूची साझा न हो।"""
notes = [] if note is None else [note]
total = sum(line["total"] for line in basket)
notes.append(f"{len(basket)} lines, {total:.2f} in total")
return notes
shopping = []
add_line(shopping, "pen", 15.0, 3)
add_line(shopping, "bag", 850.0, 1)
for line in shopping:
print(f"{line['name']:<6} {line['total']:>8.2f}")
print()
for note in summarise(shopping, "morning order"):
print(note)
print()
print("basket outside the function:", len(shopping), "lines")pen 51.75
bag 977.50
morning order
2 lines, 1029.25 in total
basket outside the function: 2 linesजब यह टूटता है
NameError: name 'tax' is not defined फ़ंक्शन के अंदर के नाम का बाहर उपयोग किया गया था। बाहर जो भी चाहिए उसे return करें।
UnboundLocalError: cannot access local variable ... where it is not associated with a value फ़ंक्शन के अंदर एक नाम को असाइन किया गया है और उस असाइनमेंट से पहले पढ़ा गया है। मान को एक तर्क के रूप में लें और परिणाम को return करें।
फ़ंक्शन में पास करने के बाद मेरी लिस्ट बदल गई यह अपेक्षित व्यवहार है — नाम नया है, लिस्ट वही है। इससे बचने के लिए फ़ंक्शन के शीर्ष पर items = items.copy() लिखें।
फ़ंक्शन अपने पिछले परिणामों को याद रखता है म्युटेबल डिफ़ॉल्ट। def f(x, acc=[]) नहीं बल्कि def f(x, acc=None) लिखें, अंदर if acc is None: acc = [] के साथ।
चरण 4 / 6 — अनुमान
अपनी समझ की जाँच करें
A value is assigned to count inside the function. What do the two lines print?
count = 10
def bump():
count = 99
return count
print(bump())
print(count)- A99 10
- B99 99
- C10 10
- DAn `UnboundLocalError`
There is no return, yet the outer list is printed. What appears?
def add_item(items):
items.append("pen")
basket = []
add_item(basket)
add_item(basket)
print(basket)- A['pen', 'pen']
- B[]
- C['pen']
- DNone
Two separate calls, with an empty list as the default. What is printed?
def collect(item, box=[]):
box.append(item)
return box
print(collect("pen"))
print(collect("bag"))- A['pen'] ['pen', 'bag']
- B['pen'] ['bag']
- C['pen', 'bag'] ['pen', 'bag']
- D['pen'] ['pen']
उत्तर देने के लिए अकाउंट आवश्यक है
अपने उत्तर जाँचने के लिए साइन इन करें
प्रश्न ऊपर दिए गए हैं, और मन में उत्तर सोचना ही मुख्य कार्य है। सही उत्तर, व्याख्या और तीन-स्तरीय संकेत देखने के लिए साइन इन करें।
आपकी बारी
तीन फ़ंक्शंस वाली cart.py नामक फ़ाइल लिखें:
line_total(price, quantity, tax_rate=0.15)— शुद्ध (pure), बाहर कुछ भी नहीं छूताadd_to(cart, name, price, quantity)— सौंपी गई कार्ट में जोड़ता हैreceipt(cart, header=None)— पंक्तियों की एक सूची लौटाता है, जब कोई शीर्षक न दिया गया हो तो एक शीर्षक तैयार करता है
फिर एक खाली कार्ट बनाएं, कुछ चीज़ें जोड़ें, और रसीद प्रिंट करें।
अंत में, तीन प्रयोग:
receiptके डिफ़ॉल्ट कोheader=[]में बदलें, फिर फ़ंक्शन को दो बार कॉल करें और दोनों परिणाम प्रिंट करें। दूसरी बार आपको क्या दिखाई देता है?add_toकी पहली पंक्ति के रूप मेंcart = cart.copy()जोड़ें, फिर बाहरी कार्ट प्रिंट करें। इसमें कितनी चीज़ें हैं, और क्यों?- किसी फ़ंक्शन के अंदर
TAX_RATE = 0.2असाइन करें, फिर उसके बाहरTAX_RATEप्रिंट करें। आपको कौन सा मान दिखाई देता है?
Step 6 of 6
चुनौती — the chapter quiz
सरल से कठिन — दस प्रश्न, अंतिम वाले जानबूझकर चुनौतीपूर्ण बनाए गए हैं।
Sign in to take the quiz