python设计模式-抽象工厂模式

问题:在上一篇 python设计模式:工厂方法模式我们尝试使用工厂方法创建了披萨店,现在为了保证披萨加盟店也能有良好的声誉,我们需要统一原材料,这个该如何做呢? 为了确保每家加盟店都是用高质量的原材料,我们打算建造一加原材料工厂,并将原材料运送到各个加盟店。每个加盟店会对原材料有不同的需求,这里我们就可以用上上一篇介绍的工厂方法模式了。 首先,建造原料工厂 然后建造区域的原料工厂(继承自原料工厂) 在区域的原料工厂中实现原料的创建方法。 将原料工厂组合起来,加入到 PizzaStore(上一篇中由工厂方法实现)代码中。 按照这个思路,我们先创建原料工厂 创建原料工厂 创建原料工厂的实现代码如下: # 原料 class FreshClams: def __str__(self): return 'Fresh Clams' class MarinaraSauce: def __str__(self): return "Marinara Sauce" class ThickCrustDough: def __str__(self): return "Thick Crust Dough" class ReggianoCheese: def __str__(self): return "Reggiano Cheese" class SlicedPepperoni: def __str__(self): return "Sliced Pepperoni" class Garlic: def __str__(self): return "Garlic" class Onion: def __str__(self): return "Onion" class RedPepper: def __str__(self): return "Red Pepper" # 披萨店原料工厂 class PizzaIngredientFactory: ''' 定义原料工厂 ''' def create_dough(self): raise NotImplementedError() def create_sauce(self): raise NotImplementedError() def create_cheese(self): raise NotImplementedError() def create_pepperoni(self): raise NotImplementedError() def create_clam(self): raise NotImplementedError() def create_veggies(self): raise NotImplementedError() 在这个工厂中,每个原料都是一个方法,原料的实现需要在具体的原料工厂中实现。 这里每个原料方法没有做任何工作,只是抛出了NotImplementedError 这样做是为了强制子类重新实现相应的方法,如果不重新实现用到时就会抛出 NotImplementedError。 ...

2017-10-28 · 3 min · 429 words

python设计模式-工厂方法模式

题目:假设你有一个 pizza 店,功能包括下订单、做 pizza,你的代码会如何写呢? def order_pizza(): pizza = Pizza() pizza.prepare() pizza.bake() pizza.cut() pizza.box() return pizza 但是现在你遇到了一个问题,你的 pizza 店需要更多的 pizza,所以现在你需要增加一些代码,来决定适合的 pizza 类型,然后再制造这个 pizza: def order_pizza(pizza_type): # 现在把 pizza 的类型传入 order_pizza() # 根据 pizza 类型,我们实例化正确的具体类,然后将其赋值给 pizza 实例变量 if pizza_type == 'cheese': pizza = CheesePizza() elif pizza_type == 'greek': pizza = GreekPizza() elif pizza_type == 'pepperoni': pizza = PepperoniPizza() # 一旦我们有了一个 pizza,需要做一些准备(擀面皮、加佐料),然后烘烤、切片、装盒 pizza.prepare() pizza.bake() pizza.cut() pizza.box() return pizza 但是经过几天的实践,你发现顾客喜欢点的 ClamPizza、Veggie Pizza 而 Greek pizza 并没有什么人喜欢,这个时候需要修改代码: ...

2017-10-15 · 4 min · 667 words

python设计模式-观察者模式

题目:现在你有一个数字,默认格式化程序是以十进制格式展示此数值,但需要提供一个功能,这个程序要支持添加/注册更多的格式化程序(比如:添加一个十六进制格式化程序和一个二进制格式化程序)。每次数值更新时,已注册的程序就会收到通知,并显示更新后的值。 我们看下需求: NumberFormatter 有一个 number 属性 当 number 值修改时,相关的格式化方式展示结果要改变 此系统必须可扩展已适应其他格式化方式的使用。 一个错误的实现可能是这样的: class NumberFormatter(object): def __init__(self, number): self.number = number def show_data(self): self.default_formatter() self.hex_formatter() self.binary_formatter() def default_formatter(self): pass def hex_formatter(self): pass def binary_formatter(self): pass 我们可以这么使用: number = NumberFormatter(10) number.show_data() 但是这样会有一个问题:这种针对实现的编程会导致我们在增加或者删除需要格式化方式时必须修改代码。比如我们现在不再需要十六进制数字格式的显示,就需要把 hex_formatter 相关的代码删除或者注释掉。 要解决这个问题,就可以用到我们这次要介绍的观察者模式了。 什么是观察者模式 认识观察者模式 我们先看看报纸和杂志的订阅是怎么回事: 报社的业务就是出版报纸 向某家报社订阅报纸,只要他们有新报纸,就会给你送来,只要你是他们的订户,你就会一直受到新报纸。 当你不再想看的时候,取消订阅,他们就不会在送新报纸给你 只要报社还在运营,就会一直有人向他们订阅报纸或取消订阅。 我们用图表示一下,这里出版者 改称为主题(Subject),订阅者改称为观察者(Observer): 1. 开始的时候,鸭子对象不是观察者 2. 鸭子对象过来告诉主题,它想当一个观察者(鸭子其实想说的是:我对你的数据改变感兴趣,一有变化请通知我) 3. 鸭子对象已经是观察者了(鸭子静候通知,一旦接到通知,就会得到一个整数)。 4. 主题有了新的数据(现在鸭子和其他所有观察者都会受到通知:主题已经改变) 5. 老鼠对象要求从观察者中把自己除名(老鼠已经观察次主题太久,决定不再当观察者了)。 6. 老鼠离开了(主题知道老鼠的请求后,把它从观察者中移除了)。 7. 主题有了一个新的整数(除了老鼠之外,每个观察者都会收到通知,如果老鼠又想当观察者了,它还可以再回来) 定义观察者模式 当你试图勾勒观察者模式时,可以利用报纸订阅服务,以及出版这和订阅者比你这一切。在程序设计中,观察者模式通常被定义为: 观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象改变状态是,它的所有依赖者都会收到通知并自动更新。 我们和之前的例子做个对比: ...

