Python如何设计面向对象的类(下)

本文将在上篇文章二维向量Vector2d类的基础上,定义表示多维向量的Vector类。

Python如何设计面向对象的类(上)

Python是一门高级语言,支持面向对象设计,如何设计一个符合Python风格的面向对象的类,是一个比较复杂的问题,本文提供一个参考,表达一种思路,探究一层原理。

Python变量小秘密

本书作者:测试开发刚哥,文章版权归个人所有,未经授权请勿转载。 变量全都是引用 跟其他编程语言不同,Python的变量不是盒子,不会存储数据,它们只是引用,就像标签一样,贴在对象上面。 比如: >>> a = [1, 2, 3] >>> b = a >>> a.append(4) >>> b [1, 2, 3, 4] >>> b is a True a变量和b变量引用的是同一个列表[1, 2, 3]。b可以叫做a的别名。 比较来看: >>> a = [1, 2, 3] >>> c = [1, 2, 3] >>> c == a True >>> c is a False c引用的是另外一个列表,虽然和a引用的列表的值相等,但是它们是不同的对象。 浅复制与深复制 浅复制是指只复制最外层容器,副本中的元素是源容器中元素的引用。如果所有元素都是不可变的,那么这样没有问题,还能节省内容。但是,如果有可变的元素,那么结果可能会出乎意料之外。构造方法或[:]做的都是浅复制。 示例: >>> x1 = [3, [66, 55, 44], (7, 8, 9)] ## x2是x1的浅复制 >>> x2 = list(x1) ## 不可变元素没有影响 >>> x1.append(100) >>> x1 [3, [66, 55, 44], (7, 8, 9), 100] >>> x2 [3, [66, 55, 44], (7, 8, 9)] ## x1[1]是列表,可变元素会影响x2 ## 因为它们引用的是同一个对象 >>> x1[1].remove(55) >>> x1 [3, [66, 44], (7, 8, 9), 100] >>> x2 [3, [66, 44], (7, 8, 9)] ## x2[1]也会反过来影响x1 >>> x2[1] += [33, 22] >>> x1 [3, [66, 44, 33, 22], (7, 8, 9), 100] >>> x2 [3, [66, 44, 33, 22], (7, 8, 9)] ## 不可变元组也不会有影响 ## +=运算符创建了一个新元组 >>> x2[2] += (10, 11) >>> x1 [3, [66, 44, 33, 22], (7, 8, 9), 100] >>> x2 [3, [66, 44, 33, 22], (7, 8, 9, 10, 11)] 深复制是指我们常规理解的复制,副本不共享内部对象的引用,是完全独立的一个副本。这可以借助copy.deepcopy来实现。 ...

Python编解码问题与文本文件处理

本书作者:测试开发刚哥,文章版权归个人所有,未经授权请勿转载。 编解码器 在字符与字节之间的转换过程称为编解码,Python自带了超过100种编解码器,比如: ascii(英文体系) gb2312(中文体系) utf-8(全球通用) latin1 utf-16 编解码器一般有多个别名,比如utf8、utf-8、U8。 这些编解码器可以传给open()、str.encode()、bytes.decode()等函数的encoding参数。 UnicodeEncodeError 多数非UTF编解码器(比如cp437)只能处理Unicode字符的一小部分子集。把字符转换成字节时,如果目标编码中没有定义这个字符,那么就会抛出UnicodeEncodeError异常。 处理方式一:使用utf8编码。 处理方式二:添加errors参数: ## 忽略 如b'So Paulo' city.encode("cp437", errors="ignore") ## 替换为? 如b'S?o Paulo' city.encode("cp437", errors="replace") ## 替换为XML实体 如b'São Paulo' city.encode("cp437", errors="xmlcharrefreplace") UnicodeDecodeError 把字节转换为字符时,遇到无法转换的字节时会抛出UnicodeDecodeError异常。这是因为不是每个字节都包含有效的ASCII字符,也不是每个字符都是有效的UTF-8。 处理方式也有两种,跟上面一样。 SyntaxError Python3默认使用UTF-8编码源码。如果加载的.py模块中包含UTF-8之外的数据,而且没有声明编码,就会抛出SyntaxError异常。 处理方式是在文件顶部添加coding注释: ## coding: cp1252 但是这个办法并不好,最好还是找到这些报错字符,把它们转换为UTF-8。 从网上直接复制代码到IDE中执行经常会报这个错。 处理文本文件 Unicode三明治: 在程序中尽量少接触二进制,把字节解码为字符,只处理字符串对象。比如在Django中,view应该输出Unicode字符串,Django会负责把响应数据编码成字节序列,而且默认使用UTF-8编码。 Python内置的open函数就是采用了这个原则,在读取文件时会做必要的解码,以文本模式写入文件时会做必要的编码。 文件乱码 Windows更容易遇到这个问题,因为Windows并不是统一的UTF-8编码,比如在Windows10中: >>> open("cafe.txt", "w", encoding="utf8").write("café") 4 >>> open("cafe.txt").read() 'caf茅' 写入文件时指定了utf8,但是读取文件没有指定,Python就会使用系统默认编码: >>> import locale ## 打开文件用这个 ## 如果没有设置PYTHONENCODING环境变量,sys.stdout/stdin/stderr也用这个 >>> locale.getpreferredencoding() 'cp936' cp936把最后一个字节解码成了茅而不是é。 >>> import sys # 二进制数据和字符串之间转换用这个 >>> sys.getdefaultencoding() 'utf-8' >>> import sys # 文件名(不是文件内容)用这个 >>> sys.getfilesystemencoding() 'utf-8' GNU/Linux或Mac OS X不会遇到这个问题,因为多年来它们的默认编码都是UTF-8。 ...

