| # 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.test_support import sortdict | |
| import pprint | |
| class defaultdict(dict): | |
| def __init__(self, default=None): | |
| dict.__init__(self) | |
| self.default = default | |
| def __getitem__(self, key): | |
| try: | |
| return dict.__getitem__(self, key) | |
| except KeyError: | |
| return self.default | |
| def get(self, key, *args): | |
| if not args: | |
| args = (self.default,) | |
| return dict.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 | |
| <class 'test.test_descrtut.defaultdict'> | |
| >>> print type(defaultdict) # its metatype | |
| <type 'type'> | |
| >>> a = defaultdict(default=0.0) # create an instance | |
| >>> print a # show the instance | |
| {} | |
| >>> print type(a) # show its type | |
| <class 'test.test_descrtut.defaultdict'> | |
| >>> print a.__class__ # show its class | |
| <class 'test.test_descrtut.defaultdict'> | |
| >>> print type(a) is a.__class__ # its type is its class | |
| True | |
| >>> 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-existent item | |
| 0.0 | |
| >>> a.merge({1:100, 2:200}) # use a dict 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(key=str) | |
| ... 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 | |
| >>> | |
| 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 | |
| >>> 'default' in dir(a) | |
| True | |
| >>> a.x1 = 100 | |
| >>> a.x2 = 200 | |
| >>> print a.x1 | |
| 100 | |
| >>> d = dir(a) | |
| >>> 'default' in d and 'x1' in d and 'x2' in d | |
| True | |
| >>> print sortdict(a.__dict__) | |
| {'default': -1000, 'x1': 100, 'x2': 200} | |
| >>> | |
| """ | |
| class defaultdict2(dict): | |
| __slots__ = ['default'] | |
| def __init__(self, default=None): | |
| dict.__init__(self) | |
| self.default = default | |
| def __getitem__(self, key): | |
| try: | |
| return dict.__getitem__(self, key) | |
| except KeyError: | |
| return self.default | |
| def get(self, key, *args): | |
| if not args: | |
| args = (self.default,) | |
| return dict.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 "<stdin>", 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([]) | |
| <type 'list'> | |
| >>> [].__class__ | |
| <type 'list'> | |
| >>> list | |
| <type 'list'> | |
| >>> isinstance([], list) | |
| True | |
| >>> isinstance([], dict) | |
| False | |
| >>> isinstance([], object) | |
| True | |
| >>> | |
| Under the new proposal, the __methods__ attribute no longer exists: | |
| >>> [].__methods__ | |
| Traceback (most recent call last): | |
| File "<stdin>", 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__', | |
| '__delslice__', | |
| '__doc__', | |
| '__eq__', | |
| '__format__', | |
| '__ge__', | |
| '__getattribute__', | |
| '__getitem__', | |
| '__getslice__', | |
| '__gt__', | |
| '__hash__', | |
| '__iadd__', | |
| '__imul__', | |
| '__init__', | |
| '__iter__', | |
| '__le__', | |
| '__len__', | |
| '__lt__', | |
| '__mul__', | |
| '__ne__', | |
| '__new__', | |
| '__reduce__', | |
| '__reduce_ex__', | |
| '__repr__', | |
| '__reversed__', | |
| '__rmul__', | |
| '__setattr__', | |
| '__setitem__', | |
| '__setslice__', | |
| '__sizeof__', | |
| '__str__', | |
| '__subclasshook__', | |
| '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: | |
| ... | |
| ... @staticmethod | |
| ... def foo(x, y): | |
| ... print "staticmethod", x, y | |
| >>> 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: | |
| ... @classmethod | |
| ... def foo(cls, y): | |
| ... print "classmethod", cls, y | |
| >>> C.foo(1) | |
| classmethod test.test_descrtut.C 1 | |
| >>> c = C() | |
| >>> c.foo(1) | |
| classmethod test.test_descrtut.C 1 | |
| >>> class D(C): | |
| ... pass | |
| >>> D.foo(1) | |
| classmethod test.test_descrtut.D 1 | |
| >>> d = D() | |
| >>> d.foo(1) | |
| classmethod test.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): | |
| ... @classmethod | |
| ... def foo(cls, y): # override C.foo | |
| ... print "E.foo() called" | |
| ... C.foo(y) | |
| >>> E.foo(1) | |
| E.foo() called | |
| classmethod test.test_descrtut.C 1 | |
| >>> e = E() | |
| >>> e.foo(1) | |
| E.foo() called | |
| classmethod test.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 property(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 = property(getx, setx) | |
| Here's a small demonstration: | |
| >>> a = C() | |
| >>> a.x = 10 | |
| >>> print a.x | |
| 10 | |
| >>> a.x = -10 | |
| >>> print a.x | |
| 0 | |
| >>> | |
| Hmm -- property is builtin now, so let's try it that way too. | |
| >>> del property # unmask the builtin | |
| >>> property | |
| <type 'property'> | |
| >>> 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 = property(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(verbose=None): | |
| # Obscure: import this module as test.test_descrtut instead of as | |
| # plain test_descrtut because the name of this module works its way | |
| # into the doctest examples, and unless the full test.test_descrtut | |
| # business is used the name can change depending on how the test is | |
| # invoked. | |
| from test import test_support, test_descrtut | |
| test_support.run_doctest(test_descrtut, verbose) | |
| # This part isn't needed for regrtest, but for running the test directly. | |
| if __name__ == "__main__": | |
| test_main(1) |