2017-09-24 · 3 min · 461 words

redis 学习笔记

这篇 redis 学习笔记主要介绍 redis 的数据结构和数据类型,并讨论数据结构的选择以及应用场景的优化。 redis 是什么 Redis是一种面向“键/值”对类型数据的分布式NoSQL数据库系统,特点是高性能,持久存储,适应高并发的应用场景。 Redis 数据结构 动态字符串 (Sds) 双端列表 (LINKEDLIST) 字典 跳跃表 (SKIPLIST) 整数集合 (INTSET) 压缩列表 (ZIPLIST) HUGOMORE42 动态字符串 Sds (Simple Dynamic String,简单动态字符串)是 Redis 底层所使用的字符串表示,它被用 在几乎所有的 Redis 模块中 Redis 是一个键值对数据库(key-value DB),数据库的值可以是字符串、集合、列表等多种类 型的对象,而数据库的键则总是字符串对象 在 Redis 中, 一个字符串对象除了可以保存字符串值之外,还可以保存 long 类型的值当字符串对象保存的是字符串时,它包含的才是 sds 值,否则的话,它就 是一个 long 类型的值 动态字符串主要有两个作用: 实现字符串对象(StringObject) 在 Redis 程序内部用作 char * 类型的替代品 [双端列表] (http://origin.redisbook.com/internal-datastruct/adlist.html) 双端链表还是 Redis 列表类型的底层实现之一,当对列表类型的键进行操作——比如执行 RPUSH 、LPOP 或 LLEN 等命令时,程序在底层操作的可能就是双端链表 双端链表主要有两个作用: 作为 Redis 列表类型的底层实现之一; 作为通用数据结构,被其他功能模块所使用; 字典 字典(dictionary),又名映射(map)或关联数组(associative array), 它是一种抽象数据结 构,由一集键值对(key-value pairs)组成,各个键值对的键各不相同,程序可以将新的键值对 添加到字典中,或者基于键进行查找、更新或删除等操作 ...

2017-09-17 · 3 min · 543 words

Python 元组和分片

这一篇是《流畅的 python》读书笔记。主要介绍元组、分片、序列赋值以及引用了大师 Edsger W.Dijkstra为什么序列从0开始计数的解释。 元组 在有些python 的介绍中,元组被称为不可变列表,这其实是不准确的,没有完全概括元组的特点。元组除了用作不可变列表,还可以用于没有字段名的记录。 元组和记录 元组其实是对数据的记录:元组中的每个元素都存放了记录中一个字段的数据,外加这个数据的位置。 如果把元组当作一些字段的集合,数量和位置信息会变得非常重要。比如以下几条用元组表示的记录: >>> lax_coordinates = (33.9425, -118.408056) # 洛杉矶国际机场的经纬度 # 东京的一些信息:市名、年份、人口、人口变化和面积 >>> city, year, pop, chg, area = ('Tokyo', 2003, 32450, 0.66, 8014) 以上这两个元组每个位置都对应一个数据记录。 元组拆包 >>> city, year, pop, chg, area = ('Tokyo', 2003, 32450, 0.66, 8014) 这个例子中,我们把元组的数据用一条语句分别赋值给 city, year, pop, chg, area,这就是元组拆包的一个具体应用。 元组拆包可以应用到任何可迭代对象上,但是被迭代的对象窄的元素的数量必须跟接受这些元素的元组的空档数一致。 比如: >>> lax_coordinates = (33.9425, -118.408056) >>> latitude, longitude = lax_coordinates >>> latitude 33.9425 >>> longitude -118.408056 还可以用 * 运算符把一个可迭代对象拆开作为函数的参数: >>> divmod(20, 8) (2, 4) >>> t = (20, 8) >>> divmode(*t) (2, 4) >>> quotient, remainder = divmode(*t) >>> quotient, remainder (2, 4) 在进行拆包是,我们可能对元组的某些值并不感兴趣,这时可以用 _ 占位符处理。比如: ...

2017-09-10 · 3 min · 583 words

Python 列表推导及优先级队列的实现

这一篇是《流畅的 python》读书笔记。主要介绍列表、列表推导有关的话题,最后演示如何用列表实现一个优先级队列。 Python 内置序列类型 Python 标准库用 C 实现了丰富的序列类型: 容器序列: list、tuple 和 collections.deque 这些序列能存放不同类型的数据。 扁平序列: str、bytes、bytearray、memoryview 和 array.array,这类序列只能容纳一种类型。 容器序列存放的是它们所包含的任意类型的对象的引用,而扁平序列里存放的是值而不是引用(也可以说扁平序列其实存放的是一段连续的内存空间)。 如果按序列是否可被修改来分类,序列分为可变序列 和 不可变序列: 可变序列 list、bytearray、array.array、collections.deque 和 memoryview。 不可变序列 tuple、str和 bytes。 下图显示了可变序列(MutableSequence)和不可变序列(sequence)的差异: 从这个图可以看出,可变序列从不可变序列那里继承了一些方法。 列表推导和生成器表达式 列表(list)是 Python 中最基础的序列类型。list 是一个可变序列,并且能同时存放不同类型的元素。 列表的基础用法这里就不再介绍了,这里主要介绍一下列表推导。 列表推导和可读性 列表推导是构建列表的快捷方式,并且有更好的可读性。 先看下面两段代码: #1. 把一个字符串变成 unicode 码位的列表 >>> symbols = '$&@#%^&*' >>> codes = [] >>> for symbol in symbols: codes.append(ord(symbol)) >>> codes [36, 38, 64, 35, 37, 94, 38, 42] #2. 把一个字符串变成 unicode 码位的列表 使用列表推导 >>> symbols = '$&@#%^&*' >>> codes = [ord(s) for s in symbols] >>> codes [36, 38, 64, 35, 37, 94, 38, 42] 对比发现,如果理解列表推导的话,第二段代码比第一段更简洁可读性也更好。 当然,列表推导也不应该被滥用,通常的原则是只用列表推导来创建新的列表,并且尽量保持简短。 如果列表推导超过两行,就应该考虑要不要使用 for 循环重写了。 ...

2017-09-03 · 3 min · 583 words

小程序开发:上传图片到腾讯云

这是小程序开发第二篇,主要介绍如何上传图片到腾讯云,之所以选择腾讯云,是因为腾讯云免费空间大😂 准备工作 上传图片主要是将图片上传到腾讯云对象存储(COS)。 要使用对象存储 API,需要先执行以下步骤: 购买腾讯云对象存储(COS)服务 在腾讯云 对象存储控制台 里创建一个 Bucket 在控制台 个人 API 密钥 页面里获取 AppID、SecretID、SecretKey 内容 编写一个请求签名算法程序(或使用任何一种服务端 SDK) 计算签名,调用 API 执行操作 所以我们要做的准备工作有: 进入腾讯云官网,注册帐号 登录云对象存储服务(COS)控制台,开通COS服务,创建资源需要上传的Bucket 在小程序官网上配置域名信息(否则无法在小程序中发起对该域名的请求) 这些配置过程这里就不做说明了,接下来主要介绍步骤4、5。 小程序上传图片到 cos 流程如下图: 在这个过程中我们需要实现的是,鉴权服务器返回签名的步骤以及小程序的相关步骤。 COS鉴权服务 使用对象存储服务 COS 时,可通过 RESTful API 对 COS 发起 HTTP 匿名请求或 HTTP 签名请求,对于签名请求,COS 服务器端将会进行对请求发起者的身份验证。 匿名请求:HTTP 请求不携带任何身份标识和鉴权信息,通过 RESTful API 进行 HTTP 请求操作。 签名请求:HTTP 请求时添加签名,COS服务器端收到消息后,进行身份验证,验证成功则可接受并执行请求,否则将会返回错误信息并丢弃此请求。 腾讯云COS对象存储,基于密钥 HMAC (Hash Message Authentication Code) 的自定义 HTTP 方案进行身份验证。 上传图片是一个签名请求,需要进行签名验证。之所以我们 签名流程 客户通过对 HTTP 请求进行签名,并将签名后的请求发送至腾讯云进行签名验证,具体流程如下图所示。 我们使用 sdk 开发,这个流程大致了解下就行,签名的实现 sdk 已经包含,只需要调用方法即可。 ...

2017-08-27 · 3 min · 461 words

Python 装饰器使用指南

装饰器是可调用的对象,其参数是另一个函数(被装饰的函数)。 装饰器基础知识 首先看一下这段代码 def deco(fn): print "I am %s!" % fn.__name__ @deco def func(): pass # output I am func! # 没有执行func 函数 但是 deco 被执行了 HUGOMORE42 在用某个@decorator来修饰某个函数func时 @decorator def func(): pass 其解释器会解释成下面这样的语句: func = decorator(func) 其实就是把一个函数当参数传到另一个函数中,然后再回调,但是值得注意的是装饰器必须返回一个函数给func 装饰器的一大特性是,能把被装饰的函数替换成其他函数。第二大特性是,装饰器在加载模块时立即执行。 装饰器何时执行 装饰器的一个关键特性是,它们在被装饰的函数定义后立即运行。这通常在导入是(python 加载模块时)。 看下下面的示例: registry = [] # registry 保存被@register 装饰的函数的引用 def register(func): # register 的参数是一个函数 print('running register(%s)' % func) # 打印被装饰的函数 registry.append(func) # 把 func 存入 `registery` return func # 返回 func:必须返回函数,这里返回的函数与通过参数传入的一样 @register # `f1` 和 `f2`被 `@register` 装饰 def f1(): print('running f1()') @register def f2(): print('running f2()') def f3(): # <7> print('running f3()') def main(): # main 打印 `registry`,然后调用 f1()、f2()和 f3() print('running main()') print('registry ->', registry) f1() f2() f3() if __name__=='__main__': main() # <9> 运行代码结果如下: ...

2017-08-15 · 8 min · 1578 words

自定义 Sanic Exception

Sanic 是一个和类Flask 的基于Python3.5+的web框架,它使用了 Python3 异步特性,有远超 flask 的性能。 编写 RESTful API 的时候,我们会定义特定的异常错误类型,比如我定义的错误返回值格式为: { "error_code": 0, "message": "string", "text": "string" } 不同的错误信息指定不同的 http 状态码。 sanic 提供了几种常用的 exception: NotFound(404) Forbidden(403) ServerError(500) InvalidUsage(400) Unauthorized(401) RequestTimeout(408) PayloadTooLarge(413) 这些 exception 继承自 SanicException 类: class SanicException(Exception): def __init__(self, message, status_code=None): super().__init__(message) if status_code is not None: self.status_code = status_code 从上述代码可以看出,这些异常只能指定 message 和 status_code 参数,那我们可不可以自定义 exception 然后在自定义的 exception 中增加参数呢?下面的代码是按照这个思路修改后的代码: class ApiException(SanicException): def __init__(self, code, message=None, text=None, status_code=None): super().__init__(message) self.error_code = code self.message = message self.text = text if status_code is not None: self.status_code = status_code 使用后我得到一个结果如下: ...

2017-08-05 · 2 min · 395 words

小程序开发:python sanic 实现小程序登录注册

开发微信小程序时,接入小程序的授权登录可以快速实现用户注册登录的步骤,是快速建立用户体系的重要一步。这篇文章将介绍 python + sanic + 微信小程序实现用户快速注册登录全栈方案。 微信小程序登录时序图如下: 这个流程分为两大部分: 小程序使用 wx.login() API 获取 code,调用 wx.getUserInfo() API 获取 encryptedData 和 iv,然后将这三个信息发送给第三方服务器。 第三方服务器获取到 code、encryptedData和 iv 后,使用 code 换取 session_key,然后将 session_key 利用 encryptedData 和 iv 解密在服务端获取用户信息。根据用户信息返回 jwt 数据,完成登录。 下面我们先看一下小程序提供的 API。 小程序登录 API 在这个授权登录的过程中,用到的 API 如下: wx.login wx.getUserInfo wx.chekSession 是可选的,这里并没有用到。 wx.login(OBJECT) 调用此接口可以获取登录凭证(code),以用来换取用户登录态信息,包括用户的唯一标识(openid) 及本次登录的 会话密钥(session_key)。 如果接口调用成功,返回结果如下: 参数名 类型 说明 errMsg String 调用结果 code String 用户允许登录后,回调内容会带上 code(有效期五分钟),开发者需要将 code 发送到开发者服务器后台,使用code 换取 session_key api,将 code 换成 openid 和 session_key code 换取 session_key 开发者服务器使用登录凭证 code 获取 session_key 和 openid。其中 session_key 是对用户数据进行加密签名的密钥。为了自身应用安全,session_key 不应该在网络上传输。所以这一步应该在服务器端实现。 ...

2017-07-30 · 5 min · 929 words