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