1# This contains most of the executable examples from Guido's descr 2# tutorial, once at 3# 4# https://www.python.org/download/releases/2.2.3/descrintro/ 5# 6# A few examples left implicit in the writeup were fleshed out, a few were 7# skipped due to lack of interest (e.g., faking super() by hand isn't 8# of much interest anymore), and a few were fiddled to make the output 9# deterministic. 10 11from test.support import sortdict 12import pprint 13import doctest 14import unittest 15 16 17class defaultdict(dict): 18 def __init__(self, default=None): 19 dict.__init__(self) 20 self.default = default 21 22 def __getitem__(self, key): 23 try: 24 return dict.__getitem__(self, key) 25 except KeyError: 26 return self.default 27 28 def get(self, key, *args): 29 if not args: 30 args = (self.default,) 31 return dict.get(self, key, *args) 32 33 def merge(self, other): 34 for key in other: 35 if key not in self: 36 self[key] = other[key] 37 38test_1 = """ 39 40Here's the new type at work: 41 42 >>> print(defaultdict) # show our type 43 <class 'test.test_descrtut.defaultdict'> 44 >>> print(type(defaultdict)) # its metatype 45 <class 'type'> 46 >>> a = defaultdict(default=0.0) # create an instance 47 >>> print(a) # show the instance 48 {} 49 >>> print(type(a)) # show its type 50 <class 'test.test_descrtut.defaultdict'> 51 >>> print(a.__class__) # show its class 52 <class 'test.test_descrtut.defaultdict'> 53 >>> print(type(a) is a.__class__) # its type is its class 54 True 55 >>> a[1] = 3.25 # modify the instance 56 >>> print(a) # show the new value 57 {1: 3.25} 58 >>> print(a[1]) # show the new item 59 3.25 60 >>> print(a[0]) # a non-existent item 61 0.0 62 >>> a.merge({1:100, 2:200}) # use a dict method 63 >>> print(sortdict(a)) # show the result 64 {1: 3.25, 2: 200} 65 >>> 66 67We can also use the new type in contexts where classic only allows "real" 68dictionaries, such as the locals/globals dictionaries for the exec 69statement or the built-in function eval(): 70 71 >>> print(sorted(a.keys())) 72 [1, 2] 73 >>> a['print'] = print # need the print function here 74 >>> exec("x = 3; print(x)", a) 75 3 76 >>> print(sorted(a.keys(), key=lambda x: (str(type(x)), x))) 77 [1, 2, '__builtins__', 'print', 'x'] 78 >>> print(a['x']) 79 3 80 >>> 81 82Now I'll show that defaultdict instances have dynamic instance variables, 83just like classic classes: 84 85 >>> a.default = -1 86 >>> print(a["noway"]) 87 -1 88 >>> a.default = -1000 89 >>> print(a["noway"]) 90 -1000 91 >>> 'default' in dir(a) 92 True 93 >>> a.x1 = 100 94 >>> a.x2 = 200 95 >>> print(a.x1) 96 100 97 >>> d = dir(a) 98 >>> 'default' in d and 'x1' in d and 'x2' in d 99 True 100 >>> print(sortdict(a.__dict__)) 101 {'default': -1000, 'x1': 100, 'x2': 200} 102 >>> 103""" 104 105class defaultdict2(dict): 106 __slots__ = ['default'] 107 108 def __init__(self, default=None): 109 dict.__init__(self) 110 self.default = default 111 112 def __getitem__(self, key): 113 try: 114 return dict.__getitem__(self, key) 115 except KeyError: 116 return self.default 117 118 def get(self, key, *args): 119 if not args: 120 args = (self.default,) 121 return dict.get(self, key, *args) 122 123 def merge(self, other): 124 for key in other: 125 if key not in self: 126 self[key] = other[key] 127 128test_2 = """ 129 130The __slots__ declaration takes a list of instance variables, and reserves 131space for exactly these in the instance. When __slots__ is used, other 132instance variables cannot be assigned to: 133 134 >>> a = defaultdict2(default=0.0) 135 >>> a[1] 136 0.0 137 >>> a.default = -1 138 >>> a[1] 139 -1 140 >>> a.x1 = 1 141 Traceback (most recent call last): 142 File "<stdin>", line 1, in ? 143 AttributeError: 'defaultdict2' object has no attribute 'x1' 144 >>> 145 146""" 147 148test_3 = """ 149 150Introspecting instances of built-in types 151 152For instance of built-in types, x.__class__ is now the same as type(x): 153 154 >>> type([]) 155 <class 'list'> 156 >>> [].__class__ 157 <class 'list'> 158 >>> list 159 <class 'list'> 160 >>> isinstance([], list) 161 True 162 >>> isinstance([], dict) 163 False 164 >>> isinstance([], object) 165 True 166 >>> 167 168You can get the information from the list type: 169 170 >>> pprint.pprint(dir(list)) # like list.__dict__.keys(), but sorted 171 ['__add__', 172 '__class__', 173 '__class_getitem__', 174 '__contains__', 175 '__delattr__', 176 '__delitem__', 177 '__dir__', 178 '__doc__', 179 '__eq__', 180 '__format__', 181 '__ge__', 182 '__getattribute__', 183 '__getitem__', 184 '__getstate__', 185 '__gt__', 186 '__hash__', 187 '__iadd__', 188 '__imul__', 189 '__init__', 190 '__init_subclass__', 191 '__iter__', 192 '__le__', 193 '__len__', 194 '__lt__', 195 '__mul__', 196 '__ne__', 197 '__new__', 198 '__reduce__', 199 '__reduce_ex__', 200 '__repr__', 201 '__reversed__', 202 '__rmul__', 203 '__setattr__', 204 '__setitem__', 205 '__sizeof__', 206 '__str__', 207 '__subclasshook__', 208 'append', 209 'clear', 210 'copy', 211 'count', 212 'extend', 213 'index', 214 'insert', 215 'pop', 216 'remove', 217 'reverse', 218 'sort'] 219 220The new introspection API gives more information than the old one: in 221addition to the regular methods, it also shows the methods that are 222normally invoked through special notations, e.g. __iadd__ (+=), __len__ 223(len), __ne__ (!=). You can invoke any method from this list directly: 224 225 >>> a = ['tic', 'tac'] 226 >>> list.__len__(a) # same as len(a) 227 2 228 >>> a.__len__() # ditto 229 2 230 >>> list.append(a, 'toe') # same as a.append('toe') 231 >>> a 232 ['tic', 'tac', 'toe'] 233 >>> 234 235This is just like it is for user-defined classes. 236""" 237 238test_4 = """ 239 240Static methods and class methods 241 242The new introspection API makes it possible to add static methods and class 243methods. Static methods are easy to describe: they behave pretty much like 244static methods in C++ or Java. Here's an example: 245 246 >>> class C: 247 ... 248 ... @staticmethod 249 ... def foo(x, y): 250 ... print("staticmethod", x, y) 251 252 >>> C.foo(1, 2) 253 staticmethod 1 2 254 >>> c = C() 255 >>> c.foo(1, 2) 256 staticmethod 1 2 257 258Class methods use a similar pattern to declare methods that receive an 259implicit first argument that is the *class* for which they are invoked. 260 261 >>> class C: 262 ... @classmethod 263 ... def foo(cls, y): 264 ... print("classmethod", cls, y) 265 266 >>> C.foo(1) 267 classmethod <class 'test.test_descrtut.C'> 1 268 >>> c = C() 269 >>> c.foo(1) 270 classmethod <class 'test.test_descrtut.C'> 1 271 272 >>> class D(C): 273 ... pass 274 275 >>> D.foo(1) 276 classmethod <class 'test.test_descrtut.D'> 1 277 >>> d = D() 278 >>> d.foo(1) 279 classmethod <class 'test.test_descrtut.D'> 1 280 281This prints "classmethod __main__.D 1" both times; in other words, the 282class passed as the first argument of foo() is the class involved in the 283call, not the class involved in the definition of foo(). 284 285But notice this: 286 287 >>> class E(C): 288 ... @classmethod 289 ... def foo(cls, y): # override C.foo 290 ... print("E.foo() called") 291 ... C.foo(y) 292 293 >>> E.foo(1) 294 E.foo() called 295 classmethod <class 'test.test_descrtut.C'> 1 296 >>> e = E() 297 >>> e.foo(1) 298 E.foo() called 299 classmethod <class 'test.test_descrtut.C'> 1 300 301In this example, the call to C.foo() from E.foo() will see class C as its 302first argument, not class E. This is to be expected, since the call 303specifies the class C. But it stresses the difference between these class 304methods and methods defined in metaclasses (where an upcall to a metamethod 305would pass the target class as an explicit first argument). 306""" 307 308test_5 = """ 309 310Attributes defined by get/set methods 311 312 313 >>> class property(object): 314 ... 315 ... def __init__(self, get, set=None): 316 ... self.__get = get 317 ... self.__set = set 318 ... 319 ... def __get__(self, inst, type=None): 320 ... return self.__get(inst) 321 ... 322 ... def __set__(self, inst, value): 323 ... if self.__set is None: 324 ... raise AttributeError("this attribute is read-only") 325 ... return self.__set(inst, value) 326 327Now let's define a class with an attribute x defined by a pair of methods, 328getx() and setx(): 329 330 >>> class C(object): 331 ... 332 ... def __init__(self): 333 ... self.__x = 0 334 ... 335 ... def getx(self): 336 ... return self.__x 337 ... 338 ... def setx(self, x): 339 ... if x < 0: x = 0 340 ... self.__x = x 341 ... 342 ... x = property(getx, setx) 343 344Here's a small demonstration: 345 346 >>> a = C() 347 >>> a.x = 10 348 >>> print(a.x) 349 10 350 >>> a.x = -10 351 >>> print(a.x) 352 0 353 >>> 354 355Hmm -- property is builtin now, so let's try it that way too. 356 357 >>> del property # unmask the builtin 358 >>> property 359 <class 'property'> 360 361 >>> class C(object): 362 ... def __init__(self): 363 ... self.__x = 0 364 ... def getx(self): 365 ... return self.__x 366 ... def setx(self, x): 367 ... if x < 0: x = 0 368 ... self.__x = x 369 ... x = property(getx, setx) 370 371 372 >>> a = C() 373 >>> a.x = 10 374 >>> print(a.x) 375 10 376 >>> a.x = -10 377 >>> print(a.x) 378 0 379 >>> 380""" 381 382test_6 = """ 383 384Method resolution order 385 386This example is implicit in the writeup. 387 388>>> class A: # implicit new-style class 389... def save(self): 390... print("called A.save()") 391>>> class B(A): 392... pass 393>>> class C(A): 394... def save(self): 395... print("called C.save()") 396>>> class D(B, C): 397... pass 398 399>>> D().save() 400called C.save() 401 402>>> class A(object): # explicit new-style class 403... def save(self): 404... print("called A.save()") 405>>> class B(A): 406... pass 407>>> class C(A): 408... def save(self): 409... print("called C.save()") 410>>> class D(B, C): 411... pass 412 413>>> D().save() 414called C.save() 415""" 416 417class A(object): 418 def m(self): 419 return "A" 420 421class B(A): 422 def m(self): 423 return "B" + super(B, self).m() 424 425class C(A): 426 def m(self): 427 return "C" + super(C, self).m() 428 429class D(C, B): 430 def m(self): 431 return "D" + super(D, self).m() 432 433 434test_7 = """ 435 436Cooperative methods and "super" 437 438>>> print(D().m()) # "DCBA" 439DCBA 440""" 441 442test_8 = """ 443 444Backwards incompatibilities 445 446>>> class A: 447... def foo(self): 448... print("called A.foo()") 449 450>>> class B(A): 451... pass 452 453>>> class C(A): 454... def foo(self): 455... B.foo(self) 456 457>>> C().foo() 458called A.foo() 459 460>>> class C(A): 461... def foo(self): 462... A.foo(self) 463>>> C().foo() 464called A.foo() 465""" 466 467__test__ = {"tut1": test_1, 468 "tut2": test_2, 469 "tut3": test_3, 470 "tut4": test_4, 471 "tut5": test_5, 472 "tut6": test_6, 473 "tut7": test_7, 474 "tut8": test_8} 475 476def load_tests(loader, tests, pattern): 477 tests.addTest(doctest.DocTestSuite()) 478 return tests 479 480 481if __name__ == "__main__": 482 unittest.main() 483