0Pricing
Python Academy · Lezione

Descriptor: __get__, __set__, __delete__

Implementi il protocollo dei descriptor per controllare l'accesso agli attributi.

Descriptor: __get__, __set__, __delete__ è una lezione Python Academy gratuita su CoddyKit. Questa è la lezione 3 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento Python Academy, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso Python Academy include 4 lezioni in totale.

Che cos'è un descriptor

Un descriptor è un oggetto che definisce __get__, __set__ o __delete__ e viene assegnato come attributo di classe. Python chiama questi metodi quando si accede all'attributo.

class Descriptor:
    def __get__(self, obj, objtype=None):
        print(f"__get__ called, obj={obj}")
        return 42

class MyClass:
    attr = Descriptor()

print(MyClass().attr)   # __get__ called ... 42

Descriptor di dati e non di dati

I descriptor di dati definiscono __set__ o __delete__ e hanno precedenza sul __dict__ dell'istanza. I descriptor non di dati, che definiscono solo __get__, cedono la precedenza a __dict__.

class DataDesc:
    def __get__(self, obj, t): return "data"
    def __set__(self, obj, val): pass

class NonDataDesc:
    def __get__(self, obj, t): return "nondata"

class C:
    d = DataDesc()
    n = NonDataDesc()

c = C()
c.__dict__["n"] = "instance"  # shadows the non-data desc
c.__dict__["d"] = "instance"  # does NOT shadow data desc
print(c.n)   # instance
print(c.d)   # data

Firma di __get__

__get__(self, obj, objtype): obj è l'istanza, oppure None quando si accede alla classe; objtype è la classe.

class Verbose:
    def __get__(self, obj, objtype=None):
        if obj is None:
            return self   # accessed on class
        return f"value for {obj!r}"

class Widget:
    label = Verbose()

print(Widget.label)        # <Verbose object>
print(Widget().label)      # value for <Widget object>

__set__ e validazione

Implementi __set__(self, obj, value) per validare o trasformare un valore prima di memorizzarlo.

class PositiveInt:
    def __set_name__(self, owner, name):
        self.name = name

    def __get__(self, obj, t):
        return obj.__dict__.get(self.name)

    def __set__(self, obj, val):
        if not isinstance(val, int) or val <= 0:
            raise ValueError(f"{self.name} must be a positive int")
        obj.__dict__[self.name] = val

class Rect:
    width  = PositiveInt()
    height = PositiveInt()

r = Rect()
r.width = 10
# r.width = -1  # ValueError

__set_name__

A partire da Python 3.6, Python chiama __set_name__(owner, name) sul descriptor quando questo viene assegnato a una classe, fornendogli accesso al nome del proprio attributo.

class Typed:
    def __set_name__(self, owner, name):
        self.public  = name
        self.private = "_" + name

    def __get__(self, obj, t):
        return None if obj is None else getattr(obj, self.private, None)

    def __set__(self, obj, val):
        setattr(obj, self.private, val)

class User:
    name = Typed()

u = User()
u.name = "Alice"
print(u.name)     # Alice

__delete__ per la rimozione degli attributi

Definisca __delete__(self, obj) per intercettare del obj.attr.

class Protected:
    def __set_name__(self, owner, name): self.name = name
    def __get__(self, obj, t):
        return obj.__dict__.get(self.name)
    def __set__(self, obj, v): obj.__dict__[self.name] = v
    def __delete__(self, obj):
        raise AttributeError(f"Cannot delete {self.name}")

class Config:
    host = Protected()

c = Config(); c.host = "localhost"
# del c.host   # AttributeError

Le funzioni sono descriptor non di dati

Le funzioni implementano __get__ per restituire un metodo associato quando vengono utilizzate su un'istanza. È così che funziona l'associazione dei metodi in Python.

def greet(self):
    return f"Hello from {self}"

class C: pass
C.greet = greet

c = C()
print(c.greet())    # Hello from <C object>

classmethod e staticmethod sono descriptor

classmethod e staticmethod sono classi descriptor integrate che racchiudono funzioni e modificano il valore restituito da __get__.

class MyClass:
    @classmethod
    def from_string(cls, s):
        return cls()

# classmethod.__get__ returns a bound method with cls
# staticmethod.__get__ returns the plain function

Descriptor di attributi lazy

Crei un descriptor che calcola un valore una sola volta e lo memorizza nella cache per ogni istanza.

class LazyAttr:
    def __init__(self, func): self.func = func
    def __set_name__(self, owner, name): self.name = name
    def __get__(self, obj, t):
        if obj is None: return self
        val = self.func(obj)
        obj.__dict__[self.name] = val   # shadows descriptor
        return val

class Report:
    @LazyAttr
    def summary(self):
        print("computing...")
        return "done"

r = Report()
print(r.summary)   # computing... done
print(r.summary)   # done (cached)

property è un descriptor

property è a sua volta un descriptor di dati implementato in C. Comprendere i descriptor aiuta a capire come funzionano @property, @classmethod e @staticmethod.

class Circle:
    def __init__(self, r): self._r = r

    @property
    def radius(self): return self._r

    @radius.setter
    def radius(self, v):
        if v < 0: raise ValueError
        self._r = v

# property is: property.__get__ = fget, property.__set__ = fset

Ordine di ricerca dei descrittori

Ordine di ricerca degli attributi in Python: 1) descrittori di dati dal tipo (MRO), 2) __dict__ dell'istanza, 3) descrittori non di dati e variabili di classe.

# Pseudocode for obj.attr:
# 1. Check type(obj).__mro__ for a data descriptor
# 2. Check obj.__dict__
# 3. Check type(obj).__mro__ for non-data descriptor or class var
# 4. Raise AttributeError

Verifica rapida

Quale tipo di descrittore ha la precedenza sul __dict__ dell'istanza?

Riepilogo

I descrittori intercettano l'accesso agli attributi tramite __get__, __set__, __delete__. I descrittori di dati hanno la precedenza sul __dict__ dell'istanza, mentre quelli non di dati no. Utilizzi __set_name__ per conoscere il nome dell'attributo. property, classmethod e staticmethod sono tutti descrittori.

Domande Frequenti

La lezione «Descriptor: __get__, __set__, __delete__» è gratuita?

Sì — il testo completo di «Descriptor: __get__, __set__, __delete__» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso Python Academy, passa a CoddyKit PRO. Il corso Python Academy include 4 lezioni in totale.

Cosa imparerò in «Descriptor: __get__, __set__, __delete__»?

Implementi il protocollo dei descriptor per controllare l'accesso agli attributi. Eserciti Python Academy con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.

Ho bisogno di esperienza per iniziare Python Academy?

Non è richiesta alcuna esperienza precedente. Python Academy su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 3 di 4.

Quanto tempo richiede la lezione «Descriptor: __get__, __set__, __delete__»?

La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.

Posso scrivere ed eseguire codice in questa lezione Python Academy?

Sì. Ogni lezione Python Academy include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.

Tutte le lezioni di questo corso

  1. Come vengono create le classi Python
  2. Scrittura di metaclassi personalizzate
  3. Descriptor: __get__, __set__, __delete__
  4. __slots__ e ottimizzazione della memoria
← Torna a Python Academy