8000 Update README.md · Cmatrix1/Python-Descriptors@958b8fc · GitHub
[go: up one dir, main page]

Skip to content

Commit 958b8fc

Browse files
authored
Update README.md
1 parent accebf1 commit 958b8fc

File tree

1 file changed

+107
-0
lines changed

1 file changed

+107
-0
lines changed

README.md

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,7 +405,114 @@ And of course if we set the value:
405405
```python
406406
p2.x = 100.9
407407
print(p2.x, p1.x)
408+
408409
> (100, 100)
409410
```
410411
So, obviously using the descriptor instance dictionary for storage at the instance level is probably not going to work in most cases!
411412
And this is the reason both the `__get__` and `__set__` methods need to know which instance we are dealing with.
413+
414+
### Approach 2
415+
We are going to **Assuming** that the `instance` is a hashable object, and use a dictionary in the descriptor to store instance specific values.
416+
```python
417+
class IntegerValue:
418+
def __init__(self):
419+
self.values = {}
420+
421+
def __set__(self, instance, value):
422+
self.values[instance] = int(value)
423+
424+
def __get__(self, instance, owner_class):
425+
if instance is None:
426+
return self
427+
else:
428+
return self.values.get(instance)
429+
```
430+
431+
```python
432+
class Point2D:
433+
x = IntegerValue()
434+
y = IntegerValue()
435+
436+
p1 = Point2D()
437+
438+
p1.x = 10.1
439+
p1.y = 20.2
440+
441+
p1.x, p1.y
442+
> (10, 20)
443+
```
444+
In fact, we can see the dictionary in the descriptor instances:
445+
```python
446+
Point2D.x.values
447+
> {<__main__.Point2D at 0x212d93d9710>: 10}
448+
Point2D.y.values
449+
> {<__main__.Point2D at 0x212d93c2110>: 20}
450+
451+
```
452+
where the key in both of these is our `p1` object
453+
We can now create a second point, and go through the same steps:
454+
```python
455+
p2 = Point2D()
456+
p2.x = 100.1
457+
p2.y = 200.2
458+
459+
Point2D.x.values
460+
> {<__main__.Point2D at 0x212d93b2290>: 10,
461+
> <__main__.Point2D at 0x212d931c8d0>: 100}
462+
463+
Point2D.y.values
464+
> {<__main__.Point2D at 0x212d93b2290>: 20,
465+
> <__main__.Point2D at 0x212d931c8d0>: 200}
466+
```
467+
And everything works just fine ( Or does it?? ):
468+
```bash
469+
p1.x, p1.y, p2.x, p2.y
470+
> (10, 20, 100, 200)
471+
```
472+
We actually have a potential memory leak - notice how the dictionary in the desccriptor instance is **also** storing a reference to the point object - as a **key** in the dictionary.
473+
474+
---
475+
Let's write a simple utility function that allows us to get the reference count for an object given it's id (and it only makes sense if the id we use still has a valid non-destroyed object):
476+
```python
477+
import ctypes
478+
479+
def ref_count(address):
480+
return ctypes.c_long.from_address(address).value
481+
482+
p1 = Point2D()
483+
id_p1 = id(p1)
484+
485+
ref_count(id_p1)
486+
> 1
487+
```
488+
Now let's set the `x` property of `p1`:
489+
```python
490+
p1.x = 100.1
491+
```
492+
And let's check the ref count again:
493+
```python
494+
ref_count(id_p1)
495+
> 2
496+
```
497+
As you can see it's now `2`. if we delete our main reference to `p1` that is in our global namespace:
498+
```python
499+
'p1' in globals()
500+
> True
501+
del p1
502+
'p1' in globals()
503+
> False
504+
ref_count(id_p1)
505+
> 1
506+
```
507+
And our reference count is still `1`, which means the object itself has not been destroyed!
508+
In fact, we can see that object referenced in our data descriptor dictionary:
509+
```python
510+
Point2D.x.values.items()
511+
> dict_items([(<__main__.Point2D object at 0x00000212D93B2290>, 10), (<__main__.Point2D object at 0x00000212D931C8D0>, 100), (<__main__.Point2D object at 0x00000212D9390150>, 100)])
512+
```
513+
As you can see, the last element's key is the same id as what `p1` was referencing.
514+
So, although we deleted `p1`, the object was not destroyed - this can result in a memory leak.
515+
There are a few ways we can handle this issue. The first one we are going to look at is something called **weak references**. So let's segway into that next.
516+
517+
518+

0 commit comments

Comments
 (0)
0