क्लासेस — डेटा और व्यवहार एक साथ
class, __init__ और self, मेथड्स, __repr__ से प्रिंट करने योग्य रूप, __eq__ से समानता, क्लास-लेवल बनाम इंस्टेंस-लेवल, और कब क्लास की ज़रूरत नहीं होती।
आप कैसे सीखना चाहते हैं?
वह समस्या जिसे हम हल कर रहे हैं
अब तक किसी एक चीज़ की कई जानकारियाँ एक साथ रखने के लिए हमने डिक्शनरी इस्तेमाल की है।
order = {"name": "pen", "price": 15.0, "quantity": 3}
print(order["nmae"])KeyError: 'nmae'एरर पकड़ में आ गया, अच्छी बात है — लेकिन सिर्फ़ तब, जब वह लाइन चली। अगर यह किसी शर्त के अंदर छिपा होता, तो शायद महीने भर बाद पहली बार सामने आता।
और समस्या सिर्फ़ स्पेलिंग की नहीं है। डिक्शनरी में कोई भी key डाली जा सकती है, इसलिए कहीं लिखा नहीं है कि एक ऑर्डर-लाइन में असल में क्या-क्या होना चाहिए। टोटल निकालने वाला फ़ंक्शन कहीं और है, और उसे कॉल करने से पहले कोई चेक नहीं करता कि डिक्शनरी में सारे फ़ील्ड हैं या नहीं। डेटा एक जगह है, उसके साथ क्या किया जा सकता है वह दूसरी जगह, और कौन किसके साथ जाता है यह सिर्फ़ आपके दिमाग़ में है।
class OrderLine:
def __init__(self, name, price, quantity):
self.name = name
self.price = price
self.quantity = quantity
def total(self):
return round(self.price * self.quantity, 2)
line = OrderLine("pen", 15.0, 3)
print(line.name)
print(line.total())pen
45.0एक क्लास एक साँचा है: वह बताती है कि इस तरह की चीज़ में क्या-क्या होता है, और उसके साथ क्या किया जा सकता है।
इस अध्याय के अंत तक आप कर पाएँगे
classलिखना, और बताना कि__init__औरselfक्या करते हैं- मेथड लिखना, और बताना कि मेथड सामान्य फ़ंक्शन से कैसे अलग हैं
__repr__से प्रिंट करने लायक़ एक रूप देना- क्लास-लेवल और इंस्टेंस-लेवल एट्रिब्यूट का फ़र्क़ बताना — और एक जाने-पहचाने जाल से बचना
__eq__से दो ऑब्जेक्ट्स को तुलना करने लायक़ बनाना- बताना कि कब क्लास की ज़रूरत नहीं है
पहले क्या आना चाहिए: JSON और CSV।
self असल में क्या है
self कोई जादुई शब्द नहीं है — वह बस पहला पैरामीटर है, और उसमें वह ऑब्जेक्ट आता है जिस पर मेथड कॉल किया गया है।
line.total() लिखने का मतलब असल में है “OrderLine का total मेथड चलाओ, और self में line दो”। इसीलिए अंदर self.price लिखने पर ठीक उसी लाइन की क़ीमत मिलती है।
self लिखना भूल जाएँ तो मैसेज पहले उलझन वाला लगता है:
class OrderLine:
def __init__(self, name):
self.name = name
def shout():
return "hello"
line = OrderLine("pen")
print(line.shout())TypeError: OrderLine.shout() takes 0 positional arguments but 1 was given“1 दिया गया” — लेकिन ब्रैकेट तो ख़ाली हैं। दिया गया है ख़ुद line, चुपचाप, और shout ने उसे लेने की जगह नहीं रखी।
__init__ भी एक सामान्य मेथड है, बस नाम ख़ास है: ऑब्जेक्ट बनते ही पायथन उसे कॉल करता है। अंदर self.name = name लिखकर एट्रिब्यूट्स सेट किए जाते हैं।
स्पेलिंग की ग़लती पर एरर ज़्यादा साफ़
class OrderLine:
def __init__(self, name):
self.name = name
line = OrderLine("pen")
print(line.nmae)AttributeError: 'OrderLine' object has no attribute 'nmae'एक बात ईमानदारी से कहनी ज़रूरी है: क्लास यह ग़लती पहले से नहीं पकड़ती। AttributeError भी बिलकुल KeyError की तरह तभी आता है जब लाइन चलती है। शर्त के अंदर छिपा हो तो यह भी महीने भर बाद सामने आ सकता है।
तो फ़ायदा कहाँ है? तीन जगह।
- एरर ज़्यादा बताता है। मैसेज में क्लास का नाम है —
'OrderLine' object— इसलिए दिखता है कि ग़लत नाम किस तरह की चीज़ पर ढूँढा गया। - क्या-क्या होना चाहिए, यह एक जगह लिखा है।
__init__पढ़ते ही दिख जाता है कि एकOrderLineमें कौन-से एट्रिब्यूट्स हैं। डिक्शनरी में ऐसी कोई लिस्ट नहीं होती। - एडिटर मदद कर सकता है। एडिटर क्लास पढ़कर
line.लिखने के बाद एट्रिब्यूट्स के नाम सुझाता है, और कई एडिटर अनजान नाम के नीचे लाइन खींच देते हैं — कोड चलाने से पहले ही। डिक्शनरी की key के लिए एडिटर आम तौर पर यह नहीं कर पाता।
सीमा भी याद रखिए: लिखते समय स्पेलिंग की ग़लती पकड़ में नहीं आती। जैसे order["nmae"] = 5 चुपचाप एक नई key बना देता है, वैसे ही line.nmae = 5 भी चुपचाप एक नया एट्रिब्यूट बना देता है।
और ऑब्जेक्ट के अंदर क्या है, यह देखा भी जा सकता है:
class OrderLine:
def __init__(self, name):
self.name = name
line = OrderLine("pen")
print(type(line))
print(isinstance(line, OrderLine))
print(line.__dict__)<class '__main__.OrderLine'>
True
{'name': 'pen'}__dict__ दिखाता है कि ऑब्जेक्ट के अंदर असल में एक डिक्शनरी ही है। क्लास ने उस डिक्शनरी को हटाया नहीं — उसे एक नाम और एक तय आकार दिया है।
__repr__ — प्रिंट करने पर क्या दिखेगा
__repr__ न लिखें तो:
class OrderLine:
def __init__(self, name):
self.name = name
print(OrderLine("pen"))<__main__.OrderLine object at 0x79b769f03830>आख़िर वाली संख्या मेमरी का पता है, और हर बार चलाने पर अलग आएगी — आपकी स्क्रीन पर कोई और संख्या दिखेगी। डीबग करते समय यह किसी काम की नहीं।
class OrderLine:
def __init__(self, name, price):
self.name = name
self.price = price
def __repr__(self):
return f"OrderLine(name={self.name!r}, price={self.price!r})"
line = OrderLine("pen", 15.0)
print(line)
print([line, line])OrderLine(name='pen', price=15.0)
[OrderLine(name='pen', price=15.0), OrderLine(name='pen', price=15.0)]दूसरी लाइन ही असली फ़ायदा है: लिस्ट के अंदर होने पर भी काम करता है। लिस्ट प्रिंट करते समय पायथन अंदर की हर चीज़ का __repr__ कॉल करता है, __str__ नहीं। इसीलिए दोनों में से __repr__ पहले लिखना चाहिए।
चलन यह है कि ऐसी स्ट्रिंग लौटाएँ जो ऑब्जेक्ट बनाने वाले कोड जैसी दिखे। !r इस्तेमाल करने से अंदर की स्ट्रिंग्स कोट्स के साथ आती हैं — अध्याय 24 वाली उसी वजह से।
समानता — डिफ़ॉल्ट वह नहीं जो आप चाहते हैं
class OrderLine:
def __init__(self, name):
self.name = name
a = OrderLine("pen")
b = OrderLine("pen")
print(a == b)
print(a == a)False
Trueदोनों ऑब्जेक्ट्स के अंदर एक ही चीज़ है, फिर भी == False देता है। क्योंकि डिफ़ॉल्ट समानता का मतलब है पहचान — तब == बिलकुल is की तरह बर्ताव करता है, यानी पूछता है “क्या यह ठीक वही ऑब्जेक्ट है?”।
बदलना हो तो बताना पड़ता है कि बराबर का मतलब क्या है:
class OrderLine:
def __init__(self, name):
self.name = name
def __eq__(self, other):
return self.name == other.name
a = OrderLine("pen")
b = OrderLine("pen")
print(a == b)
print(a is b)True
Falseअब == कहता है “एक जैसे” और is कहता है “एक ही ऑब्जेक्ट” — अध्याय 21 वाला वही फ़र्क़, इस बार आपके अपने बनाए टाइप में।
एक सावधानी: __eq__ लिखने पर ऑब्जेक्ट डिफ़ॉल्ट रूप से हैशेबल नहीं रहता, इसलिए उसे सेट में या डिक्शनरी की key के रूप में नहीं रखा जा सकता। अगले अध्याय में देखेंगे कि dataclass ये दोनों काम एक लाइन में कर देता है।
क्लास-लेवल और इंस्टेंस-लेवल
class Basket:
TAX_RATE = 0.15
items = []
def add(self, name):
self.items.append(name)
a = Basket()
b = Basket()
a.add("pen")
b.add("bag")
print(a.items)
print(b.items)
print(a.items is b.items)['pen', 'bag']
['pen', 'bag']
Trueदो अलग टोकरियाँ, लेकिन लिस्ट एक ही।
पहचाना? यह अध्याय 21 वाला म्यूटेबल डिफ़ॉल्ट का जाल ही है, बस दूसरी जगह पर। class लाइन के ठीक नीचे लिखा items = [] सिर्फ़ एक बार चलता है, जब क्लास बनती है — हर ऑब्जेक्ट के लिए अलग से नहीं। इसलिए लिस्ट क्लास की है, और सारे इंस्टेंस उसी को साझा करते हैं।
हल भी उसी तरह का है: जो हर ऑब्जेक्ट का अपना है, उसे __init__ के अंदर बनाइए।
class Basket:
TAX_RATE = 0.15
def __init__(self):
self.items = []
def add(self, name):
self.items.append(name)
a = Basket()
b = Basket()
a.add("pen")
b.add("bag")
print(a.items)
print(b.items)
print(a.TAX_RATE, b.TAX_RATE)['pen']
['bag']
0.15 0.15TAX_RATE क्लास-लेवल पर ही रहा, और यही सही है — यह हर टोकरी के लिए एक जैसा है, और हम इसे बदलना नहीं चाहते। लेकिन ध्यान दीजिए, पायथन इसे बदलने से रोकता नहीं: Basket.TAX_RATE = 0.2 लिखने पर कोई एरर नहीं आएगा। पूरे बड़े अक्षरों वाला नाम सिर्फ़ एक चलन है — पढ़ने वाले से कहता है “इसे कॉन्स्टेंट मानिए, बदलिए मत”। नियम एक लाइन में: क्लास-लेवल पर सिर्फ़ कॉन्स्टेंट; जो बदलता है वह सब __init__ में।
कब क्लास न लिखें
क्लास सीखने के बाद हर चीज़ को क्लास बनाने का मन करता है। कुछ संकेत, जहाँ क्लास फ़ालतू है:
क्लास में सिर्फ़ __init__ और एक मेथड है। वह असल में एक फ़ंक्शन है, बेवजह दो हिस्सों में लिखा हुआ।
कोई मेथड नहीं, सिर्फ़ डेटा। अगले अध्याय का dataclass ठीक इसी के लिए है, और उसमें लाइनें बहुत कम लगती हैं।
ऑब्जेक्ट कभी नहीं बदलता और एक ही चीज़ रखता है। एक टपल, या एक सादी वैल्यू ही काफ़ी है।
क्लास की असली जगह वहाँ है जहाँ डेटा और बर्ताव सच में साथ-साथ रहते हैं: कुछ डेटा जो बदलता है, और कुछ काम जो हमेशा उसी डेटा पर चलते हैं।
एक पूरा उदाहरण
"""An order built from two small classes."""
TAX_RATE = 0.15
class OrderLine:
"""One item on an order: what it is, what it costs, how many."""
def __init__(self, name, price, quantity):
if quantity < 1:
raise ValueError(f"quantity must be at least 1: {quantity}")
self.name = name
self.price = price
self.quantity = quantity
def total(self):
"""Price for this line, tax included."""
return round(self.price * self.quantity * (1 + TAX_RATE), 2)
def __repr__(self):
return f"OrderLine({self.name!r}, {self.price!r}, {self.quantity!r})"
class Order:
"""A named collection of lines, and the questions you ask about it."""
def __init__(self, customer):
self.customer = customer
self.lines = []
def add(self, name, price, quantity):
self.lines.append(OrderLine(name, price, quantity))
return self
def total(self):
return round(sum(line.total() for line in self.lines), 2)
def largest(self):
"""The most expensive line, or None for an empty order."""
if not self.lines:
return None
return max(self.lines, key=lambda line: line.total())
def __repr__(self):
return f"Order({self.customer!r}, {len(self.lines)} lines)"
def main():
order = Order("rafi")
order.add("pen", 15.0, 3)
order.add("bag", 850.0, 1)
order.add("ink", 120.0, 2)
for line in order.lines:
print(f"{line.name:<6} {line.total():>9.2f}")
print(f"{'total':<6} {order.total():>9.2f}")
print()
print(order)
print("largest:", order.largest())
try:
order.add("clip", 5.0, 0)
except ValueError as err:
print("rejected:", err)
if __name__ == "__main__":
main()pen 51.75
bag 977.50
ink 276.00
total 1305.25
Order('rafi', 3 lines)
largest: OrderLine('bag', 850.0, 1)
rejected: quantity must be at least 1: 0चार बातें ध्यान देने लायक़ हैं।
__init__ जाँच करता है। quantity < 1 हो तो ऑब्जेक्ट बनता ही नहीं। यही क्लास का सबसे काम का फ़ायदा है: एक OrderLine का होना ही बताता है कि उसकी मात्रा कम से कम एक है — इस्तेमाल की हर जगह फिर से अलग जाँच नहीं करनी पड़ती। डिक्शनरी यह गारंटी नहीं दे सकती।
Order नहीं जानता कि एक लाइन की क़ीमत कैसे निकलती है। वह line.total() कॉल करता है, बस। टैक्स कैसे लगता है यह OrderLine का मामला है, और बदलना हो तो एक ही जगह बदलना पड़ता है।
__repr__ दोनों क्लासेस में है, और largest() के आउटपुट में ही उसकी अहमियत दिख जाती है। वह न होता तो आख़िरी लाइन एक मेमरी का पता होती — जिससे कुछ पता नहीं चलता।
add ख़ुद को ही लौटाता है (return self)। इससे order.add(...).add(...) लिखना मुमकिन होता है। यहाँ उसे इस्तेमाल नहीं किया गया, और यह जानबूझकर है — सुविधा होना और उससे हर लाइन लंबी कर देना एक बात नहीं है।
जब कुछ टूटता है
TypeError: ... takes 0 positional arguments but 1 was given मेथड में self लिखना भूल गए हैं। पहले पैरामीटर के रूप में self जोड़िए।
AttributeError: 'X' object has no attribute 'y' स्पेलिंग ग़लत है, या __init__ में self.y कभी सेट ही नहीं हुआ। print(obj.__dict__) से देखिए कि असल में क्या-क्या है।
TypeError: X() takes no arguments __init__ लिखा नहीं गया, या नाम की स्पेलिंग ग़लत है (__int__ नहीं, __init__ — दोनों तरफ़ दो-दो अंडरस्कोर)।
सारे ऑब्जेक्ट्स एक ही लिस्ट साझा कर रहे हैं लिस्ट क्लास-लेवल पर रखी गई है। उसे __init__ के अंदर ले जाइए।
दो ऑब्जेक्ट्स में एक ही चीज़ है, फिर भी == False है डिफ़ॉल्ट समानता का मतलब पहचान है। __eq__ लिखिए — या अगले अध्याय का dataclass इस्तेमाल कीजिए।
__eq__ लिखने के बाद सेट में नहीं रख पा रहे __eq__ लिखने पर डिफ़ॉल्ट हैश चला जाता है। dataclass(frozen=True) दोनों देता है।
प्रिंट करने पर <__main__.X object at 0x...> आता है __repr__ लिखा नहीं गया।
चरण 4 / 6 — अनुमान
अपनी समझ की जाँच करें
shout() को बिना किसी आर्ग्युमेंट के कॉल किया गया है। क्या होगा?
class OrderLine:
def __init__(self, name):
self.name = name
def shout():
return "hello"
line = OrderLine("pen")
print(line.shout())- Aएक `TypeError` — यह एक लेता है, और शून्य दिए गए
- Bएक `TypeError` — यह शून्य लेता है, और एक दिया गया
- Cएक `AttributeError`
- D`hello` प्रिंट होता है
दो अलग टोकरियाँ, हर एक में एक चीज़। क्या प्रिंट होगा?
class Basket:
items = []
def add(self, name):
self.items.append(name)
a = Basket()
b = Basket()
a.add("pen")
b.add("bag")
print(a.items)
print(b.items)- A['pen', 'bag'] []
- B['pen'] ['pen', 'bag']
- C['pen'] ['bag']
- D['pen', 'bag'] ['pen', 'bag']
दोनों ऑब्जेक्ट्स में एक ही नाम है। क्या प्रिंट होगा?
class OrderLine:
def __init__(self, name):
self.name = name
a = OrderLine("pen")
b = OrderLine("pen")
print(a == b)
print(a == a)- AFalse True
- Bएक `TypeError`
- CFalse False
- DTrue True
उत्तर देने के लिए अकाउंट आवश्यक है
अपने उत्तर जाँचने के लिए साइन इन करें
प्रश्न ऊपर दिए गए हैं, और मन में उत्तर सोचना ही मुख्य कार्य है। सही उत्तर, व्याख्या और तीन-स्तरीय संकेत देखने के लिए साइन इन करें।
आपकी बारी
दो क्लासेस लिखिए।
Book — title, author और pages से बनेगी। pages एक से कम हो तो ValueError रेज़ करेगी। इसमें एक __repr__ होगा, और एक __eq__ होगा जो टाइटल और लेखक मिलने पर दो किताबों को बराबर कहेगा।
Shelf — किताबें जमा करेगी, और देगी: add(book), total_pages(), longest(), और by_author(name) जो एक लिस्ट लौटाएगा।
फिर पाँच प्रयोग:
Shelfकीbooks = []लाइन को__init__से बाहर,classलाइन के नीचे ले जाइए। दो अलग शेल्फ़ बनाकर एक में किताब जोड़िए, फिर दूसरी को प्रिंट कीजिए। क्या दिखता है?__repr__मिटा दीजिए, फिर किताबों की एक लिस्ट प्रिंट कीजिए। डीबग करना कितना मुश्किल हुआ?__eq__मिटा दीजिए, फिर एक ही टाइटल-लेखक वाली दो किताबों की तुलना कीजिए।- किसी एक मेथड से
selfहटा दीजिए और उसे कॉल कीजिए। मैसेज कितने आर्ग्युमेंट्स की बात करता है? - एक
Bookबनाकरbook.pagse = 100लिखिए (जानबूझकर ग़लत स्पेलिंग)। कोई एरर आता है? फिरprint(book.__dict__)कीजिए — क्या दिखता है?
आख़िरी प्रयोग ज़रूरी है, क्योंकि वह क्लास की सीमा दिखाता है। पढ़ते समय ग़लत नाम पर एरर आता है, लिखते समय नहीं — नया एट्रिब्यूट सेट करना हमेशा चलता है। अगले अध्याय में देखेंगे कि टाइप हिंट्स और एक टाइप चेकर यह कमी भी दूर कर सकते हैं।
चरण 6 / 6
चुनौती — अध्याय की क्विज़
सरल से कठिन — दस प्रश्न, अंतिम वाले जानबूझकर चुनौतीपूर्ण बनाए गए हैं।
क्विज़ देने के लिए साइन इन करें