python设计模式-模板方法模式

首先先介绍一下咖啡和茶的冲泡方法: 茶 1. 把水煮沸 2. 用沸水浸泡茶叶 3. 把茶放到杯子里 咖啡 1. 把水煮沸 2. 用沸水冲泡咖啡 3. 把咖啡倒进杯子 4. 加糖和牛奶 用python代码实现冲泡方法大概是这个样子: # 茶的制作方法 class Tea: def prepare_recipe(self): # 在下边实现具体步骤 self.boil_water() self.brew_tea_bag() self.pour_in_cup() def boil_water(self): print("Boiling water") def brew_tea_bag(self): print("Steeping the tea") def pour_in_cup(self): print("Pouring into cup") # 咖啡的制作方法 class Coffee: def prepare_recipe(self): # 在下边实现具体步骤 self.boil_water() self.brew_coffee_grinds() self.pour_in_cup() self.add_sugar_and_milk() def boil_water(self): print("Boiling water") def brew_coffee_grinds(self): print("Dripping Coffee through filter") def pour_in_cup(self): print("Pouring into cup") def add_sugar_and_milk(self): print("Adding Sugar and Milk") 仔细看上边两端代码会发现,茶和咖啡的实现方式基本类似,都有prepare_recipe,boil_water,pour_in_cup 这三个方法。 ...

2018-12-02 · 2 min · 261 words

python设计模式-外观模式

上一篇《python设计模式-适配器模式》介绍了如何将一个类的接口转换成另一个符合期望的接口。这一篇将要介绍需要一个为了简化接口而改变接口的新模式-外观模式(Facade-Pattern)。 问题 问题:如果你组装了一套家庭影院,内含播放器、投影机、自动屏幕、立体声音响、爆米花机等。如何设计一个遥控器,可以简单的操作这个系统中的各个组件呢? 首先来看一下最笨方式观赏电影的步骤: 打开爆米花机 开始爆米花 将灯光调暗 放下屏幕 打开投影仪 将投影机的输入切换到播放器 将投影及设置在宽屏模式 打开功放 将功放的输入设置为播放器 将攻防设置为环绕立体声 将攻防音量调到适中 打开播放器 播放电影 写成类和方法的调用大概是以下的样子: # 打开爆米花机,开始爆米花 poper.on() poper.pop() # 灯光调暗 lights.dim(10) # 放下屏幕 screen.down() # 打开投影仪,设置为宽屏模式 projector.on() projector.setInput(dvd) projector.wideScreenMode() # 打开功放 设置为DVD 调整成环绕立体声模式,音量调到5 amp.on() amp.setDvd(dvd) amp.setSurroundSound() amp.setVolume(5) # 打开dvd 播放器 dvd.on() dvd.play(movie) 可以看到代码中涉及到6个不同的类,而且电影看完后还需要回退,一切都要再反着重来一遍。怎样简化一下操作呢? 现在,外观模式就可以大展身手了。 使用外观模式,可以通过实现一个提供更合理的接口的外观类,将子系统变得更容易使用。当然,原来的接口还在。 解决方法 先来看一下外观模式如何运作 这里为家庭影院系统创建了一个新的外观类HomeTheaterFacade,这个类暴露出来几个简单的方法,比如watchMovie,endMovie。 这个外观类将家庭影院的多个组件看作一个子系统,通过调用这个子系统来实现watchMovie方法。 外观只提供了一个更直接的操作方式,并没有将原来的子系统隔离,子系统的功能还可以使用 注意: 可以有多个外观 外观提供简化的接口,但不隔离子系统 外观将实现从子系统中解耦,比如:现在有个子系统的组件需要升级换代,只需要把外观代码做相应的修改就可以实现 外观和适配器都可以包装多个类,但是外观的意图时简化接口的调用,而适配器的意图是将接口转换成不同的接口。 示例 class HomeTheaterFacade(object): #先声明需要用的子组件 amp = Amplifier() tuner = Tuner() dvd = DvdPlayer() cd = CdPlayer() projector = Projector() lights = TheaterLights() screen = Screen() popper = PopcornPopper() def watchMovie(self, movie): # watchMovie 将之前需要手动处理的任务批量处理 print("Get ready to watch a movie...") # 打开爆米花机,开始爆米花 self.poper.on() self.poper.pop() # 灯光调暗 self.lights.dim(10) # 放下屏幕 self.screen.down() # 打开投影仪,设置为宽屏模式 self.projector.on() self.projector.setInput(dvd) self.projector.wideScreenMode() # 打开功放 设置为DVD 调整成环绕立体声模式,音量调到5 self.amp.on() self.amp.setDvd(dvd) self.amp.setSurroundSound() self.amp.setVolume(5) # 打开dvd 播放器 self.dvd.on() self.dvd.play(movie) def endMovie(self): # endMovie 负责关闭一切,由子系统中的组件完成 print("Shutting movie theater down...") self.popper.off() self.lights.on() self.screen.up() self.projector.off() self.amp.off() self.dvd.stop() self.dvd.eject() self.dvd.off() 代码使用 def main(): home_theater = HomeTheaterFacade() # 实例化外观 home_theater.watchMovice() # 使用简化方法开启 关闭电影ß home_theater.endMovice() 定义 定义:外观模式提供了一个统一的接口,用来访问子系统中的一群接口。外观定义了一个高层接口,让子系统更容易使用。 ...

