Data Descriptor

from time import time


class Epoch:
    def __get__(self, instance, owner):
        print(f"Self object {self}")
        print(f"Instance object {instance}")
        print(f"Owner object {owner}")
        return int(time())


class MyTime:
    epoch = Epoch()


m = MyTime()
12345678910111213141516

При вызове epoch через экземпляр m класса MyTime:

>>> m.epoch

Self object <__main__.Epoch object at 0x7fcd70bf6950>
Instance object <__main__.MyTime object at 0x7fcd70bf6ad0>
Owner object <class '__main__.MyTime'>

1686562054
1234567

В Self был передан сам объект класса Epoch (сам дескриптор - экземпляр дескриптора) В Instance объект из которого произошло обращение к дескриптору (экземпляр класса MyTime) В Owner объект, класс собственник MyTime

Мы можем обращаться к атрибуту класса (epoch) через сам класс (MyTime):

>>> MyTime.epoch

Self object <__main__.Epoch object at 0x7fcd70bf6950>
Instance object None
Owner object <class '__main__.MyTime'>

1686563123
1234567

В Instance не был передан никакой объект - т.к. обращение произошло через класс (MyTime) а не через экземпляр (m)

Это напоминает работу @property:

  • Возвращать сам экземпляр класса (дескриптора), если обращение произошло через класс

  • Возвращать значение свойств, если обращение произошло через экземпляр

class Person:
     _name = 'Oleg'
     @property
     def name(self):
	    return self._name

# обращение через класс - вернул экземпляр класса
>>> Person.name

<property object at 0x7fcd70bea570>

# обращение через экземпляр - вернул значение свойства
>>> Person().name

'Oleg'
123456789101112131415

[!warning] Особенность хранения данных в дескипторах Нельзя хранить данные в экземпляре дескриптора (реализующих методы get, set, delete), т.к. они делят общее состояние с экземплярами класса.

Имеется ввиду со стандартной реализацией set.

Для хранения данных в дескипторе можно определить словарь (dict) в инициализации и в методе __set__() передавать в него значение по ключу

class MyDescriptor:
	def __init__(self):
		self._values = {}

	def __set__(self, instance, value):
		self._values[instance] = value

	def __get__(self, instance, owner):
		if instance is None:
			return self
		return self._values.get(instance)


class Vector:
	x = MyDescriptor()
	y = MyDescriptor()

v1 = Vector()
v2 = Vector()
12345678910111213141516171819

Но такой вариант реализации допустит утечку памяти - при пересохранении значений будут сохраняться ссылки на значения.

Чтобы предотвратить утечку памяти, следует использовать слабые ссылки (weakref).


Для подсчета ссылок, можно использовать:

import sys

val = 'Oleg'
sys.getrefcount(val)
1234

Либо с библиотекой ctypes

import ctypes

def ref_count(obj):
	return ctypes.c_long.from_address(id(obj_id)).value
1234

Описание утечки памяти из примера выше:

v1.x = 5

v2.x = 10

Vector.x._values

# {<__main__.Vector object at 0x7f621cddec10>: 5, <__main__.Vector object at 0x7f621cddec50>: 10}

v3 = Vector()

# вызываем верхнюю функцию для подсчёта ссылок
ref_count(v3)
# 1

v3.x = 5
ref_count(v3)
# 2

v3.y = 5
ref_count(v3)
# 3

del v3
ref_count(v3)
# 2

# тут и происходит утечка памяти - не все ссылки на объекты были удалены

Last updated