字符串在 Python 2.x 和 3.x 下的适配

RQ 提交了一个 Pull Request 来解决 issue#437 .

最开始的提交 我做了以下的工作:

  • 找到问题所在
  • 写新的单元测试
  • 将相关串做 decode 操作
  • 跑单元测试通过, 提交 Pull Request

然后….就遇到问题了, Travis 跑完发现 Python2.x 下都没问题, 但是 Python3.x 都跑不过, 原因很简单, python3.x 的时候已经不区分 string 和 unicode, 统一采用 unicode, 因此也取消了 decode 方法. 那么我们如何来同时适配 2.x 和 3.x 版本呢. 我想过很多方法, 但是都觉得不够优雅, 后来还是在 RQ 这个库本身里找到了答案.

在 RQ 中, 对字符串的获取都会经过一个 as_text 的函数处理, 该函数位于 compat/__init__.py, 就是为了同时适配 2.x 和 3.x 版本, 函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
if not PY2:
# Python 3.x and up
text_type = str
string_types = (str,)

def as_text(v):
if v is None:
return None
elif isinstance(v, bytes):
return v.decode('utf-8')
elif isinstance(v, str):
return v
else:
raise ValueError('Unknown type %r' % type(v))

def decode_redis_hash(h):
return dict((as_text(k), h[k]) for k in h)
else:
# Python 2.x
text_type = unicode
string_types = (str, unicode)

def as_text(v):
if v is None:
return None
return v.decode('utf-8')

def decode_redis_hash(h):
return h

首先通过 six 这个库来判断 Python 版本, 然后根据版本的不同, 声明 as_text 方法的具体实现, 这样在整个库在处理字符串时, 不需要考虑版本差异, 直接调用 as_text 进行处理即可.

用了pyenv-virtualenv, 天黑都不怕

之前就有听大妈推荐过 pyenv. 最近给一个项目这个库提交 Pull Request, 但 Python3.X 的单元测试没有跑过, 而我的机器上没有 Python3.X, 也不想把现有的 Python2.7 替换掉, 所以就用起了这个库.

简单的说, pyenv 是一个Python管理工具, 这个是和我们常用的 virtualenv 有所不通, 前者是对 Python 的版本进行管理, 实现不同版本的切换和使用. 后者测试创建一个虚拟环境, 与系统环境以及其他 Python 环境隔离, 避免干扰.

安装方法我就不做赘述了, pyenv readme 已经写的特别详尽

#pyenv使用方法
简单的说一下使用方法

##安装不同版本的 Python

1
2
pyenv install <version> #安装特定版本的 Python
pyenv install 3.3.0 #安装 Python 3.3.0

当我的系统 Python 版本是 2.7, 但是有个 叫做 py3-project 需要用 Python3 来运行的时候, 只需要这样做:

1
2
3
4
cd py3-project         #进入项目目录
pyenv local 3.3.0 #将当前目录下的Python环境切换为3.3.0
pyenv version #运行显示通过pyenv设置之后的python版本, 得到结果是3.3.0
python --version #查看Python版本, 得到结果也是3.3.0

此时就可以通过 python3.3 来运行项目了, 才这个项目之外的目录运行 Python, 你会发现仍然是系统版本. 通过pyenv可以给不同的目录设置不同的 Python 版本, 还可以通过 pyenv global 这个命令切换整个全局的 Python版本. 赞爆了是不是.

#告别virtualenv
接下来, 再介绍一个工具, 配合pyenv, 让我告别了用了很久了virtualenv.这个工具叫做 pyenv-virtualenv, 安装方法依然跳过, 至于使用, 你只需要记住三条命令:

1
2
3
pyenv virtualenv 3.3.0 env    #创建一个 Python 版本为 3.3.0 的环境, 环境叫做 env
pyenv activate env_name #激活 env 这个环境, 此时 Python 版本自动变为 3.3.0, 且是独立环境
pyenv deactivate #离开已经激活的环境

嗯, 写完这篇继续去修复那段 Python3.X 下通不过单元测试的程序.

再见了, virtualenv.

go语言学习(一)

##包

###包的导入方法1:

1
2
3
4
import (
"fmt"
"math"
)

###包的导入方法2:

1
2
import "fmt"
import "math"

作为 Pytonista, 更喜欢第 2 种方式 更加简洁.

##包的变量导出
Go 语言通过命名来确定该变量是否是可以导出到外部供别的包所使用.可以被导出的变量通过将变量名的首字母大写来标志确定.

##函数

###一个函数可以收 0 到任意多个参数. 接收的参数类型在参数名之后声明:

