In Python, classes can define their own behavior with respect to language operators. For example, if a class defines __getitem__(), then x[i], where x is an instance of the clas, will be execute by a call to x.__getitem__(i).
While Python has an extensive documentation on the special methods, reading a specification may not be the best way to reveal the intricate details. object_snoop allows user to observe how Python expressions and statements are translated into special method calls. object_snoop defines most special methods. It simple print a trace and returns a fixed but sensible result. Users are invited to build complex expressions to experiment how Python special methods work.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 | """
object_snoop allows user to observe how Python expressions and statements are
translated into special method calls. object_snoop defines most special methods.
It simple print a trace and returns a fixed but sensible result. Users are
invited to build complex expressions to experiment how Python special methods work.
Reference:
Data model - Python v2.7 documentation
http://docs.python.org/reference/datamodel.html
"""
import inspect
class object_snoop(object):
def __init__(self, name, truth=True):
self.name = name
self.truth = bool(truth)
def __repr__(self):
return '<%s>' % self.name
# let __str__ and __unicode__ fallback to __repr__
def trace(self, *args, **kwargs):
"""
helper method to print "<name>.<func> [: <args> <kwargs>]"
"""
caller_frame = inspect.stack()[1]
caller_name = caller_frame[3]
_name, _caller = self.name, caller_name
_colon = _args = _kwargs = ""
if args or kwargs:
_colon = ' :'
if len(args) == 1:
_args = ' ' + str(args[0])
elif args:
_args = ' ' + str(args)
if kwargs:
_kwargs = ' ' + str(kwargs)
print ("%(_name)s.%(_caller)s%(_colon)s%(_args)s%(_kwargs)s" % locals())
# ------------------------------------------------------------------------
# 1. Basic customization
# Called when the instance is about to be destroyed
def __del__(self): self.trace()
# These are the so-called "rich comparison" methods,
def __lt__(self, other): self.trace(other); return self.truth
def __le__(self, other): self.trace(other); return self.truth
def __eq__(self, other): self.trace(other); return self.truth
def __ne__(self, other): self.trace(other); return self.truth
def __gt__(self, other): self.trace(other); return self.truth
def __ge__(self, other): self.trace(other); return self.truth
# Called by comparison operations if rich comparison (see above) is not defined.
def __cmp__(self, other):
self.trace(other)
return 1
# Called by built-in function hash() and for operations on members of hashed collections
def __hash__(self):
self.trace()
return 1
# Called to implement truth value testing and the built-in operation bool()
def __nonzero__(self):
self.trace()
return self.truth
# ------------------------------------------------------------------------
# 2. Customizing attribute access
def __getattr__(self, name):
self.trace(name)
return 1
def __setattr__(self, name, value):
super(object_snoop,self).__setattr__(name, value)
self.trace(name, value)
def __delattr__(self, name):
super(object_snoop,self).__delattr__(name)
self.trace(name)
# ------------------------------------------------------------------------
# 4. Customizing instance and subclass checks
# ------------------------------------------------------------------------
# 5. Emulating callable objects
def __call__(self, *args, **kwargs):
self.trace(*args, **kwargs)
# ------------------------------------------------------------------------
# 6. Emulating container types
def __len__ (self): self.trace() ; return 1
def __getitem__ (self, key): self.trace(key) ; return 1
def __setitem__ (self, key, value): self.trace(key, value)
def __delitem__ (self, key): self.trace(key)
def __iter__ (self): self.trace() ; return iter([])
def __reversed__(self): self.trace() ; return iter([])
def __contains__(self, item): self.trace(item) ; return self.truth
# ------------------------------------------------------------------------
# 7. Additional methods for emulation of sequence types
# Deprecated since version 2.0
def __getslice__(self, i, j): self.trace(i,j) ; return self
def __setslice__(self, i, j, sequence): self.trace(i,j,sequence)
def __delslice__(self, i, j): self.trace(i,j)
# ------------------------------------------------------------------------
# 8. Emulating numeric types
# These methods are called to implement the binary arithmetic operations (+,
# -, *, //, %, divmod(), pow(), **, <<, >>, &, ^, |).
def __add__ (self, other): self.trace(other); return self
def __sub__ (self, other): self.trace(other); return self
def __mul__ (self, other): self.trace(other); return self
def __floordiv__(self, other): self.trace(other); return self
def __mod__ (self, other): self.trace(other); return self
def __divmod__ (self, other): self.trace(other); return self
def __pow__ (self, other): self.trace(other); return self
def __lshift__ (self, other): self.trace(other); return self
def __rshift__ (self, other): self.trace(other); return self
def __and__ (self, other): self.trace(other); return self
def __xor__ (self, other): self.trace(other); return self
def __or__ (self, other): self.trace(other); return self
def __div__ (self, other): self.trace(other); return self
def __truediv__ (self, other): self.trace(other); return self
# These methods are called to implement the binary arithmetic operations
# with reflected (swapped) operands.
def __radd__ (self, other): self.trace(other); return self
def __rsub__ (self, other): self.trace(other); return self
def __rmul__ (self, other): self.trace(other); return self
def __rdiv__ (self, other): self.trace(other); return self
def __rtruediv__ (self, other): self.trace(other); return self
def __rfloordiv__(self, other): self.trace(other); return self
def __rmod__ (self, other): self.trace(other); return self
def __rdivmod__ (self, other): self.trace(other); return self
def __rpow__ (self, other): self.trace(other); return self
def __rlshift__ (self, other): self.trace(other); return self
def __rrshift__ (self, other): self.trace(other); return self
def __rand__ (self, other): self.trace(other); return self
def __rxor__ (self, other): self.trace(other); return self
def __ror__ (self, other): self.trace(other); return self
# These methods are called to implement the augmented arithmetic assignments
# (+=, -=, *=, /=, //=, %=, **=, <<=, >>=, &=, ^=, |=).
def __iadd__ (self, other): self.trace(other); return self
def __isub__ (self, other): self.trace(other); return self
def __imul__ (self, other): self.trace(other); return self
def __idiv__ (self, other): self.trace(other); return self
def __itruediv__ (self, other): self.trace(other); return self
def __ifloordiv__(self, other): self.trace(other); return self
def __imod__ (self, other): self.trace(other); return self
def __ipow__ (self, other): self.trace(other); return self
def __ilshift__ (self, other): self.trace(other); return self
def __irshift__ (self, other): self.trace(other); return self
def __iand__ (self, other): self.trace(other); return self
def __ixor__ (self, other): self.trace(other); return self
def __ior__ (self, other): self.trace(other); return self
# Called to implement the unary arithmetic operations (-, +, abs() and ~).
def __neg__ (self): self.trace(); return self
def __pos__ (self): self.trace(); return self
def __abs__ (self): self.trace(); return self
def __invert__(self): self.trace(); return self
# Called to implement the built-in functions complex(), int(), long(), and float(), oct() and hex().
def __complex__(self): self.trace(); return 1
def __int__ (self): self.trace(); return 1
def __long__ (self): self.trace(); return 1
def __float__ (self): self.trace(); return 1.0
def __oct__ (self): self.trace(); return 'o'
def __hex__ (self): self.trace(); return 'x'
# Called to implement operator.index().
def __index__ (self): self.trace(); return self
# Called to implement "mixed-mode" numeric arithmetic.
def __coerce__(self, other): self.trace(other); return self
# define two dummy objects. r evaluates to True and s evaluates to False.
r = object_snoop('r', True)
s = object_snoop('s', False)
|
First of all this reveal how arithmetic special methods work.
>>> r += -s + 1 * r
s.__neg__
r.__rmul__ : 1
s.__add__ : <r>
r.__iadd__ : <s>
Next, let's play with subscript and slice.
>>> r[1]
r.__getitem__ : 1
>>> r[1:10]
r.__getslice__ : (1, 10)
>>> r[1::3]
r.__getitem__ : slice(1, None, 3)
Note the irregular call to a deprecated method __getslice__. All subscription could have implemented by __getitem__, but it is not (for Python 2.6).
>>> r[slice(1,10)]
r.__getitem__ : slice(1, 10, None)
Other subscription operations
>>> r[:] = 1
r.__setslice__ : (0, 2147483647, 1)
>>> del r[3:5]
r.__delslice__ : (3, 5)
>>> del r[3:5:2]
r.__delitem__ : slice(3, 5, 2)
Next is some boolean operations. r
evaluates to True and s
evaluates to False.
>>> r or s
r.__nonzero__
<r>
>>> r and s
r.__nonzero__
<s>
>>> r and s and 1
r.__nonzero__
s.__nonzero__
<s>
Notice the short circuit evaluation. s
is not evaluated in r or s
. Also in r and s
, s.__nonzero__
is not called. The object s
is returned instead.
Finally let's look at the comparison operators.
>>> 10 != r
r.__ne__ : 10
True
>>> 1 not in r
r.__contains__ : 1
False
>>> 10 < r < 1
r.__gt__ : 10
r.__lt__ : 1
True
Note that the less than operator do not need to be transitive!