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