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

This recipe is how you can show that the Python operators and builtin functions directly use the special methods from an object's class, rather than using normal attribute lookup on the object.

Python, 128 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
# list of special methods from http://docs.python.org/py3k/reference/datamodel.html
names = {
        "control": "{}.control", # a normal method, for comparison

        "__new__": None,
        #"__new__": "{}()", # if an instance of type
        "__init__": None,
        #"__init__": "{}()", # if an instance of type
        "__prepare__": None,
        #"__prepare__": "{}()", # if an instance of type
        "__del__": "del {}",
        "__repr__": "repr({})",
        "__str__": "str({})",
        "__format__": "'{{}}'.format({})",
        "__lt__": "{} < 5",
        "__le__": "{} <= 5",
        "__eq__": "{} == 5",
        "__ne__": "{} != 5",
        "__gt__": "{} > 5",
        "__ge__": "{} >= 5",
        "__hash__": "hash({})",
        "__bool__": "bool({})",
        "__getattr__": None,
        #"__getattr__": "{}.x",
        "__getattribute__": None,
        #"__getattribute__": "{}.x",
        "__setattr__": "{}.x = 5",
        "__delattr__": "del {}.x",
        "__dir__": "dir({})",
        "__get__": None,
        "__set__": None,
        "__delete__": None,
        "__slots__": None,
        "__call__": "{}()",
        "__len__": "len({})",
        "__getitem__": "{}[1]",
        "__setitem__": "{}[1] = 5",
        "__delitem__": "del {}[1]",
        "__iter__": "iter({})",
        "__reversed__": "reversed({})",
        "__contains__": "5 in {}",
        "__add__": "{} + 5",
        "__sub__": "{} - 5",
        "__mul__": "{} * 5",
        "__truediv__": "{} / 5",
        "__floordiv__": "{} // 5",
        "__mod__": "{} % 5",
        "__divmod__": "divmod({}, 5)",
        "__pow__": "{}**5",
        "__lshift__": "{} << 5",
        "__rshift__": "{} >> 5",
        "__and__": "{} & 5",
        "__xor__": "{} ^ 5",
        "__or__": "{} | 5",
        "__radd__": "5 + {}",
        "__rsub__": "5 - {}",
        "__rmul__": "5 * {}",
        "__rtruediv__": "5 / {}",
        "__rfloordiv__": "5 // {}",
        "__rmod__": "5 % {}",
        "__rdivmod__": "divmod(5, {})",
        "__rpow__": "5 ** {}",
        "__rlshift__": "5 << {}",
        "__rrshift__": "5 >> {}",
        "__rand__": "5 & {}",
        "__rxor__": "5 ^ {}",
        "__ror__": "5 | {}",
        "__iadd__": "{} += 5",
        "__isub__": "{} -= 5",
        "__imul__": "{} *= 5",
        "__itruediv__": "{} /= 5",
        "__ifloordiv__": "{} //= 5",
        "__imod__": "{} %= 5",
        "__ipow__": "{} **= 5",
        "__ilshift__": "{} <<= 5",
        "__irshift__": "{} >>= 5",
        "__iand__": "{} &= 5",
        "__ixor__": "{} ^= 5",
        "__ior__": "{} |= 5",
        "__neg__": "-{}",
        "__pos__": "+{}",
        "__abs__": "abs({})",
        "__invert__": "~{}",
        "__complex__": "complex({})",
        "__int__": "int({})",
        "__float__": "float({})",
        "__round__": "round({})",
        "__index__": "oct({})",
        "__enter__": None,
        "__exit__": None,
        }

SOURCE = """\
def {0}(self, *args, **kwargs):
    raise Called
Test{0} = type("Test{0}", (Test,), dict({0}={0}))
test{0} = Test{0}()
{1}
"""

class InstanceChecked(Exception): pass
class Called(Exception): pass

class Test:
    def __getattribute__(self, name):
        raise InstanceChecked

skipped = []
for name in names:
    if not names[name]:
        skipped.append(name)
        continue

    print("#########################")
    print("  "+name)

    source = SOURCE.format(name, names[name].format("test"+name))
    try:
        exec(source, globals(), globals())
    except Called:
        print("   Doing it")
    except InstanceChecked:
        print("   --- instance __getattribute__ called")
    #except Exception:
    #    pass

print("#########################")
print("skipped {}: {}".format(len(skipped), skipped))

See http://docs.python.org/py3k/reference/datamodel.html#special-method-lookup for an explanation of special method lookup.

The special methods cover all of the Python operators, several other language features (like for loops), and a number of the builtin functions. If some functionality in CPython is written in C, it's probably using special method lookup.

Running the recipe you can see that these features don't use normal attribute lookup; they're pulling the methods directly from the object's type. And they don't even use normal attribute lookup for that. Instead, (in CPython) they effectively make direct calls on the functions stored in the PyTypeObject struct of the object's type.

So just to be clear, it isn't that the special methods are special on their own or that there is some kind of special casing for when a method has a double underscore at it's beginning and end. Rather, the related Python features simply use the attributes on the types and do it directly. You could do it too if you wanted to. It's just a more roundabout approach outside of C extension modules.

The builtins and operators do it because a handful of the special methods have to be done this way, so all of them are used this way.

FYI, starting in Python 3.2 the inspect module provides a function for a more static lookup along these lines (see http://docs.python.org/dev/library/inspect.html#inspect.getattr_static).