From 95c99e57b37ede725af1fdd1ff914c91284e3048 Mon Sep 17 00:00:00 2001 From: Tim Peters Date: Mon, 3 Sep 2001 01:24:30 +0000 Subject: Made a doctest out of the examples in Guido's type/class tutorial. --- Lib/test/test_descrtut.py | 498 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 498 insertions(+) create mode 100644 Lib/test/test_descrtut.py diff --git a/Lib/test/test_descrtut.py b/Lib/test/test_descrtut.py new file mode 100644 index 0000000..edb0388 --- /dev/null +++ b/Lib/test/test_descrtut.py @@ -0,0 +1,498 @@ +# This contains most of the executable examples from Guido's descr +# tutorial, once at +# +# http://www.python.org/2.2/descrintro.html +# +# A few examples left implicit in the writeup were fleshed out, a few were +# skipped due to lack of interest (e.g., faking super() by hand isn't +# of much interest anymore), and a few were fiddled to make the output +# deterministic. + +from test_support import sortdict +import pprint + +class defaultdict(dictionary): + def __init__(self, default=None): + dictionary.__init__(self) + self.default = default + + def __getitem__(self, key): + try: + return dictionary.__getitem__(self, key) + except KeyError: + return self.default + + def get(self, key, *args): + if not args: + args = (self.default,) + return dictionary.get(self, key, *args) + + def merge(self, other): + for key in other: + if key not in self: + self[key] = other[key] + +test_1 = """ + +Here's the new type at work: + + >>> print defaultdict # show our type + + >>> print type(defaultdict) # its metatype + + >>> a = defaultdict(default=0.0) # create an instance + >>> print a # show the instance + {} + >>> print type(a) # show its type + + >>> print a.__class__ # show its class + + >>> print type(a) is a.__class__ # its type is its class + 1 + >>> a[1] = 3.25 # modify the instance + >>> print a # show the new value + {1: 3.25} + >>> print a[1] # show the new item + 3.25 + >>> print a[0] # a non-existant item + 0.0 + >>> a.merge({1:100, 2:200}) # use a dictionary method + >>> print sortdict(a) # show the result + {1: 3.25, 2: 200} + >>> + +We can also use the new type in contexts where classic only allows "real" +dictionaries, such as the locals/globals dictionaries for the exec +statement or the built-in function eval(): + + >>> def sorted(seq): + ... seq.sort() + ... return seq + >>> print sorted(a.keys()) + [1, 2] + >>> exec "x = 3; print x" in a + 3 + >>> print sorted(a.keys()) + [1, 2, '__builtins__', 'x'] + >>> print a['x'] + 3 + >>> + +However, our __getitem__() method is not used for variable access by the +interpreter: + + >>> exec "print foo" in a + Traceback (most recent call last): + File "", line 1, in ? + File "", line 1, in ? + NameError: name 'foo' is not defined + >>> + +Now I'll show that defaultdict instances have dynamic instance variables, +just like classic classes: + + >>> a.default = -1 + >>> print a["noway"] + -1 + >>> a.default = -1000 + >>> print a["noway"] + -1000 + >>> print dir(a) + ['default'] + >>> a.x1 = 100 + >>> a.x2 = 200 + >>> print a.x1 + 100 + >>> print dir(a) + ['default', 'x1', 'x2'] + >>> print a.__dict__ + {'default': -1000, 'x2': 200, 'x1': 100} + >>> +""" + +class defaultdict2(dictionary): + __slots__ = ['default'] + + def __init__(self, default=None): + dictionary.__init__(self) + self.default = default + + def __getitem__(self, key): + try: + return dictionary.__getitem__(self, key) + except KeyError: + return self.default + + def get(self, key, *args): + if not args: + args = (self.default,) + return dictionary.get(self, key, *args) + + def merge(self, other): + for key in other: + if key not in self: + self[key] = other[key] + +test_2 = """ + +The __slots__ declaration takes a list of instance variables, and reserves +space for exactly these in the instance. When __slots__ is used, other +instance variables cannot be assigned to: + + >>> a = defaultdict2(default=0.0) + >>> a[1] + 0.0 + >>> a.default = -1 + >>> a[1] + -1 + >>> a.x1 = 1 + Traceback (most recent call last): + File "", line 1, in ? + AttributeError: 'defaultdict2' object has no attribute 'x1' + >>> + +""" + +test_3 = """ + +Introspecting instances of built-in types + +For instance of built-in types, x.__class__ is now the same as type(x): + + >>> type([]) + + >>> [].__class__ + + >>> list + + >>> isinstance([], list) + 1 + >>> isinstance([], dictionary) + 0 + >>> isinstance([], object) + 1 + >>> + +Under the new proposal, the __methods__ attribute no longer exists: + + >>> [].__methods__ + Traceback (most recent call last): + File "", line 1, in ? + AttributeError: 'list' object has no attribute '__methods__' + >>> + +Instead, you can get the same information from the list type: + + >>> pprint.pprint(dir(list)) # like list.__dict__.keys(), but sorted + ['__add__', + '__class__', + '__contains__', + '__delattr__', + '__delitem__', + '__eq__', + '__ge__', + '__getattr__', + '__getitem__', + '__getslice__', + '__gt__', + '__hash__', + '__iadd__', + '__imul__', + '__init__', + '__le__', + '__len__', + '__lt__', + '__mul__', + '__ne__', + '__new__', + '__repr__', + '__rmul__', + '__setattr__', + '__setitem__', + '__setslice__', + '__str__', + 'append', + 'count', + 'extend', + 'index', + 'insert', + 'pop', + 'remove', + 'reverse', + 'sort'] + +The new introspection API gives more information than the old one: in +addition to the regular methods, it also shows the methods that are +normally invoked through special notations, e.g. __iadd__ (+=), __len__ +(len), __ne__ (!=). You can invoke any method from this list directly: + + >>> a = ['tic', 'tac'] + >>> list.__len__(a) # same as len(a) + 2 + >>> a.__len__() # ditto + 2 + >>> list.append(a, 'toe') # same as a.append('toe') + >>> a + ['tic', 'tac', 'toe'] + >>> + +This is just like it is for user-defined classes. +""" + +test_4 = """ + +Static methods and class methods + +The new introspection API makes it possible to add static methods and class +methods. Static methods are easy to describe: they behave pretty much like +static methods in C++ or Java. Here's an example: + + >>> class C: + ... + ... def foo(x, y): + ... print "staticmethod", x, y + ... foo = staticmethod(foo) + + >>> C.foo(1, 2) + staticmethod 1 2 + >>> c = C() + >>> c.foo(1, 2) + staticmethod 1 2 + +Class methods use a similar pattern to declare methods that receive an +implicit first argument that is the *class* for which they are invoked. + + >>> class C: + ... def foo(cls, y): + ... print "classmethod", cls, y + ... foo = classmethod(foo) + + >>> C.foo(1) + classmethod test_descrtut.C 1 + >>> c = C() + >>> c.foo(1) + classmethod test_descrtut.C 1 + + >>> class D(C): + ... pass + + >>> D.foo(1) + classmethod test_descrtut.D 1 + >>> d = D() + >>> d.foo(1) + classmethod test_descrtut.D 1 + +This prints "classmethod __main__.D 1" both times; in other words, the +class passed as the first argument of foo() is the class involved in the +call, not the class involved in the definition of foo(). + +But notice this: + + >>> class E(C): + ... def foo(cls, y): # override C.foo + ... print "E.foo() called" + ... C.foo(y) + ... foo = classmethod(foo) + + >>> E.foo(1) + E.foo() called + classmethod test_descrtut.C 1 + >>> e = E() + >>> e.foo(1) + E.foo() called + classmethod test_descrtut.C 1 + +In this example, the call to C.foo() from E.foo() will see class C as its +first argument, not class E. This is to be expected, since the call +specifies the class C. But it stresses the difference between these class +methods and methods defined in metaclasses (where an upcall to a metamethod +would pass the target class as an explicit first argument). +""" + +test_5 = """ + +Attributes defined by get/set methods + + + >>> class getset(object): + ... + ... def __init__(self, get, set=None): + ... self.__get = get + ... self.__set = set + ... + ... def __get__(self, inst, type=None): + ... return self.__get(inst) + ... + ... def __set__(self, inst, value): + ... if self.__set is None: + ... raise AttributeError, "this attribute is read-only" + ... return self.__set(inst, value) + +Now let's define a class with an attribute x defined by a pair of methods, +getx() and and setx(): + + >>> class C(object): + ... + ... def __init__(self): + ... self.__x = 0 + ... + ... def getx(self): + ... return self.__x + ... + ... def setx(self, x): + ... if x < 0: x = 0 + ... self.__x = x + ... + ... x = getset(getx, setx) + +Here's a small demonstration: + + >>> a = C() + >>> a.x = 10 + >>> print a.x + 10 + >>> a.x = -10 + >>> print a.x + 0 + >>> + +Hmm -- getset is builtin now, so let's try it that way too. + + >>> del getset # unmask the builtin + >>> getset + + + >>> class C(object): + ... def __init__(self): + ... self.__x = 0 + ... def getx(self): + ... return self.__x + ... def setx(self, x): + ... if x < 0: x = 0 + ... self.__x = x + ... x = getset(getx, setx) + + + >>> a = C() + >>> a.x = 10 + >>> print a.x + 10 + >>> a.x = -10 + >>> print a.x + 0 + >>> +""" + +test_6 = """ + +Method resolution order + +This example is implicit in the writeup. + +>>> class A: # classic class +... def save(self): +... print "called A.save()" +>>> class B(A): +... pass +>>> class C(A): +... def save(self): +... print "called C.save()" +>>> class D(B, C): +... pass + +>>> D().save() +called A.save() + +>>> class A(object): # new class +... def save(self): +... print "called A.save()" +>>> class B(A): +... pass +>>> class C(A): +... def save(self): +... print "called C.save()" +>>> class D(B, C): +... pass + +>>> D().save() +called C.save() +""" + +class A(object): + def m(self): + return "A" + +class B(A): + def m(self): + return "B" + super(B, self).m() + +class C(A): + def m(self): + return "C" + super(C, self).m() + +class D(C, B): + def m(self): + return "D" + super(D, self).m() + + +test_7 = """ + +Cooperative methods and "super" + +>>> print D().m() # "DCBA" +DCBA +""" + +test_8 = """ + +Backwards incompatibilities + +>>> class A: +... def foo(self): +... print "called A.foo()" + +>>> class B(A): +... pass + +>>> class C(A): +... def foo(self): +... B.foo(self) + +>>> C().foo() +Traceback (most recent call last): + ... +TypeError: unbound method foo() must be called with B instance as first argument (got C instance instead) + +>>> class C(A): +... def foo(self): +... A.foo(self) +>>> C().foo() +called A.foo() +""" + +__test__ = {"tut1": test_1, + "tut2": test_2, + "tut3": test_3, + "tut4": test_4, + "tut5": test_5, + "tut6": test_6, + "tut7": test_7, + "tut8": test_8} + +# Magic test name that regrtest.py invokes *after* importing this module. +# This worms around a bootstrap problem. +# Note that doctest and regrtest both look in sys.argv for a "-v" argument, +# so this works as expected in both ways of running regrtest. +def test_main(): + import doctest, test_descrtut + if 0: # change to 1 to run forever (to check for leaks) + while 1: + doctest.master = None + doctest.testmod(test_descrtut) + print ".", + else: + doctest.testmod(test_descrtut) + +# This part isn't needed for regrtest, but for running the test directly. +if __name__ == "__main__": + test_main() -- cgit v0.12