Mercurial > js4py
comparison source/javascript-objects.rst @ 94:454054afcc0a europython-2011
$ cp -piv ../python-classes-tutorial/source/javascript-objects.rst source/ # and some other changes
| author | Jonathan Fine <jfine@pytex.org> |
|---|---|
| date | Sun, 19 Jun 2011 20:24:03 +0100 |
| parents | |
| children | 4e6cfa066d1a |
comparison
equal
deleted
inserted
replaced
| 93:5abfbcfc01f6 | 94:454054afcc0a |
|---|---|
| 1 JavaScript objects | |
| 2 ================== | |
| 3 | |
| 4 Like Python classes | |
| 5 ------------------- | |
| 6 | |
| 7 In JavaScript all objects are part of an inheritance tree. The | |
| 8 **create** function adds a node to the inheritance tree. | |
| 9 | |
| 10 .. code-block:: javascript | |
| 11 | |
| 12 // A JavaScript object. | |
| 13 js> root = {} | |
| 14 | |
| 15 // Prototype inheritance. | |
| 16 js> create = function (obj) { | |
| 17 var f = function () {return this;}; | |
| 18 f.prototype = obj; | |
| 19 return new f; | |
| 20 } | |
| 21 | |
| 22 js> a = create(root) | |
| 23 js> b = create(a) | |
| 24 | |
| 25 js> a.name = 5 | |
| 26 js> a.name | |
| 27 5 | |
| 28 js> b.name | |
| 29 5 | |
| 30 | |
| 31 In Python classes inherit in the same way. | |
| 32 | |
| 33 .. doctest:: | |
| 34 | |
| 35 >>> root = type # Most classes are instance of type. | |
| 36 >>> class a(root): pass | |
| 37 >>> class b(a): pass # Class inheritance. | |
| 38 | |
| 39 >>> a.name = 5 # Just like JavaScript. | |
| 40 >>> a.name | |
| 41 5 | |
| 42 >>> b.name | |
| 43 5 | |
| 44 | |
| 45 class explanation | |
| 46 ^^^^^^^^^^^^^^^^^ | |
| 47 | |
| 48 In Python we can subclass anything whose type is **type** (or a | |
| 49 subclass of type). A subclass (and its instances) inherits properties | |
| 50 from the super-class. | |
| 51 | |
| 52 >>> type(root) == type(a) == type(b) == type | |
| 53 True | |
| 54 | |
| 55 | |
| 56 | |
| 57 Custom item methods | |
| 58 ------------------- | |
| 59 | |
| 60 In JavaScript attribute and item access are the same. | |
| 61 | |
| 62 .. code-block:: javascript | |
| 63 | |
| 64 js> a = create(root) | |
| 65 | |
| 66 js> a.name = 5 | |
| 67 js> a['name'] | |
| 68 5 | |
| 69 | |
| 70 js> a['key'] = 6 | |
| 71 js> a.key | |
| 72 6 | |
| 73 | |
| 74 js> a[1] = 6 | |
| 75 js> a['1'] | |
| 76 6 | |
| 77 | |
| 78 In Python we can defined our own item methods. (The programmer owns | |
| 79 the dot.) | |
| 80 | |
| 81 .. doctest:: | |
| 82 | |
| 83 >>> class A(object): | |
| 84 ... | |
| 85 ... def __getitem__(self, key): | |
| 86 ... return getattr(self, str(key)) | |
| 87 ... def __setitem__(self, key, value): | |
| 88 ... return setattr(self, str(key), value) | |
| 89 | |
| 90 >>> a = A() | |
| 91 >>> a.name = 5 | |
| 92 | |
| 93 >>> a['name'] | |
| 94 5 | |
| 95 | |
| 96 >>> a['key'] = 6 | |
| 97 >>> a.key | |
| 98 6 | |
| 99 | |
| 100 >>> a[1] = 6 | |
| 101 >>> a['1'] | |
| 102 6 | |
| 103 | |
| 104 Because **type(a)** is **A**, which has the special item methods, we | |
| 105 get the special item behaviour. | |
| 106 | |
| 107 .. doctest:: | |
| 108 | |
| 109 >>> type(a) is A | |
| 110 True | |
| 111 | |
| 112 | |
| 113 On metaclass | |
| 114 ------------ | |
| 115 | |
| 116 Using previous definition, we cannot subclass **a** to create **b**. | |
| 117 | |
| 118 .. doctest:: | |
| 119 | |
| 120 >>> class b(a): pass | |
| 121 Traceback (most recent call last): | |
| 122 class b(a): pass | |
| 123 TypeError: Error when calling the metaclass bases | |
| 124 object.__new__() takes no parameters | |
| 125 | |
| 126 This is because **a** is not a type. The solution involves Python | |
| 127 metaclasses (an advanced topic). | |
| 128 | |
| 129 .. doctest:: | |
| 130 | |
| 131 >>> isinstance(a, type) | |
| 132 False | |
| 133 | |
| 134 | |
| 135 metaclass construction | |
| 136 ^^^^^^^^^^^^^^^^^^^^^^ | |
| 137 We will subclass type, not object, and add to it the special item | |
| 138 methods. | |
| 139 | |
| 140 | |
| 141 .. doctest:: | |
| 142 | |
| 143 >>> class ObjectType(type): | |
| 144 ... | |
| 145 ... def __getitem__(self, key): | |
| 146 ... return getattr(self, str(key)) | |
| 147 ... | |
| 148 ... def __setitem__(self, key, value): | |
| 149 ... return setattr(self, str(key), value) | |
| 150 | |
| 151 Here is a fancy way of calling **ObjectType**. | |
| 152 | |
| 153 .. doctest:: | |
| 154 | |
| 155 >>> class root(object): | |
| 156 ... __metaclass__ = ObjectType | |
| 157 | |
| 158 | |
| 159 Here is a more direct (and equivalent) construction (create an | |
| 160 instance of **ObjectType**, whose instances are objects). | |
| 161 | |
| 162 .. doctest:: | |
| 163 | |
| 164 >>> root = ObjectType('root', (object,), {}) | |
| 165 >>> isinstance(root(), object) | |
| 166 True | |
| 167 | |
| 168 metaclass demonstration | |
| 169 ^^^^^^^^^^^^^^^^^^^^^^^ | |
| 170 .. doctest:: | |
| 171 | |
| 172 >>> class a(root): pass | |
| 173 >>> class b(a): pass | |
| 174 | |
| 175 >>> a.name = 5 | |
| 176 >>> a.name | |
| 177 5 | |
| 178 >>> b.name | |
| 179 5 | |
| 180 >>> a['name'] | |
| 181 5 | |
| 182 >>> b['name'] | |
| 183 5 | |
| 184 | |
| 185 >>> a[1] = 6 | |
| 186 >>> a['1'] | |
| 187 6 | |
| 188 | |
| 189 | |
| 190 metaclass explanation | |
| 191 ^^^^^^^^^^^^^^^^^^^^^ | |
| 192 | |
| 193 Because **type(root)** is a subclass of type we can subclass root. | |
| 194 | |
| 195 .. doctest:: | |
| 196 | |
| 197 >>> issubclass(type(root), type) | |
| 198 True | |
| 199 | |
| 200 Because the **type(root)** is **ObjectType**, which has special item | |
| 201 methods, we get the special item behaviour. | |
| 202 | |
| 203 .. doctest:: | |
| 204 | |
| 205 >>> type(root) == type(a) == type(b) == ObjectType | |
| 206 True | |
| 207 | |
| 208 | |
| 209 Never instantiated | |
| 210 ------------------ | |
| 211 | |
| 212 We can't call JavaScript objects (unless they are a function). But | |
| 213 **create** creates ordinary JavaScript objects. | |
| 214 | |
| 215 .. code-block:: javascript | |
| 216 | |
| 217 js> a = create(root) | |
| 218 js> a(1, 2, 3) | |
| 219 TypeError: a is not a function | |
| 220 | |
| 221 | |
| 222 We will monkey-patch the previous Python class, to provide custom | |
| 223 behaviour when called. | |
| 224 | |
| 225 .. doctest:: | |
| 226 | |
| 227 >>> def raise_not_a_function(obj, *argv, **kwargs): | |
| 228 ... raise TypeError, obj.__name__ + ' is not a function' | |
| 229 | |
| 230 >>> ObjectType.__call__ = raise_not_a_function | |
| 231 | |
| 232 >>> a(1, 2, 3) | |
| 233 Traceback (most recent call last): | |
| 234 a(1, 2, 3) | |
| 235 TypeError: a is not a function | |
| 236 | |
| 237 | |
| 238 Conclusion | |
| 239 ---------- | |
| 240 | |
| 241 JavaScript objects are like Python classes (because they inherit like | |
| 242 Python classes). | |
| 243 | |
| 244 For JavaScript attribute and item access are the same. This is | |
| 245 achieved in Python by providing custom item methods. | |
| 246 | |
| 247 In Python the custom item methods must be placed on the type of the | |
| 248 object (or a superclass of its type). | |
| 249 | |
| 250 Ordinary JavaScript objects are not functions and cannot be called. A | |
| 251 Python class can be called (to create an instance of the object). But | |
| 252 we can override this behaviour by supplying a custom method for call. | |
| 253 | |
| 254 To summarize: | |
| 255 .. | |
| 256 | |
| 257 **JavaScript objects are like Python classes with custom item | |
| 258 methods (on the metaclass) that are never instantiated.** | |
| 259 | |
| 260 It's worth saying again: | |
| 261 | |
| 262 .. | |
| 263 | |
| 264 **JavaScript objects are like Python classes with custom item | |
| 265 methods (on the metaclass) that are never instantiated.** | |
| 266 | |
| 267 | |
| 268 |
