mirror of
				https://github.com/python/cpython.git
				synced 2025-11-03 19:34:08 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			482 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			482 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
# This contains most of the executable examples from Guido's descr
 | 
						|
# tutorial, once at
 | 
						|
#
 | 
						|
#     https://www.python.org/download/releases/2.2.3/descrintro/
 | 
						|
#
 | 
						|
# 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 doctest
 | 
						|
import unittest
 | 
						|
 | 
						|
 | 
						|
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
 | 
						|
    <class '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():
 | 
						|
 | 
						|
    >>> print(sorted(a.keys()))
 | 
						|
    [1, 2]
 | 
						|
    >>> a['print'] = print              # need the print function here
 | 
						|
    >>> exec("x = 3; print(x)", a)
 | 
						|
    3
 | 
						|
    >>> print(sorted(a.keys(), key=lambda x: (str(type(x)), x)))
 | 
						|
    [1, 2, '__builtins__', 'print', '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([])
 | 
						|
    <class 'list'>
 | 
						|
    >>> [].__class__
 | 
						|
    <class 'list'>
 | 
						|
    >>> list
 | 
						|
    <class 'list'>
 | 
						|
    >>> isinstance([], list)
 | 
						|
    True
 | 
						|
    >>> isinstance([], dict)
 | 
						|
    False
 | 
						|
    >>> isinstance([], object)
 | 
						|
    True
 | 
						|
    >>>
 | 
						|
 | 
						|
You can get the information from the list type:
 | 
						|
 | 
						|
    >>> import pprint
 | 
						|
    >>> pprint.pprint(dir(list))    # like list.__dict__.keys(), but sorted
 | 
						|
    ['__add__',
 | 
						|
     '__class__',
 | 
						|
     '__class_getitem__',
 | 
						|
     '__contains__',
 | 
						|
     '__delattr__',
 | 
						|
     '__delitem__',
 | 
						|
     '__dir__',
 | 
						|
     '__doc__',
 | 
						|
     '__eq__',
 | 
						|
     '__format__',
 | 
						|
     '__ge__',
 | 
						|
     '__getattribute__',
 | 
						|
     '__getitem__',
 | 
						|
     '__getstate__',
 | 
						|
     '__gt__',
 | 
						|
     '__hash__',
 | 
						|
     '__iadd__',
 | 
						|
     '__imul__',
 | 
						|
     '__init__',
 | 
						|
     '__init_subclass__',
 | 
						|
     '__iter__',
 | 
						|
     '__le__',
 | 
						|
     '__len__',
 | 
						|
     '__lt__',
 | 
						|
     '__mul__',
 | 
						|
     '__ne__',
 | 
						|
     '__new__',
 | 
						|
     '__reduce__',
 | 
						|
     '__reduce_ex__',
 | 
						|
     '__repr__',
 | 
						|
     '__reversed__',
 | 
						|
     '__rmul__',
 | 
						|
     '__setattr__',
 | 
						|
     '__setitem__',
 | 
						|
     '__sizeof__',
 | 
						|
     '__str__',
 | 
						|
     '__subclasshook__',
 | 
						|
     'append',
 | 
						|
     'clear',
 | 
						|
     'copy',
 | 
						|
     '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 <class 'test.test_descrtut.C'> 1
 | 
						|
    >>> c = C()
 | 
						|
    >>> c.foo(1)
 | 
						|
    classmethod <class 'test.test_descrtut.C'> 1
 | 
						|
 | 
						|
    >>> class D(C):
 | 
						|
    ...     pass
 | 
						|
 | 
						|
    >>> D.foo(1)
 | 
						|
    classmethod <class 'test.test_descrtut.D'> 1
 | 
						|
    >>> d = D()
 | 
						|
    >>> d.foo(1)
 | 
						|
    classmethod <class '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 <class 'test.test_descrtut.C'> 1
 | 
						|
    >>> e = E()
 | 
						|
    >>> e.foo(1)
 | 
						|
    E.foo() called
 | 
						|
    classmethod <class '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 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
 | 
						|
    <class '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:    # implicit new-style 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):  # explicit new-style 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()
 | 
						|
called A.foo()
 | 
						|
 | 
						|
>>> 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}
 | 
						|
 | 
						|
def load_tests(loader, tests, pattern):
 | 
						|
    tests.addTest(doctest.DocTestSuite())
 | 
						|
    return tests
 | 
						|
 | 
						|
 | 
						|
if __name__ == "__main__":
 | 
						|
    unittest.main()
 |