2018-11-25 · 1 min · 196 words

python设计模式-适配器模式

问题:假设有一个软件系统,你希望它能在不改变现有代码的前提下和一个新的厂商类库搭配使用,但是这个新厂商所设计出来的接口不同于旧厂商的接口 这个问题和下图的问题类似 美国标准的插头🔌无法在欧洲标准的插座上使用,通常的做法是什么呢? 添加一个插头适配器,适配器的作用是将欧式插头转换成美式插座,以便于让美式插头可以使用。 解决方案 所以,面对一个有全新接口的类库而又不能改变现有代码时,最先想到的做法是,在这两个系统之间添加一个适配器。 简单的例子 有一个系统,需要一个鸭子🦆对象,但是现在只有一个火鸡🦃对象。鸭子和火鸡对象的功能简单描述如下: # 鸭子的简单描述 class Duck: def quack(self): # 会呱呱叫 print("Quack") def fly(self): # 飞的能力 print("I'm flying") # 火鸡的简单描述 class Turkey: def gobble(self): # 不会呱呱叫,只会咯咯叫 print("Gobble gobble") def fly(self): # 飞的能力 但是飞不远 print("I'm flying a short distance") 因为现在没有鸭子对象,只能那火鸡对象冒充。由于鸭子对象和火鸡对象功能不同,不能直接拿来用,现在就需要使用适配器来完成这个功能: class TurkeyAdapter(Duck): turkey = Turkey() # 这里实际使用的是火鸡对象 # 实现鸭子对象拥有的quack方法 def quack(self): self.turkey.gobble() def fly(self): # 假设火鸡比鸭子飞的短,为了模拟鸭子的动作,多飞几次 for i in range(5): turkey.fly() 接下来调用就可以像使用鸭子对象一样使用火鸡适配后的对象。 ...

2018-11-03 · 1 min · 112 words

Solidity 简易教程0x001

