अध्याय 26

क्लासेस — डेटा और व्यवहार एक साथ

class, __init__ और self, मेथड्स, __repr__ से प्रिंट करने योग्य रूप, __eq__ से समानता, क्लास-लेवल बनाम इंस्टेंस-लेवल, और क्लास की आवश्यकता कब नहीं होती।

38 मिनटPython 3.12
  1. 1समस्या
  2. 2समझें
  3. 3उदाहरण
  4. 4अनुमान
  5. 5स्वयं करें
  6. 6चुनौती

वह समस्या जिसे हम हल कर रहे हैं

अब तक हमने किसी एक चीज़ के कई गुणों को एक डिक्शनरी में रखा है।

python
order = {"name": "pen", "price": 15.0, "quantity": 3}

print(order["nmae"])
text
KeyError: 'nmae'

त्रुटि पकड़ी गई — लेकिन केवल तब जब वह पंक्ति चली।

और समस्या केवल वर्तनी (spelling) की नहीं है। डिक्शनरी में कोई भी की डाली जा सकती है, इसलिए कहीं भी यह नहीं लिखा है कि एक ऑर्डर लाइन में वास्तव में क्या होना चाहिए। इसका योग करने वाला फ़ंक्शन कहीं और है। डेटा एक जगह है, उसके साथ क्या किया जा सकता है वह दूसरी जगह है।

python
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())
text
pen
45.0

एक क्लास (Class) एक सांचा (mould) है: यह बताती है कि इस प्रकार की चीज़ में क्या है, और इसके साथ क्या किया जा सकता है।

इस मिशन के अंत तक आप सीखेंगे

  • एक class लिखना, और यह बताना कि __init__ और self क्या करते हैं
  • मेथड्स (methods) लिखना
  • __repr__ के साथ किसी ऑब्जेक्ट को प्रिंट करने योग्य रूप देना
  • क्लास एट्रिब्यूट और इंस्टेंस एट्रिब्यूट के बीच अंतर समझना
  • __eq__ के साथ दो ऑब्जेक्ट्स को तुलनीय बनाना
  • यह बताना कि क्लास की आवश्यकता कब नहीं होती

पूर्वापेक्षाएँ: JSON और CSV.


self वास्तव में क्या है

self कोई जादुई शब्द नहीं है — यह पहला पैरामीटर है, और इसमें वह ऑब्जेक्ट आता है जिस पर मेथड को कॉल किया गया था।

line.total() लिखने का अर्थ है "OrderLine का total मेथड चलाएं, और इसे self के रूप में line दें"।

इसे भूल जाने पर संदेश पहली नज़र में अजीब लगता है:

python
class OrderLine:
    def __init__(self, name):
        self.name = name

    def shout():
        return "hello"


line = OrderLine("pen")
print(line.shout())
text
TypeError: OrderLine.shout() takes 0 positional arguments but 1 was given

"1 was given" — लेकिन कोष्ठक खाली थे! जो दिया गया वह स्वयं line ऑब्जेक्ट था, चुपचाप।

__init__ भी एक सामान्य मेथड है, एक विशेष नाम के साथ: ऑब्जेक्ट बनने के तुरंत बाद पायथन इसे कॉल करता है। इसके अंदर, self.name = name एट्रिब्यूट्स सेट करता है।

__repr__ — प्रिंट करने पर क्या दिखता है

__repr__ के बिना:

python
class OrderLine:
    def __init__(self, name):
        self.name = name


print(OrderLine("pen"))
text
<__main__.OrderLine object at 0x00000213E88A9DF0>

अंत में दी गई संख्या एक मेमोरी एड्रेस है। डिबगिंग करते समय यह किसी काम की नहीं होती।

__repr__ के साथ:

python
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])
text
OrderLine(name='pen', price=15.0)
[OrderLine(name='pen', price=15.0), OrderLine(name='pen', price=15.0)]

दूसरी पंक्ति असली लाभ है: यह एक सूची के अंदर भी काम करती है।

समानता (__eq__) — डिफ़ॉल्ट वह नहीं है जो आप चाहते हैं

python
class OrderLine:
    def __init__(self, name):
        self.name = name


a = OrderLine("pen")
b = OrderLine("pen")

print(a == b)
print(a == a)
text
False
True

दोनों ऑब्जेक्ट्स में एक ही चीज़ है, फिर भी == असत्य (False) है। क्योंकि डिफ़ॉल्ट समानता पहचान (identity) है — == बिल्कुल is की तरह व्यवहार करता है।

इसे बदलना:

python
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)
text
True
False

क्लास स्तर और इंस्टेंस स्तर

python
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)
text
['pen', 'bag']
['pen', 'bag']
True

दो अलग-अलग बास्केट, एक ही लिस्ट।

यह अध्याय 21 का म्युटेबल डिफ़ॉल्ट जाल है। class पंक्ति के ठीक नीचे items = [] केवल एक बार चलता है, जब क्लास बनाई जाती है। इसलिए सूची क्लास की होती है, और प्रत्येक इंस्टेंस इसे साझा करता है।

समाधान: जो कुछ भी प्रत्येक ऑब्जेक्ट का है, उसे __init__ के अंदर बनाएं।

python
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)
text
['pen']
['bag']
0.15 0.15

नियम: स्थिरांक (constants) क्लास स्तर पर; सब कुछ परिवर्तनशील (mutable) __init__ में।


एक संपूर्ण उदाहरण

python
"""दो छोटी कक्षाओं से बना एक ऑर्डर।"""

TAX_RATE = 0.15


class OrderLine:
    """ऑर्डर पर एक वस्तु: यह क्या है, इसकी कीमत क्या है, कितनी संख्या है।"""

    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):
        """टैक्स सहित इस लाइन की कीमत।"""
        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:
    """लाइनों का एक संग्रह, और इसके बारे में पूछे जाने वाले प्रश्न।"""

    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):
        """सबसे महंगी लाइन, या खाली ऑर्डर के लिए None।"""
        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()
text
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__ सत्यापन (validation) करता है। quantity < 1 के साथ ऑब्जेक्ट कभी बनता ही नहीं है।

Order यह नहीं जानता कि लाइन की कीमत कैसे तय होती है। यह बस line.total() को कॉल करता है।

दोनों कक्षाओं में __repr__ है।


जब यह टूटता है

TypeError: ... takes 0 positional arguments but 1 was given मेथड में self छूट गया है। इसे पहले पैरामीटर के रूप में दें।

AttributeError: 'X' object has no attribute 'y' वर्तनी की गलती, या self.y को __init__ में सेट नहीं किया गया था।

TypeError: X() takes no arguments कोई __init__ नहीं है, या नाम की वर्तनी गलत है (दोनों तरफ दो अंडरस्कोर)।

प्रत्येक ऑब्जेक्ट एक ही सूची साझा कर रहा है सूची क्लास स्तर पर रखी गई है। इसे __init__ के अंदर ले जाएं।