Python字符与字节新编

本书作者:测试开发刚哥,文章版权归个人所有,未经授权请勿转载。 字符 字符是一个信息单位,简单来讲就是一个字母、数字、标点符号、汉字等。 字符的最佳定义是Unicode字符: 它是一个全球化的标准,能表示世界上所有语言的字符。Unicode字符的标识(码位)是以4~6个十六进制数字表示的,并且加前缀U+。 字节 字节是计算机信息计量单位,一个字节代表八个比特,存储的数值范围为0~255。 字节跳动(ByteDance)互联网公司的字节就是这个字节。 字节是机器的,字符是人类的。 把人类字符转换为机器字节时使用的算法叫做编码,反之叫做解码。 算法不同,字节与字符的关系也不同: bytes和bytearray 字节实际上是个二进制序列。不可变bytes类型和可变bytearray类型是用来存储二进制序列的,它们的示例如下: >>> cafe = bytes("café", encoding="utf_8") >>> cafe b'caf\xc3\xa9' >>> cafe[0] 99 >>> cafe[:1] b'c' >>> cafe_arr = bytearray(cafe) >>> cafe_arr bytearray(b'caf\xc3\xa9') >>> cafe_arr[-1:] bytearray(b'\xa9') 特别的是cafe[0]返回了整数,cafe[:1]返回了二进制序列,这是因为s[0] == s[:1]只对str类型成立,而对于其他类型来说,s[i]返回一个元素,s[i:i+1]返回一个相同类型的序列。 二进制序列实际上是整数序列。它们的字面量表示法包含ASCII字符(ASCII只能表示英文体系的字符),比如cafe的b'caf\xc3\xa9',具体规则是: 从空格到~的字符直接使用ASCII字符 制表符\t、换行符\n、回车符\r、转义符\\ 其他字符用十六进制转义序列,比如\x00空字节 构建bytes和bytearray对象的方式有以下几种: 一个str对象和一个encoding关键字参数 一个可迭代对象,数值在0~255 一个实现了缓冲协议的对象,如bytes、bytearray、memoryview、array.array memoryview和struct memoryview允许在二进制数据结构之间共享内存,struct能从序列中提取结构化信息。 示例如下,提取一个GIF图像的宽度和高度: import struct with open("filter.gif", "rb") as fp: img = memoryview(fp.read()) ## 这里不会复制字节序列,因为用的memoryview header = img[:10] print(bytes(header)) # b'GIF89a+\x02\xe6\x00' ## <是小字节序,3s3s是两个3字节序列,HH是两个16位二进制整数 ## 类型、版本、宽度、高度 struct.unpack("<3s3sHH", header) # (b'GIF', b'89a', 555, 230) ## 删除引用,释放memoryview实例所占的内存 del header del img 小结 本文介绍了字符和字节的概念以及它们之间的关系,一个字符对应一个或多个字节。字符是人类的,字节是机器的,编码就是人类字符转换为机器字节,反之叫做解码。然后分别介绍了二进制序列的类型bytes和bytearray,和二进制序列的工具memoryview和struct。 ...

Python函数装饰器高级用法

在了解了Python函数装饰器基础知识和闭包之后,开始正式学习函数装饰器。

关于Python闭包的一切

任何把函数当做一等对象的语言,它的设计者都要面对一个问题:作为一等对象的函数在某个作用域中定义,但是可能会在其他作用域中调用,如何处理自由变量?

Python函数装饰器基础知识

函数装饰器是Python语言最优秀的设计之一,它以非常简洁的方式增强了函数的行为,让崎岖不平之路变得平坦顺畅。

Python设计模式知多少

