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

Skip to content

Commit accebf1

Browse files
authored
Update README.md
1 parent a219216 commit accebf1

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
@@ -157,6 +157,7 @@ class Dice:
157157

158158
## Getters and Setters
159159

160+
### Getters
160161
So far we have seen how the `__get__` method is called when we assign an instance of a descriptors to a class attribute.
161162
But we can access that attribute either from the class itself, or the instance - as we saw in the last lecture, both accesses end up calling the `__get__` method.
162163
So, when __get__ is called, we may want to know:
@@ -302,3 +303,109 @@ rocket1.countdown
302303
```
303304

304305
As you can see, the current countdown value is shared by both `rocket1` and `rocket2` instances of `Rocket` - this is because the `Countdown` instance is a class attribute of `Rocket`. So we have to be careful how we deal with instance level state.
306+
307+
308+
### Setters
309+
The `__set__` method works in a similar way to `__get__` but it is used when we assign a value to the class attribute.
310+
`__set__` signature is as follows: self, instance, value
311+
- instance: the instance the __set__ method was called from
312+
- value: the value we want to assign to the attribute
313+
314+
You'll notice there is no owner_class like we have in the `__get__` method
315+
setters (and deleters) are always called from instances
316+
317+
```python
318+
class IntegerValue:
319+
def __set__(self, instance, value):
320+
print(f'__set__ called, instance={instance}, value={value}')
321+
322+
def __get__(self, instance, owner_class):
323+
if instance is None:
324+
print('__get__ called from class')
325+
else:
326+
print(f'__get__ called, instance={instance}, owner_class={owner_class}')
327+
328+
class Point2D:
329+
x = IntegerValue()
330+
y = IntegerValue()
331+
332+
p = Point2D()
333+
p.x = 100
334+
> '__set__ called, instance=<__main__.Point2D object at 0x000001E7564084D0>, value=100'
335+
```
336+
337+
## Caveat with Set and Delete (and Get)
338+
Notice that we have only created a single instance of the `TimeUTC` descriptor:
339+
```python
340+
class Logger:
341+
current_time = TimeUTC()
342+
```
343+
So what happens when we do this?
344+
```python
345+
l1 = Logger()
346+
l2 = Logger()
347+
```
348+
Any instance of `Logger` will be referencing the same instance of `TimeUTC` in this case it does not matter because `__get__` just returns the current UTC time
349+
But what happens when we have to "store" and "retrieve" data from the instances?
350+
Suppose `IntegerValue` is a data descriptor -> implements `__get__` and `__set__` methods
351+
```python
352+
class Point2D:
353+
x = IntegerValue()
354+
y = IntegerValue()
355+
# two separate instances of IntegerValue assigned to the class attributes x and y
356+
357+
p1 = Point2D()
358+
p2 = Point2D()
359+
# two separate instances of Point2D
360+
```
361+
But what object does p1.x reference? -> the class attribute x
362+
what about p2.x? -> the same class attribute x (the same instance of IntegerValue)
363+
364+
we have to be mindful of which instance we are "storing" the data for
365+
this is one of the reasons both __get__ and __set__ need to know the **instance**
366+
367+
## Storing
368+
So, where should we store the values `x` and `y` in the previous example?
369+
370+
### Approach 1
371+
Many "tutorials" I see on the web naively store the value in the descriptor itself:
372+
```python
373+
class IntegerValue:
374+
def __set__(self, instance, value):
375+
self._value = int(value)
376+
377+
def __get__(self, instance, owner_class):
378+
if instance is None:
379+
return self
380+
else:
381+
return self._value
382+
383+
class Point2D:
384+
x = IntegerValue()
385+
y = IntegerValue()
386+
```
387+
At first blush, this seems to work just fine:
388+
```python
389+
p1 = Point2D()
390+
p2 = Point2D()
391+
392+
p1.x = 1.1
393+
p1.y = 2.2
394+
395+
print(p1.x, p1.y)
396+
> (1, 2)
397+
```
398+
But, remember the point I was making about the instance of the descriptor (`IntegeraValue` in this case) being shared by all instances of the class (`Point2D` in this case)?
399+
```python
400+
p2 = Point2D()
401+
print(p2.x, p2.y)
402+
> (1, 2)
403+
```
404+
And of course if we set the value:
405+
```python
406+
p2.x = 100.9
407+
print(p2.x, p1.x)
408+
> (100, 100)
409+
```
410+
So, obviously using the descriptor instance dictionary for storage at the instance level is probably not going to work in most cases!
411+
And this is the reason both the `__get__` and `__set__` methods need to know which instance we are dealing with.

0 commit comments

Comments
 (0)
0