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

URL: https://skillaria.top/hi/guides/python/klassy-python

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 ख़र्च ट्रैकर के उदाहरण पर व्यवहार में सिखाए जाते हैं।

## FAQ

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

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

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

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

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

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

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

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

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

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


---
Source: Skillaria.Top (https://skillaria.top)
