8.10. Operator Accessors

  • a(b) - call

  • a[b] - getitem

  • a[b] - missing

  • a[b] = x - setitem

  • del a[b] - delitem

8.10.1. About

Table 8.8. Operator Overload

Operator

Method

Remarks

obj(x)

obj.__call__(x)

obj[x]

obj.__getitem__(x)

obj[x]

obj.__missing__(x)

(when x is not in obj)

obj[x] = 1

obj.__setitem__(x, 1)

del obj[x]

obj.__delitem__(x)

8.10.2. Call

Problem:

>>> class Vector:
...     def __init__(self, x, y):
...         self.x = x
...         self.y = y
...
...     def __repr__(self):
...         return f'Vector(x={self.x}, y={self.y})'
>>>
>>>
>>> a = Vector(x=1, y=2)
>>>
>>> a()
Traceback (most recent call last):
TypeError: 'Vector' object is not callable

Solution:

>>> class Vector:
...     def __init__(self, x, y):
...         self.x = x
...         self.y = y
...
...     def __repr__(self):
...         return f'Vector(x={self.x}, y={self.y})'
...
...     def __call__(self, *args, **kwargs):
...         return 'hello'
>>>
>>>
>>> a = Vector(x=1, y=2)
>>>
>>> a()
'hello'
>>>
>>> a(1, 2, a=10, b=20)
'hello'

8.10.3. Getitem

Problem:

>>> class Vector:
...     def __init__(self, x, y):
...         self.x = x
...         self.y = y
...
...     def __repr__(self):
...         return f'Vector(x={self.x}, y={self.y})'
>>>
>>>
>>> a = Vector(x=1, y=2)
>>>
>>> a['x']
Traceback (most recent call last):
TypeError: 'Vector' object is not subscriptable
>>>
>>> a['y']
Traceback (most recent call last):
TypeError: 'Vector' object is not subscriptable
>>>
>>> a[0]
Traceback (most recent call last):
TypeError: 'Vector' object is not subscriptable
>>>
>>> a[1]
Traceback (most recent call last):
TypeError: 'Vector' object is not subscriptable

Solution:

>>> class Vector:
...     def __init__(self, x, y):
...         self.x = x
...         self.y = y
...
...     def __repr__(self):
...         return f'Vector(x={self.x}, y={self.y})'
...
...     def __getitem__(self, key):
...         match key:
...             case 'x' | 0: return self.x
...             case 'y' | 1: return self.y
...             case _: KeyError(item)
>>>
>>>
>>> a = Vector(x=1, y=2)
>>>
>>> a['x']
1
>>>
>>> a['y']
2
>>>
>>> a[0]
1
>>>
>>> a[1]
2

8.10.4. Setitem

>>> class Vector:
...     def __init__(self, x, y):
...         self.x = x
...         self.y = y
...
...     def __repr__(self):
...         return f'Vector(x={self.x}, y={self.y})'
...
...     def __setitem__(self, name, value):
...         match name:
...             case 'x' | 0: self.x = value
...             case 'y' | 1: self.y = value
...             case _: raise KeyError(name)
>>>
>>>
>>> a = Vector(1, 2)
>>>
>>> a['x'] = 10
>>> a
Vector(x=10, y=2)
>>>
>>> a[1] = 20
>>> a
Vector(x=10, y=20)

8.10.5. Delitem

>>> class Vector:
...     def __init__(self, x, y):
...         self.x = x
...         self.y = y
...
...     def __repr__(self):
...         return f'Vector(x={self.x}, y={self.y})'
...
...     def __delitem__(self, name):
...         match name:
...             case 'x' | 0: self.x = None
...             case 'y' | 1: self.y = None
...             case _: raise KeyError(name)
>>>
>>>
>>> a = Vector(1, 2)
>>>
>>> del a['x']
>>> a
Vector(x=None, y=2)
>>>
>>> del a[1]
>>> a
Vector(x=None, y=None)

