Deskryptory

  • określony protokół zachowania dla atrybutów
  • istnieją 3 operacje na atrybucie - odczyt, przypisanie i usunięcie wartości
  • jeśli istnieje deskryptor - zwróć go, inaczej zwróć atrybut
  • deskryptor implementuje:
  • descriptor.__get__(self, obj, objtype=None) -> value
  • descriptor.__set__(self, obj, value) -> None
  • descriptor.__del__(self, obj) -> None
  • działają tylko dla nowych klas w Python2
  • deskryptor implementujący tylko __get__ jest nazywany "non-data descriptor"

In [ ]:
class Punkt():
    
    def __init__(self, x, y):
        self._x = x
        self._y = y
        
    def getX(self):
        return self._x
    
    def getY(self):
        return self._y
    
    def setX(self, x):
        self._x = x
        
    def setY(self, y):
        self._y = y
        
p = Punkt(1, 2)
print(p.getX())
p.setX(5)
print(p.getX())

In [ ]:
class Punkt():
    
    def __init__(self, x, y):
        self._x = x
        self._y = y
        
    def getX(self):return self._x
    def setX(self, x):self._x = x
    def getY(self):return self._y
    def setY(self, y):self._y = y
        
    x = property(fget=getX, fset=setX, fdel=None, doc=None)
    y = property(getY, setY)
        
p = Punkt(1, 2)
print(p.x)
p.x = 5
print(p.x)

In [ ]:
class Punkt():
    
    def __init__(self, x, y):
        self._x = x
        self._y = y
        
    def getX(self):return self._x
    def setX(self, x):self._x = x
        
    def getY(self):return self._y
    def setY(self, y):self._y = y
        
    def dodaj(self):
        return self._x + self._y
        
    x = property(getX, setX)
    y = property(getY, setY)
    suma = property(dodaj)
        
p = Punkt(1, 2)
print(p.x)
p.x = 5
print(p.x)
print(p.suma)

In [ ]:
class ReadOnly():
    def __init__(self, value):
        self._value = value
        
    def __get__(self, obj, objtype):
        print(self, obj, objtype)
        print(obj._y)
        return self._value
    
    def __set__(self, obj, val):
        print(self, obj, val)
        raise AttributeError

class Punkt():
    x = ReadOnly(5)
    
    def __init__(self, y):
        self._y = y

p = Punkt(2)
print(p.x)
p.x = 10

In [ ]:
class ReadOnly():
    def __init__(self, value):
        self._value = value
        
    def __get__(self, obj, objtype):
        print("get: ", self, obj, objtype)
        print(obj._y)
        return self._value
    
    def __set__(self, obj, val):
        print(self, obj, val)
        raise AttributeError

class Punkt():
    
    def __init__(self, x, y):
        self.x = ReadOnly(x)
        self._y = y

p = Punkt(1, 2)
print(p.x)
p.__dict__
p.__dict__['x']

In [ ]:
class ReadOnlyXPlus5():        
    def __get__(self, obj, objtype):
        print("get: ", self, obj, objtype)
        return obj._x + 5
    
    def __set__(self, obj, val):
        print(self, obj, val)
        raise AttributeError

class Punkt():
    
    def __init__(self, x, y):
        self._x = x
        self._y = y
        
    x = ReadOnlyXPlus5()

p = Punkt(1, 2)
print(p.x)
print(p.__dict__)
Punkt.x

In [ ]:
class NonDataDesc():
    def __init__(self, x):
        self.x = x
        
    def __get__(self, obj, objtype):
        print("NonDataGet")
        return self.x
    
class DataDesc():
    def __init__(self, x):
        self.x = x
        
    def __get__(self, obj, objtype):
        print("DataDescGet")
        return self.x
    
    def __set__(self, obj, val):
        self.x = val
    
class A():
    #x = NonDataDesc(10)
    x = DataDesc(-5)
        
a = A()
print(a.x)
a.x = 5
print(a.x)
  • deskryptor powinien być zdefiniowany na poziomie klasy
  • różnica w sposobie wywoływania deskryptora dla klasy i obiektu
  • metoda __getattribute__ dla type i object
  • nie można się dostać do deskryptora z __dict__
  • mechanizm Pythona do pobierania atrybutów
  • obj.x -> przeszukuje dict obiektu
  • jeśli x definiuje __get__ -> wywołaj x.__get__(obj)
  • sposób działania object.__getattribute__
  • b.x -> type(b).__dict__['x'].__get__(b, type(b))
  • B.x -> B.__dict__['x'].__get__(None, B)

Kolejność ważności attrybutów

  1. Deskryptory
  2. Atrybuty instancji
  3. non-data descriptors, czyli deskryptory tylko z __get__
  4. __getattribute__, jeśli zaimplementowana

Podsumowanie

  • deskryptory są wywoływane przez __getattribute__()
  • nadpisanie __getattribute__() spowoduje brak wywoływania deskryptora
  • różnica w sposobie wywołania dla obiektu i klasy
  • data descriptors zawsze nadpisują dict instancji
  • non-data descriptors mogą być nadpisane przez atrybut instancji

?


In [ ]:
def foo():
    pass

dir(foo)

In [ ]:
class A():
    def __init__(self, x):
        self.x = x
    
    def f(*args):
        print(args)
        if len(args) > 0:
            print(args[0].x)
        print("foo")
        
A.f()
#A(5).f()