本书作者:测试开发刚哥,文章版权归个人所有,未经授权请勿转载。 设计模式 设计模式是前辈们经过相当长的一段时间的试验和错误总结出来的最佳实践。我找到的资料列举了以下这些设计模式:工厂模式、抽象工厂模式、单例模式、建造者模式、原型模式、适配器模式、桥接模式、过滤器模式、组合模式、装饰器模式、外观模式、享元模式、代理模式、责任链模式、命令模式、解释器模式、迭代器模式、中介者模式、备忘录模式、观察者模式、状态模式、空对象模式、策略模式、模板模式、访问者模式、MVC模式、业务代表模式、组合实体模式、数据访问对象模式、前端控制器模式、拦截过滤器模式、服务定位器模式、传输对象模式,共33种。 这些设计模式在纯面向对象编程语言中使用最多。**Python拥有一等函数,既不需要使用某些设计模式,也减少了某些设计模式样板代码。**本文将使用一等函数实现策略模式和命令模式,研究Python代码是如何简化的。 策略模式 策略模式概述:“定义一系列算法,把它们一一封装起来,并且使它们可以相互替换。本模式使得算法可以独立于使用它的客户而变化。” 经典实现 示例,根据客户的属性或订单中的商品计算折扣,规则如下: 有1000或以上积分的客户,每个订单享5%折扣。 同一订单中,单个商品的数量达到20个或以上,享10%折扣。 订单中的不同商品达到10个或以上,享7%折扣。 这很适合用策略模式来做,UML类图设计如下: 上下文,集成算法的类,图中Order会根据不同的算法计算折扣。 策略,实现不同算法的组件的共同接口,图中Promotion是个抽象类。 具体策略,策略的具体子类,图中FidelityPromo、BulkItemPromo、LargeOrderPromo分别对应上面3条计算折扣规则。 代码实现如下: from abc import ABC, abstractmethod from collections import namedtuple Customer = namedtuple('Customer', 'name fidelity') class LineItem: def __init__(self, product, quantity, price): self.product = product self.quantity = quantity self.price = price def total(self): return self.price * self.quantity class Order: # the Context def __init__(self, customer, cart, promotion=None): self.customer = customer self.cart = list(cart) self.promotion = promotion def total(self): if not hasattr(self, '__total'): self.__total = sum(item.total() for item in self.cart) return self.__total def due(self): if self.promotion is None: discount = 0 else: discount = self.promotion.discount(self) return self.total() - discount def __repr__(self): fmt = '<Order total: {:.2f} due: {:.2f}>' return fmt.format(self.total(), self.due()) class Promotion(ABC): # the Strategy: an Abstract Base Class @abstractmethod def discount(self, order): """Return discount as a positive dollar amount""" class FidelityPromo(Promotion): # first Concrete Strategy """5% discount for customers with 1000 or more fidelity points""" def discount(self, order): return order.total() * .05 if order.customer.fidelity >= 1000 else 0 class BulkItemPromo(Promotion): # second Concrete Strategy """10% discount for each LineItem with 20 or more units""" def discount(self, order): discount = 0 for item in order.cart: if item.quantity >= 20: discount += item.total() * .1 return discount class LargeOrderPromo(Promotion): # third Concrete Strategy """7% discount for orders with 10 or more distinct items""" def discount(self, order): distinct_items = {item.product for item in order.cart} if len(distinct_items) >= 10: return order.total() * .07 return 0 实现策略模式的关键代码是Promotion类,它是一个抽象基类,通过继承abc.ABC来定义。 ...

Python函数参数和注解是什么

本书作者:测试开发刚哥,文章版权归个人所有,未经授权请勿转载。 四种参数 Python函数func定义如下: def func(first, *args, second="Hello World", **kwargs): print(first) print(args) print(second) print(kwargs) func("dongfanger", "san", py="good") 运行后会输出: dongfanger ('san',) Hello World {'py': 'good'} 它有四种参数: first是定位参数,positional parameter,不可省略。 *args是可变参数,arguments,存入元组。 second是默认值参数,default argument values,可以省略。 **args是关键字参数,keyword arguments,存入字典。 func函数的调用方式有以下这些: ①传入单个定位参数。 func("dongfanger") dongfanger () Hello World {} ②第一个参数后的任意个参数会被*args捕获,存入一个元组。 func("dongfanger", "a", "b", "c") dongfanger ('a', 'b', 'c') Hello World {} ③没有明确指定名称的关键字参数会被**kwargs捕获,存入一个字典。 func("dongfanger", j="1", k="2") dongfanger () Hello World {'j': '1', 'k': '2'} ④second只能作为关键字参数传入。 func("dongfanger", second="cool") dongfanger () cool {} ⑤定位函数也能作为关键字参数传入。 func(first="san") san () Hello World {} ⑥字典前加上**,其所有元素作为单个参数传入,同名键会绑定到对应具名参数上,余下的被**args捕获。 ...