8.10.6. Operator

  • operator.call(a, b) - call - a(b)

  • operator.getitem(a, b) - getitem - a[b]

  • operator.setitem(a, b, 1) - setitem - a[b] = 1

  • operator.delitem(a, b) - delitem - del a[b]

>>> import operator
>>> operator.call(print, 'Mark Watney')
Mark Watney
>>> operator.getitem([1, 2, 3], 1)
2
>>> operator.setitem([1, 2, 3], 1, 4)
>>> operator.delitem([1, 2, 3], 1)

8.10.7. Use Case - 1

>>> data = dict()
>>>
>>> data['a'] = 10  # data.__setitem__('a', 10) -> None
>>> data['a']       # data.__getitem__('a') -> 10
10
>>>
>>> data['x']       # data.__getitem__('x') -> data.__missing__() -> KeyError: 'x'
Traceback (most recent call last):
KeyError: 'x'
>>>
>>> data()          # data.__call__() -> TypeError: 'dict' object is not callable
Traceback (most recent call last):
TypeError: 'dict' object is not callable

8.10.8. Use Case - 2

Recap information about slice:

>>> data = slice(1, 2, 3)
>>>
>>>
>>> data.start
1
>>>
>>> data.stop
2
>>>
>>> data.step
3

Let's define a class with getitem and it's instance:

>>> class MyClass:
...     def __getitem__(self, item):
...         print(type(item))
>>>
>>>
>>> my = MyClass()
>>> my[1]
<class 'int'>
>>>
>>> my[1.0]
<class 'float'>
>>>
>>> my[1:2]
<class 'slice'>
>>>
>>> my[1,2]
<class 'tuple'>
>>>
>>> my[[1,2]]
<class 'list'>
>>>
>>> my['1969-07-20':'1969-07-21']
<class 'slice'>
>>>
>>> my[['Evening','Morning']]
<class 'list'>
>>>
>>> my['1969-07-20':'1969-07-21', ['Evening','Morning']]
<class 'tuple'>

8.10.9. Use Case - 3

Getitem in numpy:

>>> import numpy as np
>>>
>>>
>>> data = np.array([[1, 2, 3],
...                  [4, 5, 6],
...                  [7, 8, 9]])
>>>
>>> data[1][2]
np.int64(6)
>>>
>>> data[1,2]
np.int64(6)
>>>
>>> data[1:2]
array([[4, 5, 6]])
>>>
>>> data[1:2, 0]
array([4])
>>>
>>> data[1:2, 1:]
array([[5, 6]])

data[1]:

>>> data.__getitem__(1)
array([4, 5, 6])

data[1,2]:

>>> data.__getitem__((1,2))
np.int64(6)

data[1:2]:

>>> data.__getitem__(slice(1,2))
array([[4, 5, 6]])

data[:, 2]:

>>> data.__getitem__((slice(None,None,None), 2))
array([3, 6, 9])

Intuitive implementation of numpy array[row,col] accessor:

>>> class array(np.ndarray):
...     def __getitem__(key):
...         if isinstance(key, int):
...             return super().__getitem__(key)
...
...         if isinstance(key, tuple):
...             row = key[0]
...             col = key[1]
...             return super().__getitem__(row).__getitem__(col)
...
...         if isinstance(key, slice):
...             start = key[0] if key[0] else 0
...             stop = key[1] if key[0] else len(self)
...             step = key[2] if key[2] else 1
...             return ...

8.10.10. Use Case - 4

  • Cache

>>> class Cache(dict):
...     def __init__(self, func):
...         self.func = func
...
...     def __call__(self, *args):
...         return self[args]
...
...     def __missing__(self, key):
...         self[key] = self.func(*key)
...         return self[key]
>>>
>>>
>>> @Cache
... def add(a, b):
...     return a + b
>>>
>>>
>>> _ = add(1,2)  # computed
>>> _ = add(1,2)  # fetched from cache
>>> _ = add(1,2)  # fetched from cache
>>> _ = add(1,2)  # fetched from cache
>>> _ = add(2,1)  # computed
>>> _ = add(2,1)  # fetched from cache
>>>
>>> add  
{(1, 2): 3,
 (2, 1): 3}