मूल बातें

Python में क्लास और OOP: __init__, self, मेथड, इनहेरिटेंस और dataclass

अपडेट किया गया: · Skillaria.Top

Python में OOP क्लास पर आधारित है: क्लास फ़ील्ड और मेथड बताती है, और ऑब्जेक्ट (इंस्टेंस) क्लास को बुलाकर बनता है। __init__ फ़ील्ड भरता है, और self उसी ऑब्जेक्ट की ओर इशारा करता है। इनहेरिटेंस से पैरेंट का व्यवहार लेकर कुछ मेथड बदले जा सकते हैं, और @dataclass डेटा रखने वाली क्लास के लिए __init__, __repr__ और __eq__ अपने-आप बनाता है।

Python में क्लास एक ब्लूप्रिंट है: वह बताती है कि ऑब्जेक्ट कौन-सा डेटा रखेगा और उसके साथ क्या किया जा सकता है। ऑब्जेक्ट (इंस्टेंस) उस ब्लूप्रिंट से बना एक असली नमूना है। Python OOP तीन चीज़ों पर टिका है: मेथड वाली क्लासें, इनहेरिटेंस, और __init__ व __repr__ जैसे स्पेशल मेथड। एक ही उदाहरण — ख़र्चों का हिसाब — पर सब कुछ क्रम से देखते हैं।

जब डिक्शनरी है, तो क्लास क्यों

डेटा को डिक्शनरी में रखा जा सकता है: {"title": "कॉफ़ी", "amount": 250}। दिक़्क़त यह है कि डिक्शनरी नहीं जानती कि सही कुंजियाँ कौन-सी हैं। expense.get("amout", 0) जैसी टाइपो पर कोई एरर नहीं आती — शून्य मिल जाता है, और रिपोर्ट चुपचाप ग़लत आँकड़े दिखाने लगती है।

क्लास ऑब्जेक्ट के फ़ील्ड पहले से तय करती है। जो एट्रिब्यूट मौजूद नहीं, उस पर AttributeError तुरंत आता है, और आधुनिक Python सही नाम भी सुझाता है। इसके अलावा क्लास डेटा और उस पर होने वाली क्रियाएँ एक ही जगह रखती है: ख़र्च ख़ुद टैक्स के साथ राशि निकाल सकता है, और रिपोर्ट ख़ुद कुल जोड़ सकती है।

Python में क्लास कैसे बनाएँ

क्लास class शब्द से घोषित होती है, और नाम PEP 8 के अनुसार CamelCase में लिखा जाता है। सबसे सरल क्लास:

class Expense:
    def __init__(self, title, amount):
        self.title = title
        self.amount = amount

coffee = Expense("कॉफ़ी", 250)
taxi = Expense("टैक्सी", 300)
print(coffee.title, coffee.amount)   # कॉफ़ी 250

Expense("कॉफ़ी", 250) क्लास को बुलाकर एक नया इंस्टेंस बनाता है। coffee और taxi एक ही प्रकार के दो अलग ऑब्जेक्ट हैं, और हर एक के फ़ील्ड की अपनी वैल्यू है। जाँचना हो कि ऑब्जेक्ट किस क्लास का है, तो isinstance(coffee, Expense) True देगा।

कंस्ट्रक्टर __init__: क्लास को शुरू करना

__init__ को अक्सर क्लास का कंस्ट्रक्टर कहते हैं, पर सटीक शब्द है इनिशियलाइज़र। ऑब्जेक्ट खुद __new__ बनाता है, और उसके तुरंत बाद __init__ चलता है और दिए गए आर्गुमेंट फ़ील्ड में रखता है। उससे कुछ लौटाने की ज़रूरत नहीं (सही कहें तो उसे None ही लौटाना होता है)।

__init__ में इनपुट जाँचना सुविधाजनक है, ताकि प्रोग्राम में गलत ऑब्जेक्ट न बनें:

class Expense:
    def __init__(self, title, amount):
        if amount < 0:
            raise ValueError("राशि ऋणात्मक नहीं हो सकती")
        self.title = title
        self.amount = amount

Python में self क्या है

