《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 (面向对象程序设计)的更多相关文章

  1. Python Revisited Day 08 (高级程序设计技术)

    目录 8.1 过程型程序设计进阶 8.1.1 使用字典进行分支 8.1.2 生成器表达式与函数 8.1.3 动态代码执行与动态导入 动态程序设计与内省函数(表) 动态代码执行 eval(), exec ...

  2. [Python学习笔记][第六章Python面向对象程序设计]

    1月29日学习内容 Python面向对象程序设计 类的定义与使用 类定义语法 使用class关键词 class Car: def infor(self): print("This is ca ...

  3. Python基础(16)_面向对象程序设计(类、继承、派生、组合、接口)

    一.面向过程程序设计与面向对象程序设计 面向过程的程序设计:核心是过程,过程就解决问题的步骤,基于该思想设计程序就像是在设计一条流水线,是一种机械式的思维方式 优点:复杂的问题的简单化,流程化 缺点: ...

  4. 第7章 Python类型、类、协议 第7.1节 面向对象程序设计的相关知识

    Python被视为一种面向对象的语言,在介绍Python类相关的内容前,本节对面向对象程序设计相关的概念进行简单介绍. 一.    类和对象(实例) 在面向对象的程序设计(OOP)过程中有两个重要概念 ...

  5. [.net 面向对象程序设计深入](4)MVC 6 —— 谈谈MVC的版本变迁及新版本6.0发展方向

    [.net 面向对象程序设计深入](4)MVC 6 ——谈谈MVC的版本变迁及新版本6.0发展方向 1.关于MVC 在本篇中不再详细介绍MVC的基础概念,这些东西百度要比我写的全面多了,MVC从1.0 ...

  6. [.net 面向对象程序设计进阶] (25) 团队开发利器(四)分布式版本控制系统Git——使用GitStack+TortoiseGit 图形界面搭建Git环境

    [.net 面向对象程序设计进阶] (25) 团队开发利器(四)分布式版本控制系统Git——使用GitStack+TortoiseGit 图形界面搭建Git环境 本篇导读: 前面介绍了两款代码管理工具 ...

  7. Python学习之路--面向对象

    1.面向对象概述 面向过程:根据业务逻辑从上到下写垒代码  函数式:将某功能代码封装到函数中,日后便无需重复编写,仅调用函数即可  面向对象:对函数进行分类和封装,让开发“更快更好更强...”  面向 ...

  8. [.net 面向对象程序设计进阶] (2) 正则表达式 (一) 快速入门

    [.net 面向对象程序设计进阶] (2) 正则表达式 (一) 快速入门 1. 什么是正则表达式? 1.1 正则表达式概念 正则表达式,又称正则表示法,英文名:Regular Expression(简 ...

  9. [.net 面向对象程序设计深入](14)Redis——基础

    [.net 面向对象程序设计深入](14)Redis——基础 很长一段时间没更新博客了,坚持做一件事,真不是件容易的事,后面我会继续尽可能的花时间更新完这个系列文章. 因这个系列的文章涉及的范围太大了 ...

随机推荐

  1. 为你的Python程序加密

      在实际的工作中,有时候我们需要部署自己的Python应用,但这时候我们并不希望别人能够看到自己的Python源程序.因此,我们需要为自己的源代码进行加密,Python已经为我们提供了这样一套工作机 ...

  2. ADO.NET基础学习 二(Command对象)

    ②command对象用来操作数据库.(三个重要的方法:ExecuteNonQuery(),ExecuteReader(),ExecuteScalar()) ⑴以update(改数据)为例,用到Exec ...

  3. 杂牌机搞机之旅(一)——获得root权限(刷入magisk)

    刷机不规范,抱机两行泪,谨慎刷机!! 一般获取root权限,我们都是通过软件来获取的,但是,软件破解root的成功率不是很高,现在,android版本普遍5.0+,大名鼎鼎的magisk可以直接获得r ...

  4. eclipse自定义工作区列表

    打开eclipse,在菜单栏上找到Window,点击Window--->Perspective--->Customize Perspective...,会看到 弹出来的一个窗口,然后点击最 ...

  5. linux学习笔记-配置vbox虚拟机本地连接和外网同时可用

    我的邮箱地址:zytrenren@163.com欢迎大家交流学习纠错! 在设置网络里面启用两个网卡,一个桥接,一个网络地址转换 archlinux系统下第一个网络地址转换,第二个桥接 centos7系 ...

  6. html iframe高度自适应

    想到的一种办法是,在父页面里获取子页面的高度,在父页面onlod里把获取到子页面的高度赋值给父页面iframe标签,不过这种方法感觉不是很好,因为浏览器兼容性不好,获取不到高度 这种方法有两种写法 & ...

  7. arcgis api 3.x for js入门开发系列九热力图效果(附源码下载)

    前言 关于本篇功能实现用到的 api 涉及类看不懂的,请参照 esri 官网的 arcgis api 3.x for js:esri 官网 api,里面详细的介绍 arcgis api 3.x 各个类 ...

  8. C# 生成随机索引列表

    /// <summary> /// 生成随机索引列表 /// </summary> /// <param name="maxNumber">&l ...

  9. 钉钉JSAPI前端鉴权

    钉钉二次开发分为如下表所示三种类型的开发,只有企业内部应用才需要对JSAPI鉴权. 类型 开发方式 JSAPI鉴权 应用场景 第三方企业应用 E应用开发 不需要 用于发布到钉钉应用市场,供广大用户下载 ...

  10. Easyui datagrid 扩展单元格textarea editor

    datagrid 扩展单元格textarea editor by:授客 QQ:1033553122 测试环境 jquery-easyui-1.5.3 问题描述 如下,在没有扩展的情况下,初始化如下 手 ...