zoukankan      html  css  js  c++  java
  • Python Revisited Day 06 (面向对象程序设计)

    《Python 3 程序开发指南》 学习笔记

    6.1 面向对象方法

    duck typing

    “如果走起来像只鸭子,叫起来也像只鸭子,那它就是一只鸭子。”

    访问限制 __

    class Circle:
        def __init__(self, x=0, y=0, radius=0):
            self.x = x
            self.y = y
            self.radius = radius
            self.__PI = 3.1415926  #私有属性 __
        
        def get_PI(self):
            
            return self.__PI  #我们可以通过方法来获得此属性,当然相应的改变也可以
            
        def get_area(self):
            
            return self.PI * self.radius ** 2
    
    c = Circle(2, 2, 2)
    c.x, c.y,c.radius  #(2, 2, 2)
    c.__PI   # AttributeError
    c.get_PI()   #3.1415926
    

    6.2 自定义类

    class className:
    	suite
    class className(base_classes):
    	suite
    

    6.2.1 属性与方法

    
    class Circle:
        def __init__(self, x=0, y=0, radius=0):
            self.x = x
            self.y = y
            self.radius = radius
            self.__PI = 3.1415926
    
        def get_PI(self):
            return self.__PI
    
        def get_area(self):
            return self.PI * self.radius ** 2
    
        def __eq__(self, other):
            return (self.x, self.y, self.radius) == (other.x, other.y, other.radius)
        
        def __repr__(self):
            return "Circle({0.x!r}, {0.y!r}, {0.radius!r})".format(self)  # !r  强制使用表现形式
        
        def __str__(self):
            return "({0.x!r}, {0.y!r}, {0.radius!r})".format(self)
        
    c = Circle(2, 2, 2)
    repr(c) # 'Point(2, 2, 2)'   == c.__repr__()
    str(c) # '(2, 2, 2)'   == c.__str__()
    c2 = Circle(2, 2, 2)
    c == c2 # True   == c.__eq__(c2)
    c != c2  # False
    

    预定义的特殊方法 _…_

    一般的方法名起始和结尾不应该使用俩个下划线,除非是预定义的特殊方法(大概就是操作符所对应的方法,还有一些固定的方法?)。

    比较的特殊方法

    特殊方法 使用 描述
    __it__(self, other) x < y 如果x比y小,则返回True
    __le__(self, other) x <= y
    __eq__(self, other) x == y
    __ne__(self, other) x != y
    __ge__(self, other) x >= y
    __gt__(self, other) x > y

    默认情况下,自定义类的实例都是可哈希运算的。如果重新实现了__eq__(),实例便不可哈希运算。

    class Circle:
        def __init__(self, x=0, y=0, radius=0):
            self.x = x
            self.y = y
            self.radius = radius
            self.__PI = 3.1415926
        
    c = Circle(2, 2, 2)
    c2 = Circle(2, 2, 2)
    c == c2 # False
    
        def __eq__(self, other):
            if not isinstance(other, Circle):  #assert isinstance(other, Circle)
                raise TypeError() # NotImplem-entled
            return (self.x, self.y, self.radius) == (other.x, other.y, other.radius)
    

    上述对__eq__()的改写,可以避免类似 “c == 1”。

    c = Circle(2, 2, 2) 
    c2 = eval(repr(c)) #如果Circle是引用来的,要加入模块 c.__module__+'.'+repr(c)
    c == c2
    

    6.2.2 继承与多态

    import math
    
    
    class Point:
    
        def __init__(self, x=0, y=0):
            self.x = x
            self.y = y
    
        def distance_from_origin(self):
            return math.hypot(self.x, self.y)
    
        def __eq__(self, other):
    
            if not isinstance(other, Point):
                raise TypeError()
            return (self.x ,self.y) == (other.x, other.y)
    
        def __repr__(self):
            return "Point({0.x!r}, {0.y!r})".format(self)
    
        def __str__(self):
            return "({0.x!r}, {0.y!r})".format(self)
    
    
    
    
    
    class Circle(Point):
        def __init__(self, x=0, y=0, radius=0):
            super().__init__(x, y)
            self.radius = radius
            self.__PI = 3.1415926
    
        def get_PI(self):
            return self.__PI
    
        def edge_distance_from_origin(self):
            return abs(self.distance_from_origin() - self.radius)
    
        def area(self):
            return self.PI * self.radius ** 2
    
        def circumference(self):
            return 2 * self.__PI * self.radius
    
        def __eq__(self, other):
            if not isinstance(other, Circle):  #assert isinstance(other, Circle)
                raise TypeError() # NotImplem-entled
            return self.radius == other.radius and super().__eq__(other) #!!!
    
        def __repr__(self):
            return "Circle({0.x!r}, {0.y!r}, {0.radius!r})".format(self)
    
        def __str__(self):
            return "({0.x!r}, {0.y!r}, {0.radius!r})".format(self)
    

    如果__eq_() 里用Circle.__eq_(self, other)

        def __eq__(self, other):
            if not isinstance(other, Circle):  #assert isinstance(other, Circle)
                raise TypeError() # NotImplem-entled
            return self.radius == other.radius and Circle.__eq__(self, other) #!!!
    c = Circle(2, 2, 2)
    c2 = Circle(2, 2, 2)
    c == c2  #会进入无限迭代,因为实际上调用的Circle类里的__eq__而不是Point类里的
    #另外,使用 super().__eq__(other), python 会自动传入self
    

    6.2.3 使用特性进行属性存取控制 @property

    
    class Circle(Point):
        def __init__(self, radius, x=0, y=0):  #注意radius的位置 且无默认值
            super().__init__(x, y)
            self.radius = radius #!!!!!!!!!!!!!!!!!!
            self.__PI = 3.1415926
    
        def get_PI(self):
            return self.__PI
    
        @property
        def radius(self):
            """The Circle's radius
    
            >>> circle = Circle(-2)
            Traceback (most recent call last):
            ...
            AssertionError: radius must be nonzero and non-negative
            >>> circle = Circle(4)
            >>> circle.radius = -1
            Traceback (most recent call last):
            ...
            AssertionError: radius must be nonzero and non-negative
            >>> circle.radius = 6
    
            """
            return self.__radius
    
        @radius.setter
        def radius(self, radius):
            assert radius > 0, "radius must be nonzero and non-negative"
            self.__radius = radius
    
    
        @property
        def edge_distance_from_origin(self):
            return abs(self.distance_from_origin() - self.radius)
    
        @property
        def area(self):
            return self.PI * self.radius ** 2
    
        def circumference(self):
            return 2 * self.__PI * self.radius
    
        def __eq__(self, other):
            if not isinstance(other, Circle):  #assert isinstance(other, Circle)
                raise TypeError() # NotImplem-entled
            return self.radius == other.radius and super().__eq__(other)
    
        def __repr__(self):
            return "Circle({0.x!r}, {0.y!r}, {0.radius!r})".format(self)
    
        def __str__(self):
            return "({0.x!r}, {0.y!r}, {0.radius!r})".format(self)
    
    
    

    如果

        def __init__(self, radius, x=0, y=0):  #注意radius的位置 且无默认值
            super().__init__(x, y)
            self.radius = radius #!!!!!!!!!!!!!!!!!!
            self.__PI = 3.1415926
    

    改为

        def __init__(self, radius, x=0, y=0):  #注意radius的位置 且无默认值
            super().__init__(x, y)
            self.__radius = radius #!!!!!!!!!!!!!!!!!!
            self.__PI = 3.1415926
    

    那么

    c = Circle(-1) 
    

    不会报错。

    每个创建的特性(即用@property)修饰之后,都包含getter,setter,deleter等属性。

    6.2.4 创建完全整合的数据类型

    基本的特殊方法

    特殊方法 使用 描述
    __bool__(self) bool(x) 如果提供,就返回x的真值,对 if x:… 是有用的
    __format__(self, format_spec) “{0}”.format(x) 为自定义类提供str.format()支持
    __hash__(self) hash(x) 如果提供,那么x可用作字典的键或存放在集合中
    __init__(self, args) x = X(args) 对象初始化调用
    __new__(cls, args) x = X(args) 创建对象时调用
    __repr__(self) repr(x) 返回x的字符串表示,在可能的地方eval(repr(x)) == x
    __repr_(self) ascii(x) 仅使用ASCII返回x的字符串表示
    str(self) str(x) 返回x的适合阅读的字符串表示

    数值型与位逻辑运算的特殊方法

    特殊方法 使用 特殊方法 使用
    __abs__(self) abs(x) __complex__(self) complex(x)
    __float__(self) float(x) __init__(self) int(x)
    __index__(self) bin(x) oct(x) hex(x) __round__(self, digits) round(x, digits)
    __pos__(self) +x __neg__(self) -x
    __add__(self, other) x + y __sub__(self, other) x - y
    __iadd__(self, other) x += y __isub__(self, other) x -= y
    __radd__(self, other) y + x __rsub__(self, other) y - x
    __mul__(self, other) x * y __mod__(self, other) x % y
    __imul__(self, other) x *= y __imod__(self, other) x %= y
    __rmul__(self, other) y * x __rmod__( self, other) y % x
    __floordiv__(self, other) x // y __truediv__(self, other) x / y
    __ifloordiv__(self, other) x //= y __itruediv__(self, other) x /= y
    __rfloordiv__(self, other) y // x __rtruediv__(self,other) y / x
    __divmod__(self, other) divmod(x, y) __rdivmod__(self, other) divmod(y, x)
    __pow__(self, other) x ** y __and__(self, other) x & y
    __ipow__(self, other) x **= y __iand__(self, other) x &= y
    __rpow__(self, other) y ** x __rand__(self, other) y & x
    __xor__(self, other) x ^ y __or__(self, other) x | y
    __ixor__(self, other) x ^= y __ior__(self, other) x |= y
    __rxor__(self, other) y ^ x __ror__(self, other) y | x
    __lshift__(self, other) x << y __rshift__(self, other) x >> y
    __ilshift__(self, other) x <<= y __irshift__(self, other) x >>= y
    __rlshift__(self, other) y << x __rrshift__(self, other) y >> x
    __invert__(self) ~x

    6.2.4.1 从头开始创建数据类型

    class FuzzyBool:
    
        """从头开始创建数据类型FuzzyBool:模糊型布尔值
        FuzzyBool扩展了二元值true 和 false. 1.0 表示 true,
        0.0 表示false, 0.5 表示 50% true.
    
    
        >>> a = FuzzyBool(0.875)
        >>> b = FuzzyBool(0.25)
    
        实例提供比较"> >= < <= = !="
        >>> a >= b
        True
        >>> a == 1
        Traceback (most recent call last):
        ...
        TypeError
    
        实例支持bool()操作
        >>> bool(a), bool(b)
        (True, False)
    
        实例支持位操作符
        >>> ~a
        FuzzyBool(0.125)
        >>> a & b
        FuzzyBool(0.25)
        >>> b |= FuzzyBool(.5)
    
        支持format
        >>> "a={0:.1%} b={1:.0%}".format(a, b)
        'a=87.5% b=50%'
    
        """
        def __init__(self, value=0.0):
            """初始化函数 value 默认值为0.0
            且传入的值要求在0,1之间,否则取0.0
            """
            self.__value = value if 0.0 <= value <= 1.0 else 0.0
    
        def __invert__(self):
            """
            倒置操作符 ~
            :return: FuzzyBool(1.0 - self.__value)
            """
            return FuzzyBool(1.0 - self.__value)
    
        def __and__(self, other):
            """
            & 的特殊方法
            :param other: 相同的鸭子。。。
            :return: self.__value 与 other.__value的小的
            """
            if not isinstance(other, FuzzyBool):
                raise  TypeError()
            return FuzzyBool(min(self.__value, other.__value))
    
        def __iand__(self, other):
            """
            &= 的特殊方法
            :param other: 相同的鸭子。。。
            :return: self.__value更新为self.__value和other.__value中较小的那个, 返回self.
            """
            if not isinstance(other, FuzzyBool):
                raise TypeError()
            self.__value = min(self.__value, other.__value)
            return self
    
        def __or__(self, other):
            """
            |
            :param other:
            :return:
            """
            if not isinstance(other, FuzzyBool):
                raise TypeError()
            return FuzzyBool(max(self.__value, other.__value))
    
        def __ior__(self, other):
            """
            |=
            :param other:
            :return:
            """
            if not isinstance(other, FuzzyBool):
                raise TypeError()
            return FuzzyBool(max(self.__value, other.__value))
    
        def __repr__(self):
            """
            表象形式
            :return:
            """
            return "{0}({1})".format(self.__class__.__name__,
                                     self.__value)
    
        def __str__(self):
            """
            字符串形式
            :return:
            """
            return str(self.__value)
    
        def __bool__(self):
            """
            if self.__value > 0.5
            :return: True
            """
            return self.__value > 0.5
    
        def __int__(self):
            """
            整数形式
            :return:
            """
            return round(self.__value)
    
        def __float__(self):
            """
            浮点数形式
            :return:
            """
            return self.__value
    
        # 要想完整的比较操作符集< > <= >= == != 只需要提供其中3个即可(< <= ==)
        # 余下的Python自己会推导出来
        def __lt__(self, other):
            """
            <
            :param other:
            :return:
            """
            if not isinstance(other, FuzzyBool):
                raise TypeError()
            return self.__value < other.__value
    
    
        def __eq__(self, other):
            """
            ==
            :param other:
            :return:
            """
            if not isinstance(other, FuzzyBool):
                raise  TypeError()
            return self.__value == other.__value
    
        def __le__(self, other):
            """
            <=
            :param other:
            :return:
            """
            if not isinstance(other, FuzzyBool):
                raise  TypeError()
            return self.__value <= other.__value
    
        def __hash__(self):
            """
            因为重写了__eq__(),所以需要提供__hash__()来使其可哈希
            !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
            不能把self.__value作为哈希值,因为它是可变的
            !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
            :return:
            """
            return hash(id(self))
    
        def  __format__(self, format_spec):
            return format(self.__value, format_spec)
    
        @staticmethod
        def conjunction(*fuzzies):
            """
            结链处理
            :param fuzzies:
            :return: fuzzies[0] & fuzzies[1] ... & fuzzies[n]
            """
            return FuzzyBool(min(float(x) for x in fuzzies))
    
    if __name__ == "__main__":
    
        import doctest
        doctest.testmod()
    
    

    6.2.4.2 从其他数据类型创建数据类型

    
    
    class FuzzyBool(float):
    
        """从头开始创建数据类型FuzzyBool:模糊型布尔值
        FuzzyBool扩展了二元值true 和 false. 1.0 表示 true,
        0.0 表示false, 0.5 表示 50% true.
    
    
        >>> a = FuzzyBool(0.875)
        >>> b = FuzzyBool(0.25)
    
        实例提供比较"> >= < <= = !="
        >>> a >= b
        True
    
        实例支持bool()操作
        >>> bool(a), bool(b)
        (True, False)
    
        实例支持位操作符
        >>> ~a
        FuzzyBool(0.125)
        >>> a & b
        FuzzyBool(0.25)
        >>> b |= FuzzyBool(.5)
    
        支持format
        >>> "a={0:.1%} b={1:.0%}".format(a, b)
        'a=87.5% b=50%'
    
        不支持+,-,*,/等运算符
        >>> -a
        Traceback (most recent call last):
        ...
        NotImplementedError
        >>> a + b
        Traceback (most recent call last):
        ...
        TypeError: unsupported operand type(s) for '+':'FuzzyBool' and 'FuzzyBool'
    
    
    
        """
        def __new__(cls, value=0.0):
            """
            创建一个新类时,通常是可变的。对于固定类,
            我们需要在一个步骤中同时完成创建和初始化,
            因为对于固定对象而言,一旦创建,就不能更改。
            :param value:
            :return:
            """
            return super().__new__(cls,
                                   value if 0.0 <= value <= 1.0 else 0)
    
        def __invert__(self):
            return FuzzyBool(1.0 - float(self)) #注意是self!!!
    
        def __and__(self, other):
            return FuzzyBool(min(self, other))
    
        def __iand__(self, other):
            """
            因为是固定类型,所以,实际上依旧是创建了一个新实例
            :param other:
            :return:
            """
            return FuzzyBool(min(self, other))
    
        def __or__(self, other):
            return FuzzyBool(max(self, other))
    
        def __ior__(self, other):
            return FuzzyBool(max(self, other))
    
        def __repr__(self):
            return "{0}({1})".format(self.__class__.__name__,
                                     super().__repr__())
    
        def __bool__(self):
            return self > 0.5
    
        def __int__(self):
            return round(self)
    
        def __add__(self, other):
            """
            FuzzyBool类型加法是没有意义的
            :param other:
            :return:
            """
            raise  TypeError("unsupported operand type(s) for '+':"
                             "'{0}' and '{1}'".format(
                self.__class__.__name__, other.__class__.__name__
            ))
    
        def __iadd__(self, other):
            raise NotImplementedError()
    
        def __radd__(self, other):
            """
            通过TypeError异常
            :param other:
            :return:
            """
    
            raise  TypeError("unsupported operand type(s) for '+':"
                             "'{0}' and '{1}'".format(
                self.__class__.__name__, other.__class__.__name__
            ))
    
        def __neg__(self):
            raise NotImplementedError()
    
        def __eq__(self, other):
            raise NotImplemented
    
    
    
    if __name__ == "__main__":
    
        import doctest
        doctest.testmod()
    
    Tips 如何快速无效化不要的方法 exec()

    将下段代码放在FuzzyBool控制范围内即可无效化"-“和"index()”。
    该代码主要是用到了exec()函数。
    当然,下面无效化的是单值操作,二元操作符等要更加复杂。

    for name, operator in (("__neg__", "-"),
                           ("__index__", "index()")):
        message = "bad operand type for unary {0}: '{{self}}'".format(
            operator
        )
        exec("def {0}(self): raise TypeError("{1}".format("
             "self=self.__class__.__name__))".format(name, message))
    

    6.3 自定义组合类

    本节将展式3种自定义类:

    • Image 用于存放图像数据
    • SortedList
    • SortedDict

    6.3.1 创建聚集组合数据的类

    用于表示2D颜色图像的一个简单方法是使用一个2维数组存储,每个数组元素代表一种颜色。
    Image将采用一种更加高效的做法:记入一种单一的背景色,以及图像种不同于背景色的颜色。

    
    
    
    """
    This module provides the Image class which holds (x, y, color) triples
    and a background color to provide a kind of sparse-array representation of
    an image. A method to export the image in XPM format is also provided.
    
    >>> import os
    >>> import tempfile
    >>> red = "#FF0000"
    >>> blue = "#0000FF"
    >>> img = os.path.join(tempfile.gettempdir(), "test.img")
    >>> xpm = os.path.join(tempfile.gettempdir(), "test.xpm")
    >>> image = Image(10, 8, img)
    >>> for x, y in ((0, 0), (0, 7), (1, 0), (1, 1), (1, 6), (1, 7), (2, 1),
    ...             (2, 2), (2, 5), (2, 6), (2, 7), (3, 2), (3, 3), (3, 4),
    ...             (3, 5), (3, 6), (4, 3), (4, 4), (4, 5), (5, 3), (5, 4),
    ...             (5, 5), (6, 2), (6, 3), (6, 4), (6, 5), (6, 6), (7, 1),
    ...             (7, 2), (7, 5), (7, 6), (7, 7), (8, 0), (8, 1), (8, 6),
    ...             (8, 7), (9, 0), (9, 7)):
    ...    image[x, y] = blue
    >>> for x, y in ((3, 1), (4, 0), (4, 1), (4, 2), (5, 0), (5, 1), (5, 2),
    ...             (6, 1)):
    ...    image[(x, y)] = red
    >>> print(image.width, image.height, len(image.colors), image.background)
    10 8 3 #FFFFFF
    >>> border_color = "#FF0000" # red
    >>> square_color = "#0000FF" # blue
    >>> width, height = 240, 60
    >>> midx, midy = width // 2, height // 2
    >>> image = Image(width, height, img, "#F0F0F0")
    >>> for x in range(width):
    ...     for y in range(height):
    ...         if x < 5 or x >= width - 5 or y < 5 or y >= height -5:
    ...             image[x, y] = border_color
    ...         elif midx - 20 < x < midx + 20 and midy - 20 < y < midy + 20:
    ...             image[x, y] = square_color
    >>> print(image.width, image.height, len(image.colors), image.background)
    240 60 3 #F0F0F0
    >>> image.save()
    >>> newimage = Image(1, 1, img)
    >>> newimage.load()
    >>> print(newimage.width, newimage.height, len(newimage.colors), newimage.background)
    240 60 3 #F0F0F0
    >>> image.export(xpm)
    >>> image.thing
    Traceback (most recent call last):
    ...
    AttributeError: 'Image' object has no attribute 'thing'
    >>> for name in (img, xpm):
    ...     try:
    ...         os.remove(name)
    ...     except EnvironmentError:
    ...         pass
    """
    
    import os, pickle
    """
    在Python中,pickling是将Python对象进行序列化的一种方法。Pickling之所以
    强大,是因为进行pickling处理的对象可以是组合数据类型。并且,即便要进行
    pickling处理的对象内部包含其他对象,仍然可以统一进行pickling处理————并且
    不会使得对象重复出现。
    说实话,并没有很深的理解,有空找官方文档看看吧。
    """
    
    """
    定义异常
    """
    class ImageError(Exception): pass
    class CoordinateError(ImageError): pass
    class LoadError(ImageError): pass
    class SaveError(ImageError): pass
    class ExportError(ImageError): pass
    class NoFilenameError(ImageError): pass
    
    """
    Image 类
    """
    
    class Image:
        """Class Image provides some methods about image, such as building and saving.
    
    
        """
        def __init__(self, width, height, filename="",
                     background="#FFFFFF"):
            """
            the keys of self.__data are (x, y)
            :param 
            :param height:
            :param filename: default: ""
            :param background:  default: "#FFFFFF"  white
            """
            self.filename = filename
            self.__background = background
            self.__data = {}
            self.__width = width
            self.__height = height
            self.__colors = {self.__background}
    
    
        @property
        def background(self):
            return self.__background
    
        @property
        def width(self):
            return self.__width
    
        @property
        def height(self):
            return self.__height
    
        @property
        def colors(self):
            """
            why we set() the set. In, fact, we return
            a copy of self.__colors to avoid the changing in accident.
            :return:
            """
            return set(self.__colors) #返回一个复制而避免外界不小心的改变 | {self.__background}
    
        def __getitem__(self, coordinate):
            """
            y[k] 方法的实现 同时要求输入的2元组
            :param coordinate:
            :return:
            """
            assert len(coordinate) == 2, "coordinate should be a 2-tuple"
            if (not (0 <= coordinate < self.width) or
                not (0 <= coordinate[1] < self.height)):
                raise  CoordinateError(str(coordinate))
            return self.__data.get(tuple(coordinate), self.__background)
    
        def __setitem__(self, coordinate, color):
            """
            y[k] = v 方法的实现 同时要求输入2元组
            :param coordinate: 坐标
            :param color: 该坐标上的颜色
            :return: None
            """
            assert len(coordinate) == 2, "coordinate should be a 2-tuple"
            if (not (0 <= coordinate[0] < self.width) or
                not (0 <= coordinate[1] < self.height)):
                raise CoordinateError(str(coordinate))
            if color == self.__background:
                self.__data.pop(tuple(coordinate), None) # 不用del的原因是 避免产生异常
            else:
                self.__data[tuple(coordinate)] = color
                self.__colors.add(color)
    
    
        def __delitem__(self, coordinate):
            assert len(coordinate) == 2, "coordinate should be a 2-tuple"
            if (not (0 <= coordinate[0] < self.width) or
                    not (0 <= coordinate[1] < self.height)):
                raise CoordinateError(str(coordinate))
            self.__data.pop(tuple(coordinate), None)
    
        def save(self, filename=None):
            """
            save the image...
    
            first block addresses the filename, if no filename is provided,
            raise Error.
    
            second block is the process to save
    
            :param filename:
            :return:
            """
            if filename is not None:
                self.filename = filename
            elif not self.filename:
                raise NoFilenameError()
    
    
    
            fh = None
            try:
                data = [self.width, self.height, self.__background,
                        self.__data]
                fh = open(self.filename, "wb") #二进制打开文件
                pickle.dump(data, fh, pickle.HIGHEST_PROTOCOL) #pickle.HIGHEST_PROTOCOL 是一种紧凑的二进制格式
            except (EnvironmentError, pickle.PicklingError) as err:
                raise SaveError(str(err))
            finally:
                if fh is not None:
                    fh.close()
    
        def load(self, filename=None):
            """
            for load image...
            the first block is the same as save...
            the second block is the process to loading...
            :param filename:
            :return: None
            """
            if filename is not None:
                self.filename = filename
            elif not self.filename:
                raise NoFilenameError()
    
    
            fh = None
            try:
                fh = open(self.filename, "rb")
                data = pickle.load(fh)
                (self.__width, self.__height,
                 self.__background, self.__data) = data
                self.__colors = (set(self.__data.values()) |
                                 {self.__background}) # s.union(t) == s|t
            except (EnvironmentError, pickle.UnpicklingError) as err:
                raise LoadError(str(err))
            finally:
                if fh is not None:
                    fh.close()
    
        def export(self, filename):
            if filename.lower().endswith(".xpm"):
                self.__export_xpm(filename) #
            else:
                raise ExportError("unsupported export format:" +
                                  os.path.splitext(filename)[1])
    
    
        def __export_xpm(self, filename):  #直接从源代码中复制过来的
            """Exports the image as an XPM file if less than 8930 colors are
            used
            """
            name = os.path.splitext(os.path.basename(filename))[0]
            count = len(self.__colors)
            chars = [chr(x) for x in range(32, 127) if chr(x) != '"']
            if count > len(chars):
                chars = []
                for x in range(32, 127):
                    if chr(x) == '"':
                        continue
                    for y in range(32, 127):
                        if chr(y) == '"':
                            continue
                        chars.append(chr(x) + chr(y))
            chars.reverse()
            if count > len(chars):
                raise ExportError("cannot export XPM: too many colors")
            fh = None
            try:
                fh = open(filename, "w", encoding="ascii")
                fh.write("/* XPM */
    ")
                fh.write("static char *{0}[] = {{
    ".format(name))
                fh.write("/* columns rows colors chars-per-pixel */
    ")
                fh.write('"{0.width} {0.height} {1} {2}",
    '.format(
                         self, count, len(chars[0])))
                char_for_colour = {}
                for color in self.__colors:
                    char = chars.pop()
                    fh.write('"{char} c {color}",
    '.format(**locals()))
                    char_for_colour[color] = char
                fh.write("/* pixels */
    ")
                for y in range(self.height):
                    row = []
                    for x in range(self.width):
                        color = self.__data.get((x, y), self.__background)
                        row.append(char_for_colour[color])
                    fh.write('"{0}",
    '.format("".join(row)))
                fh.write("};
    ")
            except EnvironmentError as err:
                raise ExportError(str(err))
            finally:
                if fh is not None:
                    fh.close()
    
    
    
    
    if __name__ == "__main__":
    
        import doctest
        doctest.testmod()
    

    组合类型的特殊方法 [ ], in

    特殊方法 使用 描述
    __contains__(self, x) x in y 如果x在序列y中,或x是映射y种的键,就返回True
    __delitem__(self, k) del y[k] 删除序列y中的第k项或映射y中键为k的项
    __getitem__(self, k) y[k] 返回序列y中第k项或映射y中键为k的项的值
    __iter__(self) for x in y: pass 返回序列y中的项或映射y中键的迭代子
    __len__(self) len(y) 返回y中项的个数
    __reversed__(self) reversed(y) 返回序列y中的项或映射y中的键的反向迭代子
    __setitem__(self, k, v) y[k] = v 将序列y中的第k项(或映射y中键为k的项)设置为v

    6.3.2 使用聚集创建组合类 SortedList

    """
    >>> L = SortedList((5, 8, -1, 3, 4, 22))
    >>> L[2] = 18 #doctest: +IGNORE_EXCEPTION_DETAIL
    Traceback (most recent call last):
    ...
    TypeError: use add() to insert a value and rely on the...
    >>> list(L)
    [-1, 3, 4, 5, 8, 22]
    >>> L.add(5)
    >>> L.add(5)
    >>> L.add(6)
    >>> list(L)
    [-1, 3, 4, 5, 5, 5, 6, 8, 22]
    >>> L.index(4)
    2
    >>> L.count(5), L.count(2)
    (3, 0)
    >>> L.insert(2, 9)
    Traceback (most recent call last):
    ...
    AttributeError: 'SortedList' object has no attribute 'insert'
    >>> L.reverse()
    Traceback (most recent call last):
    ...
    AttributeError: 'SortedList' object has no attribute 'reverse'
    >>> L.sort()
    Traceback (most recent call last):
    ...
    AttributeError: 'SortedList' object has no attribute 'sort'
    
    >>> import collections
    >>> isinstance(L, collections.Sequence)
    False
    """
    
    
    _identity = lambda x: x
    
    class SortedList:
        def __init__(self, sequence=None, key=None):
            """Creates a SortedList that orders using < on the items,
            or on the results of using the given key function
    
            >>> L = SortedList()
            >>> print(L)
            []
            >>> L = SortedList((5, 8, -1, 3, 4, 22))
            >>> print(L)
            [-1, 3, 4, 5, 8, 22]
            >>> L = SortedList({9, 8, 7, 6, -1, -2})
            >>> print(L)
            [-2, -1, 6, 7, 8, 9]
            >>> L = SortedList([-5, 4, -3, 8, -2, 16, -1, 0, -3, 8])
            >>> print(L)
            [-5, -3, -3, -2, -1, 0, 4, 8, 8, 16]
            >>> L2 = SortedList(L)
            >>> print(L2)
            [-5, -3, -3, -2, -1, 0, 4, 8, 8, 16]
            >>> L = SortedList(("the", "quick", "brown", "fox", "jumped"))
            >>> print(L)
            ['brown', 'fox', 'jumped', 'quick', 'the']
            >>> L.index('1')
            Traceback (most recent call last):
            ...
            ValueError: SortedList.index(x): x not in list
            """
            self.__key = key or _identity  #_identity = lambda x: x
            assert hasattr(self.__key, "__call__") # 对象是否能调用
            if sequence is None:
                self.__list = []
            elif (isinstance(sequence, SortedList) and
                  sequence.key == self.__key):
                """因为Python采用的是短路检测,所以不用担心后面部分会报错
                如果key使用lambdaIf创建的,那么这部分就不会执行,所以,这部分
                代码可能不会带来多大效率的提升。
                """
                self.__list = sequence.__list[:]
            else:
                self.__list = sorted(list(sequence), key=self.__key)
    
    
        @property
        def key(self):
            return self.__key
    
        def add(self, value):
            """
            书上说为了避免index超出限制,才分开来
            事实上,没问题啊,即便超出了也是加末尾
            所以不需要分类讨论
            是Python版本的问题?
            :param value:
            :return:
            """
    
            index = self.__bisect_left(value)
            if index == len(self.__list):
                self.__list.append(value)
            else:
                self.__list.insert(index, value)
    
        def __bisect_left(self, value):
            """
            二叉树算法找插入的index
            :param value:
            :return:
            """
            key = self.__key(value)
            left, right = 0, len(self.__list)
            while left < right:
                middle = (left + right) // 2
                if self.__key(self.__list[middle]) < key:
                    left = middle + 1
                else:
                    right = middle
    
            return left
    
        def remove(self, value):
            index = self.__bisect_left(value)
            if index < len(self.__list) and self.__list[index] == value:
                del self.__list[index]
            else:
                raise ValueError("{0}.remove(x): x not in list.".format(
                    self.__class__.__name__
                ))
    
        def remove_every(self, value):
            """
            删除每一个值为value的项
            :param value:
            :return:
            """
            count = 0
            index = self.__bisect_left(value)
            while (index < len(self.__list) and
                   sekf.__list[index] == value):
                del self.__list[index]
                count += 1
            return count
    
        def count(self, value):
            """
            :param value:
            :return: 值为value的项的数目
            """
            count = 0
            index = self.__bisect_left(value)
            while(index < len(self.__list) and
                  self.__list[index] == value):
                index += 1
                count += 1
    
            return count
    
        def index(self, value):
            """返回值为value的index,如果不存在报错"""
            index = self.__bisect_left(value)
            if (index < len(self.__list) and
                self.__list[index] == value):
                return index
            raise ValueError("{0}.index(x): x not in list".format(
                self.__class__.__name__
            ))
    
        def __delitem__(self, index):
            del self.__list[index]
    
        def __getitem__(self, index):
            return self.__list[index]
    
        def __setitem__(self, index, value):
            """
            禁止 L[n] = k
            但是我觉得可以
            __delitem__()
            add()
            :param index:
            :param value:
            :return:
            """
            raise TypeError("use add() to insert a value and rely on"
                            "the list to pu it in the right place")
    
        def __iter__(self):
            """
            list(L), 此时Python将调用SortedList.__iter__(L)
            来提供list()函数所需要的序列。
            :return:
            """
            return iter(self.__list)
    
        def __reversed__(self):
            return reversed(self.__list)
    
        def __contains__(self, value):
            """是否包含value"""
            index = self.__bisect_left(value)
            return (index < len(self.__list) and
                    self.__list[index] == value)
        def clear(self):
            self.__list = []
    
        def pop(self, index=-1):
            return self.__list.pop(index)
    
        def __len__(self):
            return len(self.__list)
    
        def __str__(self):
            return str(self.__list)
    
        def copy(self):
            return SortedList(self, self.__key)
    
        __copy__ = copy #其意义在于,使得copy.copy()也将调用copy()方法
    
    
    
    if __name__ == "__main__":
        import doctest
        doctest.testmod()
    

    6.3.3 使用继承创建组合类

    """A dictionary that is sorted by < over its keys or by < over
    the result of the key function applied to the keys
    
    These are tests for inherited methods that aren't reimplemented
    >>> d = SortedDict(dict(s=1, a=2, n=3, i=4, t=5, y=6))
    >>> d["i"]
    4
    >>> d["y"]
    6
    >>> d["z"]
    Traceback (most recent call last):
    ...
    KeyError: 'z'
    >>> d = SortedDict(dict(s=1, a=2, n=3, i=4, t=5, y=6))
    >>> d.get("X", 21)
    21
    >>> d.get("i")
    4
    >>> d = SortedDict(dict(s=1, a=2, n=3, i=4, t=5, y=6))
    >>> "a" in d
    True
    >>> "x" in d
    False
    >>> d = SortedDict(dict(s=1, a=2, n=3, i=4, t=5, y=6))
    >>> len(d)
    6
    >>> del d["n"]
    >>> del d["y"]
    >>> len(d)
    4
    >>> d.clear()
    >>> len(d)
    0
    >>> d = SortedDict(dict(V=1, E=2, I=3, N=4, S=5))
    >>> str(d)
    "{'E': 2, 'I': 3, 'N': 4, 'S': 5, 'V': 1}"
    """
    
    
    from practice import SortedList
    
    
    class SortedDict(dict):
    
        def __init__(self, dictionary=None, key=None, **kwargs):
            """Initializes with a shallow copy of the given dictionary
                   and/or with keyword key=value pairs and preserving order using
                   the key function. All keys must be unique.
    
                   key is a key function which defaults to the identity
                   function if it is not specified
    
                   >>> d = SortedDict(dict(s=1, a=2, n=3, i=4, t=5, y=6))
                   >>> list(d.items())
                   [('a', 2), ('i', 4), ('n', 3), ('s', 1), ('t', 5), ('y', 6)]
                   >>> dict(SortedDict())
                   {}
                   >>> e = SortedDict(d)
                   >>> list(e.items())
                   [('a', 2), ('i', 4), ('n', 3), ('s', 1), ('t', 5), ('y', 6)]
                   >>> dict(e)
                   {'a': 2, 'i': 4, 'n': 3, 's': 1, 't': 5, 'y': 6}
                   >>> f = SortedDict(key=str.lower, S=1, a=2, n=3, I=4, T=5, y=6)
                   >>> dict(f)
                   {'a': 2, 'I': 4, 'n': 3, 'S': 1, 'T': 5, 'y': 6}
                   """
            dictionary = dictionary or {}
            super().__init__(dictionary)
            if kwargs:
                super().update(kwargs)
            self.__keys = SortedList.SortedList(super().keys(), key)
    
        def update(self, dictionary=None, **kwargs):
            if dictionary is None:
                pass
            elif isinstance(dictionary, dict):
                super().update(dictionary)
            else:
                for key, value in dictionary.items():#如果没有提供items方法,AttributeError
                    super().__setitem__(key, value)
            if kwargs:
                super().update(kwargs)
            self.__keys = SortedList.SortedList(super().keys(), self.__keys.key)
    
        @classmethod #类方法 可以调用类属性ClassCase.classmethod() 会自动传入cls
        def fromkeys(cls, iterable, value=None, key=None):
            return cls({k: value for k in iterable}, key)
    
        def __setitem__(self, key, value):
            if key not in self:
                self.__keys.add(key)
            return super().__setitem__(key, value)
    
        def __delitem__(self, key):
            try:
                self.__keys.remove(key)
            except ValueError:
                raise KeyError(key)
            return super().__delitem__(key)
    
        def setdefault(self, key, value=None):
            if key not in self:
                self.__keys.add(key)
            return super().setdefault(key, value)
    
        def pop(self, key, *args):
            """
            d.pop(k)
            d.pop(k, value)
            :param key:
            :param args:
            :return:
            """
            if key not in self:
                if len(args) == 0:
                    raise KeyError(key)
                return args[0]
            self.__keys.remove(key)
            return super().pop(key, args)
    
        def popitem(self):
            """
            移除并返回字典中一个随机的键-值对
            :return:
            """
            item = super().popitem()
            self.__keys.remove(item[0])
            return item
    
        def clear(self):
            super().clear()
            self.__keys.clear()
    
        def values(self):
            """
            返回的是一个迭代子
            :return:
            """
            for key in self.__keys:
                yield self[key]
    
        def items(self):
            """
            迭代子
            :return:
            """
            for key in self.__keys:
                yield key, self[key]
    
        def __iter__(self):
            return iter(self.__keys)
    
        keys = __iter__ #相同功效
    
        def __repr__(self):
            """不能eval()的表象形式"""
            return object.__repr__(self)
    
        def __str__(self):
            return "{" + ", ".join(["{0!r}: {1!r}".format(k, v)
                                    for k, v in self.items()]) + "}"
    
        def copy(self):
            """
            不带参数的时候,super()将针对基类与对象进行工作。
            这里我们显示地传递类与对象
            :return:
            """
            d = SortedDict()
            super(SortedDict, d).update(self) # == dict.update(d, self) | d.update(self)
            d.__keys = self.__keys.copy()
            return d
    
        __copy__ = copy
    
        def value_at(self, index):
            """因为这是有序地dict所以可以根据位置来获取"""
            return self[self.__keys[index]]
    
        def set_value_at(self, index, value):
            self[self.__keys[index]] = value
    
    
    
    if __name__ == "__main__":
        import doctest
        doctest.testmod()
    

    静态方法与类方法

    静态方法:

    class foo:
        @staticmethod
        def f():
            print("Q!!!!")
            
    f = foo()
    foo.f(), f.f()
    

    类方法:

    class foo:
        @classmethod
        def f(cls):
            print("Q!!!!")
            
    f = foo()
    foo.f(), f.f()
    

    上面的输出都是:

    Q!!!!
    Q!!!!
    (None, None)
    

    @staticmethod是修饰器,具体如何实现我不知道,但是,如果像下面一样定义foo:

    class foo:
        def f():
            print("Q!!!!")
    

    执行foo.f() 没有问题
    但是执行f.f()的时候就有问题了,

    TypeError                                 Traceback (most recent call last)
    <ipython-input-54-4591815b19b5> in <module>
    	4 
     	5 f = foo()
    ----> 6 foo.f(), f.f()
    TypeError: f() takes 0 positional arguments but 1 was given
    

    大概是Python在执行的时候,默认当期为实例方法,会把实例作为第一个参数传入而报错。@staticmethod的作用大概就是修正这一点。
    而@classmethod的作用则是,无论是通过类还是实例调用方法,都只会把类作为第一个参数传入。这大概就是修饰器的作用所在。修饰器的强大可见一斑。

    6.5 练习

    import pickle
    
    class AccountError(Exception): pass
    class SaveError(AccountError): pass
    class LoadError(AccountError): pass
    
    class Transaction:
        """
        实现一个Transaction类
    
        >>> t = Transaction(100, "2019-2-18", "RMB", 0.1476, "Go forward...")
        >>> t.amount
        100
        >>> t.date
        '2019-2-18'
        >>> t.currency
        'RMB'
        >>> t.usd_conversion_rate
        0.1476
        >>> t.description
        'Go forward...'
        """
        def __init__(self, amount, date, currency="USD",
                     usd_conversion_rate=1, description=None):
            """
            属性均为私有
            :param amount:
            :param date:
            :param currency: 默认为"USD",U.S. dollars
            :param usd_conversion_rate: 默认为1
            :param description: 默认为None
            """
            self.__amount = amount
            self.__date = date
            self.__currency = currency
            self.__usd_conversion_rate = usd_conversion_rate
            self.__description = description
    
        @property
        def amount(self):
            return self.__amount
    
        @property
        def date(self):
            return self.__date
    
        @property
        def currency(self):
            return self.__currency
    
        @property
        def usd_conversion_rate(self):
            return self.__usd_conversion_rate
    
        @property
        def description(self):
            return self.__description
    
        @property
        def usd(self):
            return self.__amount * self.__usd_conversion_rate
    
    
    
    class Account:
        """
        >>> import os
        >>> import tempfile
        >>> name = os.path.join(tempfile.gettempdir(), "account01")
        >>> account = Account(name, "Qtrac Ltd.")
        >>> os.path.basename(account.number), account.name,
        ('account01', 'Qtrac Ltd.')
        >>> account.balance, account.all_usd, len(account)
        (0.0, True, 0)
        >>> account.apply(Transaction(100, "2008-11-14"))
        >>> account.apply(Transaction(150, "2008-12-09"))
        >>> account.apply(Transaction(-95, "2009-01-22"))
        >>> account.balance, account.all_usd, len(account)
        (155.0, True, 3)
        >>> account.apply(Transaction(50, "2008-12-09", "EUR", 1.53))
        >>> account.balance, account.all_usd, len(account)
        (231.5, False, 4)
        >>> account.save()
        >>> newaccount = Account(name, "Qtrac Ltd.")
        >>> newaccount.balance, newaccount.all_usd, len(newaccount)
        (0.0, True, 0)
        >>> newaccount.load()
        >>> newaccount.balance, newaccount.all_usd, len(newaccount)
        (231.5, False, 4)
        >>> try:
        ...     os.remove(name + ".acc")
        ... except EnvironmentError:
        ...     pass
        """
        def __init__(self, number, name):
            self.__number = number
            self.__name = name
            self.__transactions = []
    
        @property
        def number(self):
            return self.__number
    
        @property
        def name(self):
            return self.__name
    
        @name.setter
        def name(self, name):
            assert isinstance(name, str) and len(name) > 3, 
                "name must be string whose length >= 4"
            self.__name = name
    
        def __len__(self):
            return len(self.__transactions)
    
        @property
        def balance(self):
            """
            交易额  单位USD
            :return:
            """
            total = 0.0
            for transaction in self.__transactions:
                total += transaction.usd
            return total
    
        @property
        def all_usd(self):
            """是否均为USD"""
            for transaction in self.__transactions:
                if transaction.currency is not "USD":
                    return False
            return True
    
        def apply(self, transaction):
            if not isinstance(transaction, Transaction):
                raise TypeError("{0} is not Transaction".format(transaction))
            self.__transactions.append(transaction)
    
    
        def save(self):
            """数据保存为number.acc"""
            fh = None
            try:
                data = [self.__number, self.__name, self.__transactions]
                fh = open(self.__number + ".acc", "wb")
                pickle.dump(data, fh, pickle.HIGHEST_PROTOCOL)
            except (EnvironmentError, pickle.PicklingError) as err:
                raise SaveError(str(err))
    
            finally:
                if fh is not None:
                    fh.close()
    
        def load(self):
            """加载数据,原有数据会被覆盖"""
            fh = None
            try:
                fh = open(self.__number + ".acc", "rb")
                data = pickle.load(fh)
                assert self.__number == data[0], "not match"
                self.__name, self.__transactions = data[1:]
            except (EnvironmentError, pickle.UnpicklingError) as err:
                raise LoadError(str(err))
            finally:
                if fh is not None:
                    fh.close()
    
    
    
    if __name__ == "__main__":
        import doctest
        doctest.testmod()
    
    
  • 相关阅读:
    集训第五周动态规划 G题 回文串
    集训第五周动态规划 F题 最大子矩阵和
    集训第五周动态规划 E题 LIS
    集训第五周动态规划 D题 LCS
    集训第五周动态规划 C题 编辑距离
    集训第五周 动态规划 B题LIS
    集训第五周 动态规划 最大子段和
    防线问题
    P2486 [SDOI2011]染色
    P2146 [NOI2015]软件包管理器
  • 原文地址:https://www.cnblogs.com/MTandHJ/p/10526757.html
Copyright © 2011-2022 走看看