> For the complete documentation index, see [llms.txt](https://igor-19.gitbook.io/qa/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://igor-19.gitbook.io/qa/avtomatizaciya-testirovaniya/python/oop-v-python-vo-vsekh-podrobnostyakh/data-descriptor.md).

# Data Descriptor

```python
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`:

```python
>>> 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`):

```python
>>> MyTime.epoch

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

1686563123
1234567
```

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

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

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

```python
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__()` передавать в него значение по ключу

```python
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*).

***

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

```python
import sys

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

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

```python
import ctypes

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

***

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

```python
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

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