Solidity是以太坊的主要编程语言,它是一种静态类型的 JavaScript-esque 语言,是面向合约的、为实现智能合约而创建的高级编程语言,设计的目的是能在以太坊虚拟机(EVM)上运行。 本文基于CryptoZombies,教程地址为:https://cryptozombies.io/zh/lesson/2 地址(address) 以太坊区块链由 account (账户)组成,你可以把它想象成银行账户。一个帐户的余额是以太 (在以太坊区块链上使用的币种),你可以和其他帐户之间支付和接受以太币,就像你的银行帐户可以电汇资金到其他银行帐户一样。 每个帐户都有一个“地址”,你可以把它想象成银行账号。这是账户唯一的标识符,它看起来长这样: 0x0cE446255506E92DF41614C46F1d6df9Cc969183 这是 CryptoZombies 团队的地址,为了表示支持CryptoZombies,可以赞赏一些以太币! address:地址类型存储一个 20 字节的值(以太坊地址的大小)。 地址类型也有成员变量,并作为所有合约的基础。 address 类型是一个160位的值,且不允许任何算数操作。这种类型适合存储合约地址或外部人员的密钥对。 映射(mapping) Mappings 和哈希表类似,它会执行虚拟初始化,以使所有可能存在的键都映射到一个字节表示为全零的值。 映射是这样定义的: //对于金融应用程序,将用户的余额保存在一个 uint类型的变量中: mapping (address => uint) public accountBalance; //或者可以用来通过userId 存储/查找的用户名 mapping (uint => string) userIdToName; 映射本质上是存储和查找数据所用的键-值对。在第一个例子中,键是一个 address,值是一个 uint,在第二个例子中,键是一个uint,值是一个 string。 映射类型在声明时的形式为 mapping(_KeyType => _ValueType)。 其中 _KeyType 可以是除了映射、变长数组、合约、枚举以及结构体以外的几乎所有类型。 _ValueType 可以是包括映射类型在内的任何类型。 对映射的取值操作如下: userIdToName[12] // 如果键12 不在 映射中,得到的结果是0 映射中,实际上并不存储 key,而是存储它的 keccak256 哈希值,从而便于查询实际的值。所以映射是没有长度的,也没有 key 的集合或 value 的集合的概念。,你不能像操作python字典那应该获取到当前 Mappings 的所有键或者值。 特殊变量 在 Solidity 中,在全局命名空间中已经存在了(预设了)一些特殊的变量和函数,他们主要用来提供关于区块链的信息或一些通用的工具函数。 msg.sender msg.sender指的是当前调用者(或智能合约)的 address。 ...

2018-10-22 · 7 min · 1349 words

SQLAlchemy in 空列表问题分析

SQLAlchemy in 空列表问题 问题场景 有model Account,SQLAlchemy 查询语句如下: query = Account.query.filter(Account.id.in_(account_ids)).order_by(Account.date_created.desc()) 这里 account_ids 如果为空,执行查询会有如下警告: /usr/local/lib/python2.7/site-packages/sqlalchemy/sql/default_comparator.py:35: SAWarning: The IN-predicate on "account.id" was invoked with an empty sequence. This results in a contradiction, which nonetheless can be expensive to evaluate. Consider alternative strategies for improved performance. return o[0](self, self.expr, op, *(other + o[1:]), **kwargs) 这里的意思是使用一个空的列表会花费较长的时间,需要优化以提高性能。 为什么会有这个提示呢?一个空列表为什么会影响性能呢? 首先打印 query 可得到如下 sql 语句: SELECT * // 字段使用 “*” 代替 FROM account WHERE account.id != account.id ORDER BY account.date_created DESC 会发现生成的语句中过滤条件是 WHERE account.id != account.id,使用 PostgreSQL Explain ANALYZE 命令, ...

2018-10-04 · 3 min · 542 words

Solidity 简易教程

