Python Python

Care este diferența dintre @staticmethod, @classmethod și metodele de instanță în Python

Dima Aug 30, 2026 10.0

Pe scurt

Toate trei sunt funcții obișnuite stocate în __dict__-ul clasei. Singura diferență este ce descriptor învelește funcția și, prin urmare, ce predă înapoi accesul la atribut: __get__-ul unei funcții simple returnează o metodă legată, care injectează instanța ca prim argument, classmethod.__get__ injectează în schimb clasa, iar staticmethod.__get__ nu injectează nimic și returnează funcția de dedesubt neatinsă. Alege după ce are nevoie efectiv codul — stare de instanță înseamnă o metodă de instanță, clasa însăși înseamnă o metodă de clasă, iar niciuna dintre ele înseamnă o metodă statică.


Cum funcționează

Un def din corpul unei clase creează un obiect funcție simplu, imposibil de deosebit de unul definit la nivel de modul. @classmethod și @staticmethod nu transformă acea funcție; o învelesc într-un obiect classmethod sau staticmethod care o stochează. La sfârșitul corpului clasei, toate trei sunt doar intrări în __dict__-ul clasei, iar nimic despre felul în care vor fi apelate nu a fost încă decis.

Decizia se ia la căutarea atributului. object.__getattribute__ parcurge MRO-ul căutând numele, iar când găsește o intrare al cărei tip definește __get__, nu returnează acea intrare — apelează descr.__get__(instance, owner) și returnează rezultatul. Funcțiile definesc __get__, ceea ce înseamnă că funcțiile sunt descriptori, iar acest unic fapt este tot mecanismul. Python nu are niciun concept separat de „metodă" la momentul definirii; o metodă este ce devine o funcție când este căutată printr-o clasă.

function.__get__(obj, cls) construiește un obiect MethodType care ține o pereche: funcția și obj. Apelarea acelui obiect apelează funcția cu obj pus în fața argumentelor. Când același atribut este citit de pe clasă, argumentul instanță este None și se întoarce funcția brută — motiv pentru care Invoice.formatted_total(invoice) funcționează și motiv pentru care self nu este magic, ci doar primul parametru care primește ce a legat __get__.

classmethod.__get__ ignoră complet instanța și leagă clasa proprietară, sau type(instance) când se ajunge printr-o instanță. Rezultatul este același obiect în ambele cazuri, motiv pentru care o metodă de clasă se comportă identic, fie că este apelată pe clasă, fie pe o instanță. Consecința care contează: căutată pe o subclasă, ea leagă subclasa, deci cls(...) dintr-un constructor alternativ produce subclasa fără ca clasa de bază să știe ceva despre ea.

staticmethod.__get__ returnează funcția învelită neschimbată. Nu se creează niciun obiect de legare, nu se injectează niciun argument și nu se alocă nimic. O metodă statică este o funcție simplă care se întâmplă să trăiască într-un spațiu de nume de clasă.

Acea diferență de alocare este observabilă. Metodele legate sunt construite proaspăt la fiecare acces la atribut, deci două citiri consecutive ale aceleiași metode sunt egale, dar nu identice, în timp ce funcția de dedesubt este partajată și accesibilă prin __func__. Accesul la o metodă statică nu alocă absolut nimic.


Cum arată la rulare

Creează un fișier:

touch invoice_methods.py

Rulează-l cu:

python3 invoice_methods.py

Ce este stocat față de ce returnează accesul

Cel mai mic caz care le desparte pe cele trei: compari tipul fiecărei intrări din __dict__-ul clasei cu tipul produs de citirea aceluiași nume de pe clasă și de pe o instanță.

class Invoice:
    def __init__(self, invoice_id, amount_cents):
        self.invoice_id = invoice_id
        self.amount_cents = amount_cents

    def formatted_total(self):
        return f"{self.amount_cents / 100:.2f}"

    @classmethod
    def from_csv_row(cls, row):
        invoice_id, amount = row.split(",")
        return cls(invoice_id, Invoice.cents_from_text(amount))

    @staticmethod
    def cents_from_text(text):
        return round(float(text) * 100)


invoice = Invoice("INV-4471", 24999)

for name in ("formatted_total", "from_csv_row", "cents_from_text"):
    stored = type(Invoice.__dict__[name]).__name__
    on_class = type(getattr(Invoice, name)).__name__
    on_instance = type(getattr(invoice, name)).__name__
    print(f"{name}: stored={stored} class_access={on_class} instance_access={on_instance}")


