Welcome, guest | Sign In | My Account | Store | Cart

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.

Python, 209 lines
  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!

Created by Wai Yip Tung on Sun, 5 Sep 2010 (MIT)
Python recipes (4591)
Wai Yip Tung's recipes (9)

Required Modules

  • (none specified)

Other Information and Tasks