Solidity是以太坊的主要编程语言,它是一种静态类型的 JavaScript-esque 语言,是面向合约的、为实现智能合约而创建的高级编程语言,设计的目的是能在以太坊虚拟机(EVM)上运行。 本文基于CryptoZombies,教程地址为:https://cryptozombies.io/zh/ 合约 Solidity 的代码都包裹在合约里面. 一份合约就是以太应币应用的基本模块, 所有的变量和函数都属于一份合约, 它是你所有应用的起点. 一份名为 HelloWorld 的空合约如下: contract HelloWorld { } hello world 首先看一个简单的智能合约。 pragma solidity ^0.4.0; contract SimpleStorage { uint storedData; // 声明一个类型为 uint (256位无符号整数)的状态变量,叫做 storedData function set(uint x) public { storedData = x; // 状态变量可以直接访问,不需要使用 this. 或者 self. 这样的前缀 } function get() public view returns (uint) { return storedData; } } 所有的 Solidity 源码都必须冠以 “version pragma” — 标明 Solidity 编译器的版本. 以避免将来新的编译器可能破坏你的代码。 例如: pragma solidity ^0.4.0; (当前 Solidity 的最新版本是 0.4.0). 关键字 pragma 的含义是,一般来说,pragmas(编译指令)是告知编译器如何处理源代码的指令的(例如, pragma once )。 ...

2018-09-04 · 4 min · 719 words

垃圾回收算法|引用计数法

