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()

posted @ 2019-03-14 09:32  馒头and花卷  阅读(326)  评论(0编辑  收藏  举报