formatted_total: stored=function class_access=function instance_access=method
from_csv_row: stored=classmethod class_access=method instance_access=method
cents_from_text: stored=staticmethod class_access=function instance_access=function


  • Invoice.__dict__[name] ocolește __getattribute__, deci arată obiectul stocat brut — o function, un classmethod și un staticmethod. Decoratorii au schimbat învelișul, nu funcția din el.
  • formatted_total diferă după calea de acces: prin clasă este în continuare o function, prin instanță devine o method. Nimic din definiție nu a provocat asta; __get__ a primit o instanță într-un caz și None în celălalt.
  • from_csv_row este o method în ambele feluri, pentru că classmethod.__get__ leagă clasa, indiferent dacă a fost implicată vreo instanță.
  • cents_from_text este o function în ambele feluri — staticmethod.__get__ predă înapoi exact ce a primit.

Apelarea manuală a lui __get__

O dimensiune în plus: faci manual ce face automat căutarea atributului. Dacă protocolul de descriptori chiar este mecanismul, invocarea lui directă trebuie să reproducă exact accesul obișnuit. Înlocuiește fișierul cu:

class Invoice:
    def __init__(self, invoice_id, amount_cents):
        self.invoice_id = invoice_id
        self.amount_cents = amount_cents

    def formatted_total(self):
        return f"{self.amount_cents / 100:.2f}"

    @classmethod
    def from_csv_row(cls, row):
        invoice_id, amount = row.split(",")
        return cls(invoice_id, Invoice.cents_from_text(amount))

    @staticmethod
    def cents_from_text(text):
        return round(float(text) * 100)


invoice = Invoice("INV-4471", 24999)

plain = Invoice.__dict__["formatted_total"]
as_class = Invoice.__dict__["from_csv_row"]
as_static = Invoice.__dict__["cents_from_text"]

bound = plain.__get__(invoice, Invoice)
print("function __get__ binds the instance:", bound.__self__ is invoice)
print("hand-bound call equals attribute access:", bound() == invoice.formatted_total())
print("classmethod __get__ binds the class:", as_class.__get__(invoice, Invoice).__self__ is Invoice)
print("staticmethod __get__ returns the bare function:", as_static.__get__(invoice, Invoice) is Invoice.cents_from_text)


function __get__ binds the instance: True
hand-bound call equals attribute access: True
classmethod __get__ binds the class: True
staticmethod __get__ returns the bare function: True


Apelarea lui plain.__get__(invoice, Invoice) produce o metodă legată al cărei __self__ este instanța, iar apelarea ei dă același răspuns ca invoice.formatted_total() — pentru că este literalmente același apel, făcut cu un strat mai jos. A treia linie este contrastul cheie: as_class.__get__ a primit invoice și l-a aruncat, legând în schimb Invoice. A patra arată staticmethod.__get__ neefectuând nicio legare și returnând exact obiectul la care se rezolvă Invoice.cents_from_text.


De ce o metodă de clasă este conștientă de subclase

Treapta următoare este câștigul practic. Un constructor alternativ scris ca metodă de clasă urmează subclasa pe care a fost apelat, pentru că __get__ a legat acea subclasă, nu clasa în care a fost definită metoda. Înlocuiește fișierul cu:

class Invoice:
    ledger_code = "AR-100"

    def __init__(self, invoice_id, amount_cents):
        self.invoice_id = invoice_id
        self.amount_cents = amount_cents

    @classmethod
    def from_csv_row(cls, row):
        invoice_id, amount = row.split(",")
        return cls(invoice_id, cls.cents_from_text(amount))

    @staticmethod
    def cents_from_text(text):
        return round(float(text) * 100)

    def describe(self):
        return f"{type(self).__name__} {self.invoice_id} -> {self.ledger_code}"


class CreditNote(Invoice):
    ledger_code = "AR-900"


print(Invoice.from_csv_row("INV-4471,249.99").describe())
print(CreditNote.from_csv_row("CN-8802,74.50").describe())
print("staticmethod ignores both:", Invoice.cents_from_text("12.34"), CreditNote.cents_from_text("12.34"))


Invoice INV-4471 -> AR-100
CreditNote CN-8802 -> AR-900
staticmethod ignores both: 1234 1234


from_csv_row este definit o singură dată, pe Invoice, și nu pomenește niciodată CreditNote. Ajungerea la el prin CreditNote face classmethod.__get__ să lege CreditNote drept cls, deci cls(...) construiește un CreditNote, iar cls.cents_from_text se rezolvă prin subclasă. Scrierea aceleiași fabrici ca metodă statică ar impune un Invoice(...) scris fix în interiorul ei, iar fiecare subclasă ar primi tăcut tipul greșit înapoi. Ultima linie arată reversul: cents_from_text nu are nevoie nici de instanță, nici de clasă, deci ambele căi de acces rulează cod identic, iar ea ar funcționa la fel de bine ca funcție la nivel de modul.


Legarea se întâmplă la fiecare acces