本文是《垃圾回收的算法与实现》读书笔记 上一篇为《GC 标记-清除算法》 引用计数算法 给对象中添加一个引用计数器,每当有一个地方引用它时,计数器的值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。这也就是需要回收的对象。 引用计数算法是对象记录自己被多少程序引用,引用计数为零的对象将被清除。 计数器表示的是有多少程序引用了这个对象(被引用数)。计数器是无符号整数。 计数器的增减 引用计数法没有明确启动 GC 的语句,它与程序的执行密切相关,在程序的处理过程中通过增减计数器的值来进行内存管理。 new_obj() 函数 与GC标记-清除算法相同,程序在生成新对象的时候会调用 new_obj()函数。 func new_obj(size){ obj = pickup_chunk(size, $free_list) if(obj == NULL) allocation_fail() else obj.ref_cnt = 1 // 新对象第一只被分配是引用数为1 return obj } 这里 pickup_chunk()函数的用法与GC标记-清除算法中的用法大致相同。不同的是这里返回 NULL 时,分配就失败了。这里 ref_cnt 域代表的是 obj 的计数器。 在引用计数算法中,除了连接到空闲链表的对象,其他对象都是活跃对象。所以如果 pickup_chunk()返回 NULL,堆中也就没有其它大小合适的块了。 update_ptr() 函数 update_ptr() 函数用于更新指针 ptr,使其指向对象 obj,同时进行计数器值的增减。 func update_ptr(ptr, obj){ inc_ref_cnt(obj) // obj 引用计数+1 dec_ref_cnt(*ptr) // ptr之前指向的对象(*ptr)的引用计数-1 *ptr = obj } 这里 update_ptr 为什么需要先调用 inc_ref_cnt,再调用dec_ref_cnt呢? 是因为有可能 *ptr和 obj 可能是同一个对象,如果先调用dec_ref_cnt可能会误伤。 **inc_ref_cnt()**函数 这里inc_ref_cnt函数只对对象 obj 引用计数+1 func inc_ref_cnt(obj){ obj.ref_cnt++ } dec_ref_cnt() 函数 ...

2018-08-12 · 3 min · 429 words

使用github+travis将Python包部署到Pypi

我在 github 托管 Python 代码,然后将包发布到 Pypi,通常的操作步骤是,更新完代码将提交到 github ,然后手动将包更新到 pypi,这样比较繁琐,就想到了使用github+travis-ci 构建一个自动部署环境。 注册 pypi 访问https://pypi.org 点击Register注册账号,记住自己的用户名密码。 创建 setup.py 文件 setup.py 文件放置于包的根目录,示例内容如下: #!/usr/bin/env python from setuptools import setup, find_packages with open("README.md", "r") as fh: long_description = fh.read() with open('requirements.txt') as f: requirements = [l for l in f.read().splitlines() if l] setup(name="python-weixin", # 项目名 version="0.3.2", # 版本号 description="Python Weixin API client support wechat-app", #简介 long_description=long_description, # 长简介 这里使用的 readme 内容 long_description_content_type="text/markdown", license="BSD", # 授权 install_requires=requirements, # 依赖 author="gusibi", # 作者 author_email="[email protected]", # 邮箱 url="https://github.com/gusibi/python-weixin", # 地址 download_url="https://github.com/gusibi/python-weixin/archive/master.zip", packages=find_packages(), keywords=["python-weixin", "weixin", "wechat", "sdk", "weapp", "wxapp"], zip_safe=True) 以上特别需要注意的是 packages参数,用来申明你的包里面要包含的目录,这里使用setuptools自动决定要包含哪些包。 ...

2018-07-23 · 2 min · 308 words

垃圾回收算法|GC标记-清除算法

本文是《垃圾回收的算法与实现》读书笔记 什么是GC标记-清除算法(Mark Sweep GC) GC 标记-清除算法由标记阶段和清除阶段构成。在标记阶段会把所有的活动对象都做上标记,然后在清除阶段会把没有标记的对象,也就是非活动对象回收。 名词解释: 在 GC 的世界里对象指的是通过应用程序利用的数据的集合。是 GC 的基本单位。一般由头(header)和域(field)构成。 活动对象:能通过引用程序引用的对象就被称为活动对象。(可以直接或间接从全局变量空间中引出的对象) 非活动对象:不能通过程序引用的对象呗称为非活动对象。(这就是被清除的目标) 标记-清除算法的伪代码如下所示: func mark_sweep(){ mark_phase() // 标记阶段 sweep_phase() // 清除阶段 } 标记阶段 标记阶段就是遍历对象并标记的处理过程。 标记阶段伪代码如下: func mark_phase(){ for (r : $roots) // 在标记阶段,会给所有的活动对象打上标记 mark(*r) } func mark(){ if (obj.mark == False) obj.mark = True // 先标记找出的活动对象 for (child: children(obj)) // 然后递归的标记通过指针数组能访问到的对象 mark(*child) } 这里 $root 是指针对象的起点,通过$root 可以遍历全部活动对象。 下图是标记前和标记后内存中堆的状态 清除阶段 在清除阶段,collector 会遍历整个堆,回收没有打上标记的对象(垃圾),使其能再次利用。 sweep_phase() 函数伪代码实现如下: func sweep_phase(){ sweeping = $heap_start // 首先将堆的首地址赋值给 sweeping while(sweeping < $head_end){ if(sweeping.mark == TRUE) // 如果是标记状态就设为 FALSE,如果是活动对象,还会在标记阶段被标记为 TRUE sweeping.mark == FALSE else: sweeping.next = $free_list // 将非活动对象 拼接到 $free_list 头部位置 $free_list = sweeping sweeping += sweeping.size } } size 域指的是存储对象大小的域,在对象头中事先定义。 ...

2018-07-21 · 2 min · 348 words

Python 生成便签图片

最近有文字转图片的需求,但是不太想下载 APP,就使用 Python Pillow 实现了一个,效果如下: PIL 提供了 PIL.ImageDraw.ImageDraw.text 方法,可以方便的把文字写到图片上,简单示例如下: from PIL import Image, ImageDraw, ImageFont # get an image base = Image.open('Pillow/Tests/images/hopper.png').convert('RGBA') # make a blank image for the text, initialized to transparent text color txt = Image.new('RGBA', base.size, (255,255,255,0)) # get a font fnt = ImageFont.truetype('Pillow/Tests/fonts/FreeMono.ttf', 40) # get a drawing context d = ImageDraw.Draw(txt) # draw text, half opacity d.text((10,10), "Hello", font=fnt, fill=(255,255,255,128)) # draw text, full opacity d.text((10,60), "World", font=fnt, fill=(255,255,255,255)) out = Image.alpha_composite(base, txt) out.show() 为什么要计算文字的宽高呢?把文字直接写到背景图不可以么? ...

2018-07-08 · 3 min · 469 words