self उस ऑब्जेक्ट का संदर्भ है, जिस पर मेथड अभी काम कर रहा है। जब आप coffee.with_tax() लिखते हैं, तो Python असल में Expense.with_tax(coffee) बुलाता है और ऑब्जेक्ट को पहले आर्गुमेंट के रूप में देता है। इसलिए मेथड की घोषणा में self पहला होता है, और बुलाते समय उसे नहीं लिखा जाता।

self कोई कीवर्ड नहीं, एक परंपरा है: तकनीकी रूप से पैरामीटर का नाम कुछ भी हो सकता है, पर कोई ऐसा नहीं करता। नए छात्रों की आम गलती है मेथड की घोषणा में self भूल जाना। तब कॉल पर TypeError: Expense.with_tax() takes 0 positional arguments but 1 was given आता है।

मेथड और स्पेशल मेथड

मेथड क्लास के भीतर घोषित फ़ंक्शन है। वह self से फ़ील्ड देखता है:

class Expense:
    def __init__(self, title, amount):
        self.title = title
        self.amount = amount

    def with_tax(self, rate=0.2):
        return self.amount * (1 + rate)

    def __repr__(self):
        return f"Expense({self.title!r}, {self.amount})"

coffee = Expense("कॉफ़ी", 250)
print(coffee.with_tax())   # 300.0
print(coffee)              # Expense('कॉफ़ी', 250)

बिना __repr__ के छपाई <__main__.Expense object at 0x7f...> जैसी होती — डीबगिंग के लिए बेकार। दोनों ओर दो अंडरस्कोर वाले मेथड को स्पेशल (dunder) मेथड कहते हैं: Python उन्हें ख़ुद बुलाता है। __str__ उपयोगकर्ता के लिए सुंदर आउटपुट देता है, __eq__ == से तुलना करता है, और __len__ len(obj) के लिए है।

@classmethod और @staticmethod

साधारण मेथड इंस्टेंस के साथ काम करता है। @classmethod वाले मेथड को पहला आर्गुमेंट स्वयं क्लास (cls) मिलती है — इसे अक्सर वैकल्पिक कंस्ट्रक्टर के रूप में लिखते हैं। @staticmethod को न ऑब्जेक्ट मिलता है न क्लास: वह साधारण फ़ंक्शन है, जो तर्कसंगत रूप से क्लास से जुड़ा है।

class Expense:
    def __init__(self, title, amount):
        self.title = title
        self.amount = amount

    @classmethod
    def from_csv(cls, line):
        title, amount = line.split(";")
        return cls(title, float(amount))

    @staticmethod
    def is_valid_amount(value):
        return value >= 0

e = Expense.from_csv("दोपहर का खाना;520")
print(e.amount)                    # 520.0
print(Expense.is_valid_amount(-1)) # False

क्लास का डिस्ट्रक्टर: __del__

Python में __del__ मेथड है, जो ऑब्जेक्ट नष्ट होने से पहले गार्बेज कलेक्टर बुलाता है। संसाधन छोड़ने के लिए इस पर भरोसा नहीं करना चाहिए, क्योंकि उसका समय तय नहीं। फ़ाइलों और कनेक्शनों के लिए with कॉन्टेक्स्ट मैनेजर या साफ़ close() मेथड लें।

क्लास एट्रिब्यूट और इंस्टेंस एट्रिब्यूट

self.name = ... से बने फ़ील्ड किसी ख़ास ऑब्जेक्ट के होते हैं। क्लास की बॉडी में सीधे घोषित वेरिएबल क्लास एट्रिब्यूट हैं: वे सारे इंस्टेंस में साझा रहते हैं। स्थिरांक (कॉन्स्टेंट) अक्सर ऐसे ही रखे जाते हैं।

class Expense:
    CURRENCY = "INR"          # क्लास एट्रिब्यूट, स्थिरांक

    def __init__(self, title, amount):
        self.title = title    # इंस्टेंस एट्रिब्यूट
        self.amount = amount

a = Expense("कॉफ़ी", 250)
b = Expense("चाय", 150)
print(a.CURRENCY, Expense.CURRENCY)   # INR INR

