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 width:
        :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 */\n")
            fh.write("static char *{0}[] = {{\n".format(name))
            fh.write("/* columns rows colors chars-per-pixel */\n")
            fh.write('"{0.width} {0.height} {1} {2}",\n'.format(
                     self, count, len(chars[0])))
            char_for_colour = {}
            for color in self.__colors:
                char = chars.pop()
                fh.write('"{char} c {color}",\n'.format(**locals()))
                char_for_colour[color] = char
            fh.write("/* pixels */\n")
            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}",\n'.format("".join(row)))
            fh.write("};\n")
        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()
												
											Python Revisited Day 06 (面向对象程序设计)的更多相关文章
- Python Revisited Day 08 (高级程序设计技术)
		
目录 8.1 过程型程序设计进阶 8.1.1 使用字典进行分支 8.1.2 生成器表达式与函数 8.1.3 动态代码执行与动态导入 动态程序设计与内省函数(表) 动态代码执行 eval(), exec ...
 - [Python学习笔记][第六章Python面向对象程序设计]
		
1月29日学习内容 Python面向对象程序设计 类的定义与使用 类定义语法 使用class关键词 class Car: def infor(self): print("This is ca ...
 - Python基础(16)_面向对象程序设计(类、继承、派生、组合、接口)
		
一.面向过程程序设计与面向对象程序设计 面向过程的程序设计:核心是过程,过程就解决问题的步骤,基于该思想设计程序就像是在设计一条流水线,是一种机械式的思维方式 优点:复杂的问题的简单化,流程化 缺点: ...
 - 第7章 Python类型、类、协议  第7.1节 面向对象程序设计的相关知识
		
Python被视为一种面向对象的语言,在介绍Python类相关的内容前,本节对面向对象程序设计相关的概念进行简单介绍. 一. 类和对象(实例) 在面向对象的程序设计(OOP)过程中有两个重要概念 ...
 - [.net 面向对象程序设计深入](4)MVC 6 —— 谈谈MVC的版本变迁及新版本6.0发展方向
		
[.net 面向对象程序设计深入](4)MVC 6 ——谈谈MVC的版本变迁及新版本6.0发展方向 1.关于MVC 在本篇中不再详细介绍MVC的基础概念,这些东西百度要比我写的全面多了,MVC从1.0 ...
 - [.net 面向对象程序设计进阶] (25) 团队开发利器(四)分布式版本控制系统Git——使用GitStack+TortoiseGit 图形界面搭建Git环境
		
[.net 面向对象程序设计进阶] (25) 团队开发利器(四)分布式版本控制系统Git——使用GitStack+TortoiseGit 图形界面搭建Git环境 本篇导读: 前面介绍了两款代码管理工具 ...
 - Python学习之路--面向对象
		
1.面向对象概述 面向过程:根据业务逻辑从上到下写垒代码 函数式:将某功能代码封装到函数中,日后便无需重复编写,仅调用函数即可 面向对象:对函数进行分类和封装,让开发“更快更好更强...” 面向 ...
 - [.net 面向对象程序设计进阶] (2) 正则表达式 (一) 快速入门
		
[.net 面向对象程序设计进阶] (2) 正则表达式 (一) 快速入门 1. 什么是正则表达式? 1.1 正则表达式概念 正则表达式,又称正则表示法,英文名:Regular Expression(简 ...
 - [.net 面向对象程序设计深入](14)Redis——基础
		
[.net 面向对象程序设计深入](14)Redis——基础 很长一段时间没更新博客了,坚持做一件事,真不是件容易的事,后面我会继续尽可能的花时间更新完这个系列文章. 因这个系列的文章涉及的范围太大了 ...
 
随机推荐
- Mybatis源码之StatementType
			
在mybatis中StatementType的值决定了由什么对象来执行我们的SQL语句.本文来分析下在mybatis中具体是怎么处理的. StatementType 1.StatementType ...
 - 在windows中创建.gitignore文件
			
1.先任意创建一个文件,例如:1.txt 2.打开cmd命令行窗口,到1.txt目录下 windows7/8输入ren 1.txt .gitignore修改成功 windows10输入mv 1.txt ...
 - C# winform在关闭窗体的时候及时释放内存问题
			
winform中如果每次打开的窗体都是通过new出来的,发现几次过后就会出现提示”内存不足“问题,那么在关闭窗体的时候怎么处理可以及时释放内存?dispose方法可能也无法解决这个问题.我们可以每次在 ...
 - Android项目刮刮奖详解(四)
			
Android项目刮刮奖详解(三) 前言 上一期我们已经是完成了刮刮卡的基本功能,本期就是给我们的项目增加个功能以及美化一番 目标 增加功能 用户刮卡刮到一定程度的时候,清除遮盖层 在遮盖层放张图片, ...
 - CANVAS画布与SVG的区别
			
CANVAS是html5提供的新元素<canvas>,而svg存在的历史要比canvas久远,svg并不是html5专有的标签,最初svg是用xml技术(超文本扩展语言,可以自定义标签或属 ...
 - CSS 渐变色
			
CSS linear-gradient() 函数 http://www.runoob.com/cssref/func-linear-gradient.html CSS radial-gradient( ...
 - Html和Css学习笔记-css基础知识
			
我的邮箱地址:zytrenren@163.com欢迎大家交流学习纠错! 此篇博客是我的复习笔记,html和css学的时间太久了,忘得差不多了,最近要使用一下,所以重新打开html的书略读,后记录了标签 ...
 - 快速使用CSS Grid布局,实现响应式设计
			
常用Grid布局属性介绍 下面从一个简单Grid布局例子说起. CSS Grid 布局由两个核心组成部分是 wrapper(父元素)和 items(子元素). wrapper 是实际的 grid(网格 ...
 - dede 采集到数据后,发布日期变为本地日期解决方法
			
找到dede目录下的co_export.php 大概在170行左右 //获取时间和标题 $pubdate = $sortrank = time(); $title = $row->title; ...
 - MySQL 基础知识梳理学习(一)----系统数据库
			
information_schema 此数据库是MySQL数据库自带的,主要存储数据库的元数据,保存了关于MySQL服务器维护的所有其他数据库的信息,如数据库名.数据库表.表列的数据类型及访问权限等. ...