1
2
3
func add(x int, y int) int {
return x+y
}

如果一个函数接受的所有参数是同一个类型的时候, 那么可以不需要每一个参数都标明类型, 只需要在最后一个参数上标明类型就可以.

1
2
3
func add(x, y int){ #当多个参数是同一类型时, 可以省略只声明一次.
return x+y
}

###一个函数可以返回多个值

1
2
3
func swap(x, y int) int, int{
return y, x
}

###go也可以对返回变量命名

1
2
3
4
func split(sum int) (x, y int){
x = sum * 4 / 9
y = sum - x
}

##变量

###变量声明
go 通过 var 关键字来声明变量, 和函数参数声明一致, 变量类型在变量名之后声明:

1
2
var i int
var c, python, java bool #三个值都为bool类型

###变量初始化
变量可以在声明的时候立刻初始化:

1
2
var i, j int = 1, 2
var c, python, java = true, false, "no!"

###短变量声明
短变量声明是一个特殊的使用方法, 有以下特点:

  • 只能在函数内部使用
  • 不需要var关键字和类型
  • 类型通过赋值时隐式声明
    1
    2
    3
    4
    5
    func main(){
    var i, j int = 1, 2
    k := 3
    c, python, java = true, false, "No!"
    }

###变量类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
bool

string

int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr

byte // alias for uint8

rune // alias for int32
// represents a Unicode code point

float32 float64

complex64 complex128

###常量
常量只能使用const关键字生成

1
const Pi = 3.14

postgres-snippet

创建数据库

1
CREATE DATABASE DATABASE_NAEM;

删除数据库

1
DROP DATABASE DATABASE_NAME;

查看所有数据库

1
2
\list #或者\l
\l+ #显示更详尽的数据库信息

查看所有用户

1
2
\du
\du+ #显示更详尽的用户信息

创建用户

1
CREATE USER USERNAME;

删除用户

1
DROP USER USERNAME;

授予用户操作某个DATABASE的所有权限

1
GRANT ALL ON DATABASE DATABASE_NAME TO USERNAME;

Bye

现在在飞机上, 广州之行在真正的意义上来说, 也是告一段落. 昨晚宝爷给我发微信问我怎么说走就走了, 连一起吃顿饭的机会都没有, 让我想起<后会无期>中的那句话,每一眼都有可能是最后一眼.收拾好东西沉甸甸的行李, 打车来到白云机场, 我尝试看遍这里的每一个角落, 但是还是不现实:).

昨天下午, 去了一趟北京路. 有的时候就是这么奇妙, 上次和刘狗屎一起逛北京路, 吃了好多好吃的, 走了好多好多路. 不过这次是一个人, 我按照上次的路线, 一个人重复了一遍, 吃了好吃的牛杂, 一个人走过略显萧索的定制服装界, 转过街角, 路过匡威阿迪耐克店, 到了联合书店. 我在书店里来回的踱步, 给弟弟妹妹买了点有广州元素的小礼物, 给朋友买了点小礼物, 就到淘淘居和莲香楼买了一些手信.

在路上我看到有卖刮刮乐的销售点, 我想和你一起买彩票, 我发短信跟刘狗屎说. 我最终选择了路过, 彩票这种东西对我来说一直是属于智商税, 但是两个人一起的时候, 却格外开心. 真是奇妙的感觉.

刚刚经历了半个小时的乱流, 飞机上充斥着小朋友们哭闹的声音, 我刚刚带上耳机.

然后我就想起了 Swift, 我人生中接触最密切的一只小猫. 我会各种都弄他, 在他要走的那天, 我看到他就那样坐在那里, 背对着我, 望着门外的世界. “外面的世界是什么呢”, 我在朋友圈里给他配了这样的旁白, 但是我知道, 他想说的, 是”要离开了呢”. 现在只是偶尔, 能在 Ray 的朋友圈里看到 Swift, 不知道他还会不会想起我, 那个伸出手和他拍爪子的那个人.

相聚有时, 后会无期.

再见.

如果是程序员做产品经理

周六在咖啡馆 和老王 宅里奥还有ray呆到凌晨三点。

基本上算是做了一把产品经理。

 

最终让老王放弃了APP中原有的 滑动路径多选 这个功能。

 

这个功能原本是这个样子的:

联系人呈M*N二维平铺在手机屏幕上, 手机在屏幕上滑动, 滑动路径经过的人将会成为选中状态, 手指离开屏幕时视为多选完成, 进入下一个操作场景.

简单地说, 就像是屏幕解锁用的九宫格, 你划过的点视为选中.

 