Ultima treaptă este consecința asupra costului și a identității. O metodă legată este un obiect și este construită din nou de fiecare dată când este citit atributul. Înlocuiește fișierul cu:

class Invoice:
    def __init__(self, invoice_id, amount_cents):
        self.invoice_id = invoice_id
        self.amount_cents = amount_cents

    def formatted_total(self):
        return f"{self.amount_cents / 100:.2f}"

    @staticmethod
    def cents_from_text(text):
        return round(float(text) * 100)


invoice = Invoice("INV-4471", 24999)

print("two accesses give the same object:", invoice.formatted_total is invoice.formatted_total)
print("but they compare equal:", invoice.formatted_total == invoice.formatted_total)
print("__self__ is the instance:", invoice.formatted_total.__self__ is invoice)
print("__func__ is the shared class function:", invoice.formatted_total.__func__ is Invoice.__dict__["formatted_total"])
print("staticmethod access is the same object every time:", invoice.cents_from_text is Invoice.cents_from_text)


two accesses give the same object: False
but they compare equal: True
__self__ is the instance: True
__func__ is the shared class function: True
staticmethod access is the same object every time: True


Fiecare citire a lui invoice.formatted_total rulează din nou __get__ și alocă un MethodType proaspăt, deci verificarea de identitate pică, în timp ce egalitatea reușește — metodele legate se compară egale când __self__ și __func__ se potrivesc. Doar învelișul este nou; __func__ este unicul obiect funcție care trăiește în __dict__-ul clasei, partajat de fiecare instanță. Comparația pentru metoda statică este True pentru că nu se construiește niciodată vreun înveliș, motiv pentru care înregistrarea unei metode statice ca funcție callback și înlăturarea ei ulterioară după identitate funcționează, în timp ce același lucru cu o metodă legată nu o găsește, tăcut.


Cazuri limită și capcane

  • __new__ este implicit o metodă statică. Python o învelește automat, chiar și când decoratorul este omis, motiv pentru care primește cls ca prim parametru explicit, dar nu este o metodă de clasă. Adăugarea de către tine a lui @staticmethod este redundantă; adăugarea lui @classmethod o strică.
  • Uitarea lui @staticmethod produce un eșec intermitent și derutant. O funcție ajutătoare definită fără el este în continuare o funcție simplă, deci instanța este injectată: apelarea ei prin clasă funcționează, în timp ce apelarea ei pe o instanță ridică TypeError, spunând că a primit un argument în plus. Eroarea pare că depinde de locul apelului, pentru că chiar depinde.
  • O metodă statică care își numește propria clasă anulează derivarea. Orice fabrică sau funcție ajutătoare al cărei răspuns ar trebui să urmeze subclasa își are locul într-o metodă de clasă, pentru că doar classmethod.__get__ știe prin ce clasă a venit căutarea.
  • Metodele de instanță pot fi apelate prin clasă, cu o instanță explicită. Invoice.formatted_total(invoice) este forma nelegată și este exact ce face intern un apel super().
  • Preferă o metodă de clasă unei metode statice când eziți. Utilizarea reală dominantă a unei metode de clasă este constructorul alternativ; o metodă statică care nu atinge niciodată clasa este de obicei mai clară ca funcție la nivel de modul, iar punerea ei în clasă cumpără doar spațiu de nume.

Rezumat

Cele trei forme înseamnă un singur obiect funcție plus o alegere de descriptor. Accesul la atribut apelează __get__-ul acelui descriptor, iar ce injectează el — instanța, clasa sau nimic — este toată diferența. Tot restul decurge de aici: de ce metodele de clasă urmăresc subclasele, de ce metodele statice nu costă nicio alocare și de ce o metodă legată nu este niciodată identică cu ea însăși.

  • Toate trei sunt stocate în __dict__-ul clasei; diferă doar învelișul, iar învelișul este consultat la momentul căutării, nu la momentul definirii.
  • Funcțiile sunt descriptori, deci instance.method înseamnă type(instance).__dict__["method"].__get__(instance, type(instance)).
  • classmethod.__get__ leagă clasa prin care a venit căutarea, deci cls(...) dintr-un constructor alternativ returnează subclasa.
  • staticmethod.__get__ returnează funcția brută, fără să injecteze nimic și fără să aloce nimic.
  • Metodele legate sunt create la fiecare acces, deci obj.m is obj.m este False, în timp ce obj.m == obj.m este True, iar __func__ este unica funcție partajată.
  • Folosește o metodă de instanță pentru stare de instanță, o metodă de clasă când este nevoie de clasa însăși și o metodă statică doar când nu este nevoie de niciuna — caz în care o funcție la nivel de modul este adesea răspunsul mai bun.

You need to be logged in to access the cloud lab.

Log in