0Pricing
Python Academy · Lekcja

Deskryptory: __get__, __set__, __delete__

Proszę implementować protokół deskryptora do kontrolowania dostępu do atrybutów.

Deskryptory: __get__, __set__, __delete__ to bezpłatna lekcja Python Academy na CoddyKit. To lekcja 3 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej Python Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Python Academy zawiera 4 lekcji w sumie.

Czym jest deskryptor?

Deskryptor to obiekt definiujący __get__, __set__ lub __delete__, przypisany jako atrybut klasy. Python wywołuje te metody podczas dostępu do atrybutu.

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

Deskryptory danych a deskryptory niedanych

Deskryptory danych definiują __set__ lub __delete__ i mają pierwszeństwo przed __dict__ instancji. Deskryptory niedanych, definiujące tylko __get__, ustępują miejsca __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

Sygnatura __get__

__get__(self, obj, objtype): obj jest instancją lub ma wartość None, gdy atrybut jest pobierany za pośrednictwem klasy; objtype jest klasą.

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__ i walidacja

Proszę zaimplementować __set__(self, obj, value), aby zweryfikować lub przekształcić wartość przed jej zapisaniem.

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__

Python 3.6+ wywołuje __set_name__(owner, name) na deskryptorze, gdy jest on przypisywany do klasy, dzięki czemu deskryptor otrzymuje dostęp do własnej nazwy atrybutu.

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__ do usuwania atrybutów

Proszę zdefiniować __delete__(self, obj), aby przechwytywać operację 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

Funkcje są deskryptorami niedanych

Funkcje implementują __get__, aby po pobraniu z instancji zwracać metodę związaną. Tak właśnie działa wiązanie metod w Pythonie.

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

class C: pass
C.greet = greet

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

classmethod i staticmethod są deskryptorami

classmethod i staticmethod to wbudowane klasy deskryptorów, które opakowują funkcje i zmieniają wartość zwracaną przez __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

Deskryptor leniwego atrybutu

Proszę zbudować deskryptor, który oblicza wartość raz i buforuje ją dla każdej instancji.

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 jest deskryptorem

property samo jest deskryptorem danych zaimplementowanym w języku C. Zrozumienie deskryptorów pomaga zrozumieć działanie @property, @classmethod i @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

Kolejność wyszukiwania deskryptorów

Kolejność wyszukiwania atrybutów w Pythonie: 1) deskryptory danych z typu (MRO), 2) __dict__ instancji, 3) deskryptory niedanych i zmienne klasowe.

# 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

Szybkie sprawdzenie

Jaki typ deskryptora ma pierwszeństwo przed __dict__ instancji?

Podsumowanie

Deskryptory przechwytują dostęp do atrybutów za pomocą __get__, __set__, __delete__. Deskryptory danych przesłaniają __dict__ instancji, a deskryptory niedanych tego nie robią. Należy użyć __set_name__, aby poznać nazwę atrybutu. property, classmethod i staticmethod są deskryptorami.

Często zadawane pytania

Czy lekcja „Deskryptory: __get__, __set__, __delete__” jest bezpłatna?

Tak — pełny tekst „Deskryptory: __get__, __set__, __delete__” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu Python Academy, przejdź na CoddyKit PRO. Kurs Python Academy zawiera 4 lekcji w sumie.

Co nauczysz się w „Deskryptory: __get__, __set__, __delete__”?

Proszę implementować protokół deskryptora do kontrolowania dostępu do atrybutów. Ćwiczysz Python Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć Python Academy?

Nie wymagamy żadnego doświadczenia. Python Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 3 z 4.

Ile czasu zajmuje lekcja „Deskryptory: __get__, __set__, __delete__”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji Python Academy?

Tak. Każda lekcja Python Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. Jak tworzone są klasy Pythona
  2. Pisanie własnych metaklas
  3. Deskryptory: __get__, __set__, __delete__
  4. __slots__ i optymalizacja pamięci
← Powrót do Python Academy