老王想要保留这个功能, 但是严格来说这不是一个完整的功能.

是想一下, 如果你想选择第一行第一个人和最后一行最后一个人, 那么你将无法完成选择.

同样的, 因为滑动动作被占用, 所以不能像手机通讯录一样滑动遍历所有人, 只能依靠翻页.

 

所以你想同时选择下一页的人的时候, 这个功能不能满足此需求.

 

我想到了一个更好的方式, 但是, 在一小部分的功能上, 与国外同款APP相同.

当老王问我这不是和XXX一样么.

 

这个时候我问了一个问题, 是问老王的, 也是问我自己的.

如果有一种体验方式是自然美好的, 但是却让我们背上借(chao)鉴(xi)的嫌疑, 那么我们该怎么做.

是为用户负责, 使用更加良好的交互方式, 还是为什么别的负责.

 

到了两点左右的时候宅里奥出了一个demo 然后ray做了一个更加完善的设计,

这才算是告一段落.

 

 

和宅里奥回来的时候接近三点,

随便吃了点东西我在想,

今晚上算是客串了一下产品经理的职位, 在这个角色上, 我发现我是从来没有考虑程序实现的,

也突然意识到其实这才是产品经理的本分,

本就不需要考虑程序实现, 哪怕我是个程序员, 我明白身份所赋予的责任.

 

这样想, 还真是有趣, 当了一次逼死程序员的产品经理, 而我还是程序员…

记录一个不应该的失误

今天发现 fabu.io 的一个小bug,

当一个活动的参与人数上到一定量的时候, response慢的令人发指,

一开始觉得这是一个很奇怪的问题,

因为已经用了Paginator, 每次Select应该不会这么慢

看源码才发现一个严重失误.

源码是呈现这样的一种形式

foo_info_list = [foo.info for foo in Foo.objects.filter(attr=boo)]
paginator = Paginator(foo_info_list, page_size)

好吧 这样的话结果当然慢的令人发指。

因为Django的分页器接受的对象只要是Iterable就可以,

所以当时是这么用的, 没有考虑到数据变大时的性能问题。

 

后来将foo_queryset代替现有的foo_info_list传入Paginator, 问题得以解决。

 

 

 

 

PS:

今天给www-data配置shell的时候不小心在passwd中写错了shell的路径

直接无法以www-data身份操作

查了下发现有个很不错的命令叫做chsh 远门用于切换用户shell

 

PPS:为什么我这么晚还在写Blog, 是因为我本来要配置qa服务器, 结果连不上去

Mock小记(一)

最近在用Python写一个Client, 不过单元测试却是个问题, 我不能把自己的用户名密码放到单元测试里,而且这么做每次测试的时间太慢(受网络IO限制)
于是找到了这个, mock,
在Python 3.3里这个是作为标准库存在于unittest.mock这个包里, 不过我用的是2.7.x就用pip安装了一个

用mock来修饰函数:

>>> from mocks import MagicMock
>>> real = SomeClass()

>>> real.method = MagicMock(name=’method’)

>>> real.method(3, 4, 5, key=’value’)

通过一个MagicMock对象我们重写(修饰)了method方法.直接调用real.method()不会有任何动作触发, 也不会返回任何东西

使用mock来检查对象方法是否被调用过:

>>> **from mock import MagicMock >>> class ProductionClass(object): **def method(self):
self.something(1, 2, 3)
… **def something(self, a, b, c): … ** pass

>>> real = ProductionClass()
>>> real.something = MagicMock()
>>> real.method()
>>> real.something.assert_called_once_with(1, 2, 3) #检查是否只被调用过一次
>>> real.something.assert_called_with(1, 2, 3) #检查是否被调用过

mock另外一个常用用法是把mock对象作为参数传入函数:

>>> **class **ProductionClass(object):
… **def closer(self, something): something.close() >>> real = ProductionClass() >>> mock = Mock() >>> real.closer(mock) >>> **mock.close.assert_called_with()

在这里我们把mock作为参数传入用来检测something.close是否真的被调用(但是不执行任何动作)

写出好的 commit message

转自:http://ruby-china.org/topics/15737

 

为什么要关注提交信息

  • 加快 Reviewing Code 的过程
  • 帮助我们写好 release note
  • 5年后帮你快速想起某个分支,tag或者commit增加了什么功能,改变了哪些代码
  • 让其他开发者在运行 git blame的时候想跪谢
  • 总之一个好的提交信息,会帮助你提高项目的整体质量