ख़तरनाक जाल — बदले जा सकने वाला क्लास एट्रिब्यूट। अगर क्लास की बॉडी में rows = [] लिखा और self.rows.append(...) से उसमें जोड़ा, तो सूची सारे ऑब्जेक्ट में साझा होगी। बदले जा सकने वाले फ़ील्ड हमेशा __init__ में बनाएँ।

ऑब्जेक्ट के फ़ील्ड vars(obj) या obj.__dict__ से देखे जा सकते हैं, और नाम से एट्रिब्यूट पढ़ना हो तो getattr(obj, "amount")।

क्लास की प्रॉपर्टी: @property

प्रॉपर्टी एक मेथड है, जो बाहर से बिना कोष्ठक के साधारण फ़ील्ड की तरह पढ़ा जाता है। यह गणना से निकलने वाली वैल्यू के लिए सुविधाजनक है: रिपोर्ट का कुल अलग से सहेजने की ज़रूरत नहीं, और उसे अपडेट करना भूलने का ख़तरा भी नहीं।

class Report:
    def __init__(self):
        self.rows = []

    def add(self, expense):
        self.rows.append(expense)

    @property
    def total(self):
        return sum(e.amount for e in self.rows)

r = Report()
r.add(Expense("कॉफ़ी", 250))
r.add(Expense("टैक्सी", 300))
print(r.total)   # 550

लिखने पर नियंत्रण चाहिए तो प्रॉपर्टी में @total.setter जोड़ा जाता है। सेटर के बिना r.total = 0 लिखने पर AttributeError आएगा।

इनकैप्सुलेशन पर एक बात। Python में सख़्त अर्थ में निजी फ़ील्ड नहीं होते। एक अंडरस्कोर (_cache) एक समझौता है: «बाहर से न छुएँ»। दो अंडरस्कोर (__secret) नाम बदल देते हैं (name mangling), ताकि उत्तराधिकारी गलती से वही फ़ील्ड न ढक दे।

Python में इनहेरिटेंस

इनहेरिटेंस से पैरेंट क्लास पर आधारित चाइल्ड क्लास बनती है: चाइल्ड को पैरेंट के सारे फ़ील्ड और मेथड मिलते हैं, और ज़रूरत के मेथड वह बदल सकता है। पैरेंट का नाम कोष्ठक में लिखा जाता है।

class MonthlyReport(Report):
    def __init__(self, month):
        super().__init__()        # पैरेंट का __init__ बुलाना
        self.month = month

    def add(self, expense):       # मेथड का बदला हुआ रूप
        print(f"[{self.month}] + {expense}")
        super().add(expense)      # पैरेंट का व्यवहार बचाए रखना

m = MonthlyReport("अक्टूबर")
m.add(Expense("कॉफ़ी", 250))   # [अक्टूबर] + Expense('कॉफ़ी', 250)
print(m.total)                # 250 — प्रॉपर्टी विरासत में मिली

super() पैरेंट के मेथड बुलाने के लिए बिचौलिया ऑब्जेक्ट लौटाता है। चाइल्ड के __init__ में super().__init__() भूल जाना क्लासिक गलती है: फ़ील्ड rows बनता ही नहीं, और पहला add AttributeError पर गिरता है।

संबंध जाँचने के लिए issubclass(MonthlyReport, Report) है। Python 3 में सारी क्लासें अंतर्निहित रूप से object से आती हैं — इसी से बेसिक __repr__, __eq__ और दूसरे मेथड मिलते हैं। Python में मल्टीपल इनहेरिटेंस भी है (class C(A, B)); मेथड खोजने का क्रम C.__mro__ में दिखता है।

Python में एब्स्ट्रैक्ट क्लास

एब्स्ट्रैक्ट क्लास एक इंटरफ़ेस तय करती है: कौन-से मेथड सबक्लास को लागू करने ही होंगे। उसका इंस्टेंस नहीं बनाया जा सकता। इसके लिए मॉड्यूल abc है:

from abc import ABC, abstractmethod

class Exporter(ABC):
    @abstractmethod
    def export(self, report): ...

class CsvExporter(Exporter):
    def export(self, report):
        return "\n".join(f"{e.title};{e.amount}" for e in report.rows)

Exporter()   # TypeError: Can't instantiate abstract class Exporter ...