基本要求

  • 第一行应该少于50个字。随后是一个空行 第一行题目也可以写成:Fix issue #8976
  • 喜欢Vim的哥们把下面这行代码加入.vimrc文件中,来检查拼写和自动折行

    autocmd Filetype gitcommit setlocal spell textwidth=72`

  • 永远不在git commit 上增加 -m &lt;msg&gt;或者 --message=&lt;msg&gt;参数,而单独提交信息。
    一个不好的例子 git commit -m "Fix login bug"
    一个推荐的commit message应该是这样的:

    `Redirect user to the requested page after login
    
    https://trello.com/path/to/relevant/card
    
    Users were being redirected to the home page after login, which is less
    useful than redirecting to the page they had originally requested before
    being redirected to the login form.
    
        * Store requested path in a session variable
        * Redirect to the stored location after successfully logging in the user`
  • 注释最好包含一个链接指向项目的issue/story/card。一个完整的链接比一个issue number更好
  • 提交信息中包含一个简短的故事,让别人更容易理解你的项目

    注释要回答以下问题

    为什么这次修改是必要的?
    要告诉Reviewers,你的提交包含什么改变。让他们更容易审核代码和忽略无关的改变。

    如何解决的问题?
    这可不是说技术细节。看下面的两个例子:

    Introduce a red/black tree to increase search speed 
      Remove &lt;troublesome gem X&gt;, which was causing &lt;specific description of issue introduced by gem&gt;

    如果你的修改特别明显,就可以忽略这个。

    这些变化可能影响那些地方?
    这是你最需要回答的问题。因为他会帮助你发现在某个branch或者commit中做了过多的改动。一个提交尽量只做1,2个变化。
    你的团队应该有一个自己的行为规范,规定每个commit和branch最多能含有多少个功能修改。

    小提示

  • 使用fix,add,change而不是fixed,added,changed

  • 永远不要忘了第二行是空行
  • Line break来分割提交信息,让它在某些软件里面更容易读

    例子

    `Fix bug where user can't signup.
    
    [Bug #2873942]
    
    Users were unable to register if they hadn't visited the plans
    and pricing page because we expected that tracking
    information to exist for the logs we create after a user
    signs up.  I fixed this by adding a check to the logger
    to ensure that if that information was not available
    we weren't trying to write it.`

    `Redirect user to the requested page after login

    https://trello.com/path/to/relevant/card

    Users were being redirected to the home page after login, which is less
    useful than redirecting to the page they had originally requested before
    being redirected to the login form.

    • Store requested path in a session variable
    • Redirect to the stored location after successfully logging in the user

参考文献

针对行为测试, 而非实现

本文改编自TotT中的一掌.你可以从这下载打印版本贴在你的办公室里:)

你可能的开源项目也会会有一个有很多用户使用的可靠的Calculator类:

public class Calculator {
public int add(int a, int b) {
return a + b;
}
}

你也会写个测试来确保它是正常的:

[java]
public void testAdd() {
assertEquals(3, calculator.add(2, 1));
assertEquals(2, calculator.add(2, 0));
assertEquals(1, calculator.add(2, -1));
}
[/java]

但是一个有特点的库往往会使用一些高级的编码方法来代替简单的加法运算符, 你可以把你的代码改成这样:

[java]
public class Calculator {
private AdderFactory adderFactory;
public Calculator(AdderFactor adderFactory) {
this.adderFactory = adderFactory;
}
public int add(int a, int b) {
Adder adder = adderFactory.createAdder();
ReturnValue returnValue = adder.compute(new Number(a),
new Number(b));
return returnValue.convertToInteger();
}
}
[/java]

改代码很容易,但是你的测试要怎么改变呢?
答案就是: 现有的测试不需要做任何改变. 因为你所改变的仅仅是代码的实现方式,面向用户的行为没有发生任何变化.在大多数哦情况下, 测试应该专注于测试你的代码的public API, 代码实现的细节不应该暴露在测试中.

测试与具体实现分离, 意味着测试不需要随着具体实现的修改而修改, 这更易于维护. 同时, 测试就如同代码样例, 帮助人们了解这个类的各种使用方法, 即使一个人不怎么熟悉这个库的具体实现也能够通过测试来了解如何使用.

确实有时候你需要测试具体的代码实现细节(比如想确认你的程序是从cache而不是数据库中读取数据),但这并不常见, 大多数情况的你的测试和具体实现是分离的.

需要注意的是如果具体实现发生变化, 测试的初始化往往会发生变化(比如你为类的构造函数的新加了一个参数, 那么测试实例化对象的时候也需要传入这个参数).如果面向用户的行为为发生变化, 那么测试就不需要改变