जो सबक्लास सारे एब्स्ट्रैक्ट मेथड लागू नहीं करता, वह भी एब्स्ट्रैक्ट रहता है, और उसका ऑब्जेक्ट बनाते समय वही एरर आएगा।

गहरी पदानुक्रमों की जगह कंपोज़िशन

इनहेरिटेंस सीखने के बाद सीढ़ियाँ बनाने का मन करता है: बेसिक ख़र्च, मुद्रा वाला ख़र्च, रुपये वाला ख़र्च — और पाँच स्तर तक। Python में ऐसा कम लिखा जाता है। इनहेरिटेंस «है» संबंध बताता है, कंपोज़िशन «रखता है» संबंध: रिपोर्ट ख़र्चों की सूची नहीं है, उसमें वह सूची rows फ़ील्ड में रखी है।

व्यावहारिक नियम: अगर किसी दूसरे ऑब्जेक्ट की क्षमता चाहिए, तो उसे फ़ील्ड में रखें, इनहेरिट न करें। डेटा के लिए अक्सर dataclass काफ़ी है, और तर्क साधारण फ़ंक्शन में रखा जा सकता है।

Python में dataclass: बिना दोहराव वाली क्लास

जो क्लासें मुख्यतः डेटा रखती हैं, उनमें __init__, __repr__ और __eq__ हाथ से लिखना थकाऊ है। मॉड्यूल dataclasses का @dataclass डेकोरेटर इन्हें फ़ील्ड की एनोटेशन से अपने-आप बना देता है:

from dataclasses import dataclass, field

@dataclass
class Expense:
    title: str
    amount: float
    tags: list[str] = field(default_factory=list)

    def __post_init__(self):
        if self.amount < 0:
            raise ValueError("राशि ऋणात्मक नहीं हो सकती")

e = Expense("कॉफ़ी", 250)
print(e)                           # Expense(title='कॉफ़ी', amount=250, tags=[])
print(e == Expense("कॉफ़ी", 250))   # True

जो जाँच पहले __init__ में लिखी जाती थी, वह अब __post_init__ में जाती है — वह जनरेट किए गए इनिशियलाइज़र के ठीक बाद चलता है। बदले जा सकने वाले डिफ़ॉल्ट मान सिर्फ़ field(default_factory=list) से दें: tags: list = [] लिखने पर dataclass इसकी मनाही करता है और ValueError देता है।

frozen और slots

frozen=True ऑब्जेक्ट को न बदलने लायक बनाता है, और slots=True (Python 3.10 से) __slots__ बनाकर मेमोरी बचाता है। फ़्रोज़न ऑब्जेक्ट का फ़ील्ड बदलने पर FrozenInstanceError आता है। नई वैल्यू वाला ऑब्जेक्ट पाने के लिए कॉपी बनाई जाती है:

from dataclasses import dataclass, replace

@dataclass(frozen=True, slots=True)
class Expense:
    title: str
    amount: float

e = Expense("कॉफ़ी", 250)
# e.amount = 300   -> dataclasses.FrozenInstanceError
e2 = replace(e, amount=300)
print(e, e2)   # Expense(title='कॉफ़ी', amount=250) Expense(title='कॉफ़ी', amount=300)

Python 3.13 से यही काम सामान्य फ़ंक्शन copy.replace() भी करता है। फ़्रोज़न dataclass ऑब्जेक्ट सेट में रखे जा सकते हैं और डिक्शनरी की कुंजी बन सकते हैं — उनमें __hash__ होता है।

क्लासों में नया: Self, @override, __slots__

क्लासों की टाइपिंग के लिए दो उपयोगी औज़ार हैं। typing.Self (Python 3.11, PEP 673) का मतलब है «वापस उसी क्लास का ऑब्जेक्ट मिलेगा» — यह विरासत में मिले मेथड में भी काम करता है। डेकोरेटर @override (Python 3.12, PEP 698) बताता है कि मेथड पैरेंट का मेथड बदल रहा है: अगर नाम में टाइपो है, तो mypy या pyright बताएँगे कि पैरेंट में ऐसा मेथड नहीं है।

from typing import Self, override

class Report:
    def __init__(self):
        self.rows = []

    def add(self, e) -> Self:
        self.rows.append(e)
        return self              # चेन में बुलाया जा सकता है

class MonthlyReport(Report):
    @override
    def add(self, e) -> Self:
        return super().add(e)

Python 3.15 में एक संकीर्ण सीमा हटी है: tuple के सबक्लास में खाली न होने वाले __slots__ अनुमत हैं, और __dict__ व __weakref__ किसी भी क्लास के __slots__ में लिखे जा सकते हैं। Python 3.14 में टपल के उत्तराधिकारी में ऐसी घोषणा TypeError: nonempty __slots__ not supported for subtype of 'tuple' देती थी।

क्लासों के साथ आम गलतियाँ

गलतीलक्षणहल
मेथड में self भूल गएTypeError: ... takes 0 positional arguments but 1 was givenमेथड का पहला पैरामीटर हमेशा self
__init__ में self. के बिना फ़ील्ड__init__ के बाद वैल्यू ग़ायबself.amount = amount लिखें
क्लास एट्रिब्यूट के रूप में सूचीडेटा ऑब्जेक्ट्स के बीच «बहता» हैसूचियाँ __init__ में या default_factory से बनाएँ
super().__init__() नहींपैरेंट के फ़ील्ड पर AttributeErrorपैरेंट का इनिशियलाइज़र बुलाएँ
प्रॉपर्टी को कोष्ठक के साथ बुलानाTypeError: 'int' object is not callableप्रॉपर्टी बिना कोष्ठक के पढ़ें: r.total

निष्कर्ष

Python OOP सरल शुरू होता है: क्लास फ़ील्ड और मेथड बताती है, __init__ फ़ील्ड भरता है, और self उसी ऑब्जेक्ट की ओर इशारा करता है। इनहेरिटेंस और super() व्यवहार बढ़ाने देते हैं, एब्स्ट्रैक्ट क्लास इंटरफ़ेस तय करती है, और @property गणना वाले फ़ील्ड बनाती है। डेटा-क्लासों के लिए @dataclass लें, और गहरी पदानुक्रमों की जगह कंपोज़िशन चुनें। कोर्स में क्लासें और dataclasses ख़र्च ट्रैकर के उदाहरण पर व्यवहार में सिखाए जाते हैं।

अक्सर पूछे जाने वाले प्रश्न

Python में क्लास और ऑब्जेक्ट में क्या फ़र्क है?

क्लास प्रकार का विवरण है: उसके कौन-से फ़ील्ड और मेथड हैं। ऑब्जेक्ट, यानी इंस्टेंस, क्लास को बुलाने से बनता है, जैसे Expense("कॉफ़ी", 250), और ठोस मान रखता है।

क्या क्लास में __init__ लिखना ज़रूरी है?

नहीं। अगर __init__ घोषित न हो, तो object से मिलने वाला इस्तेमाल होता है, जो कोई आर्गुमेंट नहीं लेता। dataclass में उसे डेकोरेटर बनाता है।

__str__ और __repr__ में क्या फ़र्क है?

__repr__ डेवलपर और डीबगर के लिए स्पष्ट प्रतिनिधित्व है, और __str__ print और f-string में उपयोगकर्ता के लिए सुंदर आउटपुट। __str__ न हो तो Python __repr__ इस्तेमाल करता है।

dataclass कब लें और सामान्य क्लास कब?

dataclass तब ठीक है जब क्लास मुख्यतः डेटा रखती है और तैयार __init__, __repr__ और तुलना चाहिए। सामान्य क्लास तब बेहतर है जब इनिशियलाइज़ेशन का तर्क जटिल हो या व्यवहार डेटा से ज़्यादा महत्वपूर्ण हो।

क्या Python में निजी फ़ील्ड होते हैं?

सख़्त निजता नहीं है। नाम की शुरुआत में एक अंडरस्कोर एक परंपरा है («बाहर के लिए नहीं»), और दो अंडरस्कोर नाम बदलकर उत्तराधिकारी द्वारा गलती से ढकने से बचाते हैं।

इस विषय को वीडियो में सीखें

यह विषय “Python 3.15: от новичка до профи” कोर्स में अभ्यास के साथ विस्तार से समझाया गया है।

कोर्स पर जाएँ