CI_Knight

且行善举,莫问前程。

0%

with 关键字

在 python 关于 with 关键字,中我们一般会这样写。

1
2
with open('file.txt', 'w') as f:
f.write('hello world!')

在这段代码中,无论with中的代码块在执行的过程中发生任何情况,文件最终都会被关闭。如果代码块在执行的过程中发生了一个异常,那么在这个异常被抛出前,程序会先将被打开的文件关闭。

with 关键字一般执行过程

一段基本的 with 表达式其结构是这样的:

1
2
with EXPR as VAR:
BLOCK

其中:EXPR可以是任意表达式;as VAR是可选的。其一般的执行过程是这样的:

  • 计算EXPR,并获取一个上下文管理器。
  • 上下文管理器的__exit()__方法被保存起来用于之后的调用。
  • 调用上下文管理器的__enter()__方法。
  • 如果with表达式包含as VAR,那么EXPR的返回值被赋值给VAR。
  • 执行BLOCK中的表达式。
  • 调用上下文管理器的__exit()__方法。如果BLOCK的执行过程中发生了一个异常导

致程序退出,那么异常的type、value和traceback(即sys.exc_info()的返回值)将作为参数传递给__exit()__方法。否则,将传递三个None。

将这个过程用代码表示,是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
mgr = (EXPR)
exit = type(mgr).__exit__ # 这里没有执行
value = type(mgr).__enter__(mgr)
exc = True

try:
try:
VAR = value # 如果有 as VAR
BLOCK
except:
exc = False
if not exit(mgr, *sys.exc_info()):
raise
finally:
if exc:
exit(mgr, None, None, None)

这个过程有几个细节:

  • 如果上下文管理器中没有__enter()__或者__exit()__中的任意一个方法,那么解释器会抛出一个AttributeError。
  • 在BLOCK中发生异常后,如果__exit()__方法返回一个可被看成是True的值,那么这个异常就不会被抛出,后面的代码会继续执行。

实现上下文管理器类

实现一个类

第一种方法是实现一个类,其含有一个实例属性db和上下文管理器所需要的方法__enter()__和__exit()__。

1
2
3
4
5
6
7
8
9
10
11
12
class transaction(object):
def __init__(self, db):
self.db = db

def __enter__(self):
self.db.begin()

def __exit__(self, type, value, traceback):
if type is None:
db.commit()
else:
db.rollback()

使用生成器装饰器

在 Python 的标准库中,有一个装饰器可以通过生成器获取上下文管理器。使用生成器装饰器的实现过程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
from contextlib import contextmanager

@contextmanager
def transaction(db):
db.begin()

try:
yield db
except:
db.rollback()
raise
else:
db.commit()

contextmanager 实现

详解

  • Python 解释器识别到yield关键字后,def会创建一个生成器函数替代常规的函数(在类定义之外我喜欢用函数代替方法)。
  • 装饰器contextmanager被调用并返回一个帮助函数,这个帮助函数在被调用后会生成一个GeneratorContextManager实例。最终with表达式中的EXPR调用的是由contentmanager装饰器返回的帮助函数。
  • with表达式调用transaction(db),实际上是调用帮助函数。帮助函数调用生成器函数,生成器函数创建一个生成器。
  • 帮助函数将这个生成器传递给GeneratorContextManager,并创建一个GeneratorContextManager的实例对象作为上下文管理器。
  • with表达式调用实例对象的上下文管理器的__enter()__方法。
  • __enter()__方法中会调用这个生成器的next()方法。这时候,生成器方法会执行到yield db处停止,并将db作为next()的返回值。如果有as VAR,那么它将会被赋值给VAR。
  • with中的BLOCK被执行。
  • BLOCK执行结束后,调用上下文管理器的__exit()__方法。__exit()__方法会再次调用生成器的next()方法。如果发生StopIteration异常,则pass。
  • 如果没有发生异常生成器方法将会执行db.commit(),否则会执行db.rollback()。

代码实现

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
30
31
32
def contextmanager(func):
def helper(*args, **kwargs):
return GeneratorContextManager(func(*args, **kwargs))
return helper

class GeneratorContextManager(object):
def __init__(self, gen):
self.gen = gen

def __enter__(self):
try:
return self.gen.next()
except StopIteration:
raise RuntimeError("generator didn't yield")

def __exit__(self, type, value, traceback):
if type is None:
try:
self.gen.next()
except StopIteration:
pass
else:
raise RuntimeError("generator didn't stop")
else:
try:
self.gen.throw(type, value, traceback)
raise RuntimeError("generator didn't stop after throw()")
except StopIteration:
return True
except:
if sys.exc_info()[1] is not value:
raise

一些例子

锁机制

1
2
3
4
5
6
7
@contextmanager
def locked(lock):
lock.acquired()
try:
yield
finally:
lock.release()

标准输出重定向

1
2
3
4
5
6
7
8
9
10
11
12
@contextmanager
def stdout_redirect(new_stdout):
old_stdout = sys.stdout
sys.stdout = new_stdout
try:
yield
finally:
sys.stdout = old_stdout

with open("file.txt", "w") as f:
with stdout_redirect(f):
print "hello world"

前言

修修改改总算把第三方登录,遇到很多坑,很多在网上也搜不到。涉及有QQ、微信、微博,我想读完这文章能少走很多弯路,还有注意文章的时间。那么从坑最多的来讲。

当有多个网站需求但是只有一个 appid 的时候可以考虑下 proxy 的实现方式了。

关于测试

如果说是为公司开发,公司没有提供appid,那么就可以自己在平台注册一个开发者,这样拿到开发账号就可以直接测试了。

如果遇到问题,移动开发可以看是否提供sdk源码,如果在 github 上,可以在 issue中看到很多别人遇到的问题,这样也能少走很多弯路,比如最近 ios10,开发者们都已早早更新,微博出现了 https 错误的问题,在 issue 里就找到了答案。

QQ 登录

QQ 坑是最多的,QQ 分为互联和开放平台,开放平台和互联管理的各不同,开放平台管的是空间微博朋友网授权以及移动授权,并不支持 PC 上 Web 授权登录,想要申请 Web授权就需要到互联申请,但是千万不要将这两个平台的账号管理起来,不然就需要全部审核成功后才能上线。

当然授权是为了拿到用户信息,还有就是openid,也是就是用户的唯一标识,但是互联和开放平台拿到的openid 是不同的,这点要做全平台登录的一定要注意了。后来咨询 QQ 那边,互联改版后支持移动和Web 两种授权方式了,所以可以直接在互联申请了。也是很坑,这是我在做完之后遇到问题才改版的。结果是我们只有移动端的注册。

QQ 的文档很老旧,接口返回还有 jsonp 和 query 的形式。要自己写解析的。

微信登录

微信比较好,但是也分为开放平台和公众平台,公众平台绑定开放平台后能拿到 unionid这点也是要注意的。所以,在写登录的时候就要看需不需要这个 unionid 了,不然拿到的就是 openid。

登录分两种,一种是扫码登录,这个是在开放平台的。还有种是在微信客户端内登录,这个是公众平台的,所以看文档注意自己看的是哪个平台的文档。

微信公众平台提供了测试工具,还算比较方便的,但是有很多隐患,比如在真机上出现的问题会在测试工具上无法复现,也许是被测试工具吞掉了抛错。

另一点是登录请求的 url 的 query 一定要是排序的,不然也会抛错。

微信的 get_user_info 拿到的信息会有问题,在存储的时候可能会是乱码,因为它给你的是一个 utf8的 encode 位串,这点最好注意一下。

记住一定要把微信的文档全都复印一遍,然后烧掉它,这只是一种仪式感。

微博登录

应该是最好的了,没有太多的问题,认真看文档。

后记

一个人做完了微信、微博、QQ第三方登录,这也是我第一次做,经验不多犯了很多错,写这篇文章分享一下,让别人能少走一些弯路。还有设计 API 的时候一定要求同,保证接口一致,只是微信移动端 SDK 里出的一些问题。

如果有其他问题都可以在留言中提出。

RaspberryPi

树莓派是我大学的时候接触的,刚刚买的时候就把它当 linux 玩了,用的但是最熟悉的 debian,虽然说最早接触的系统是 redhat 系列的,还记得大学在 linux 社团,学完命令后学长就让我们开始去编译 Lump,还有些人都开始搞 gentoo 了,也做好了镜像,为了方便还是选择了 debian。

说树莓派是嵌入式方面的也不为过,因为有很强大的 GPIO,但对于嵌入来讲又过于庞大。总的来说 Raspberry 的用处是相当广的,目前我的树莓派充当着我的网络数据中心,也跑着很多的服务,还有一些 传感器,比如温湿度等等。

可玩性最高的其实还是 GPIO 了。

Raspberry-pi GPIO

我的是 raspberry-pi 2,总共40个针脚,每个针脚有不同的作用,具体可以参考下面的网站。

Raspberry Pinout

准备什么当然最好是买个扩展板还有面包版和N多子母、子子头的插线,最好电阻也买些(我用的是一个可变电阻,通过电路控制电压)对,当然还有电压表。

买一些传感器,还有放着传感器的小盒子,温度传感器的话推荐用DHT22,比DHT11高了0.1的精度值,价格上也更贵一些。使用的时候一定要加上电阻,不要烧坏传感器。还有来一些不同颜色的发光二极管,可以练习跑马灯什么的。

这些东西某宝上都能买到,最重要的是你得有个树莓派。目前最新的是 Raspberry pi 3

GPIO 编程

树莓派支持的编程语言很多,C族编程语言,Python(效率也是最高的,也很简单),还有Shell等等。我主要使用 Python,玩 GPIO 的话用的是 C,用的是 Wiring Pi 驱动。python 话应该有很多,pip就可以搞定了,shell 应该需要重新配置下树莓派的 /dev 支持吧(shell的可以令查)

Wiring pi

针脚封装成8个从 0-7 一一对应 Raspberry pi 的GPIO。

对应关系如下图

Wiring Pi 下载

我的 Github 中有 DHT11 和 DHT22 的实现,可以作为参考。

自己瞎搞

YouCompleteMe 报错

这是使用 vim 编辑 python 文件时报的错,从昨天开始就出现这个问题,python环境绝对是没问题的,我一直使用的是 python2.7 的 virtualenv 的环境,又重新编译 ycm 依然没有解决问题,退出 virtualenv 发现奇迹的好了。

这就可以断定 virtualenv 和 YouCompleteMe 是有冲突的,定位报错的位置,看的一脸懵逼。python标准库都不可以使用。google 一下错误,没有任何可用结果。

以我的能力是解决不了这个问题的,面对的这崩溃的 vim 束手无策,重新安装 python2.7 和 vim这种笨办法都试过了。一筹莫展之际想到可以在 github 上找找。

那么问题来了,是该看 YCM 还是 virtualenv 呢,我在 YCM 找到了问题的所在。

很可惜这个 issue 是在12小时之前打开的,也没有得到具体的解决办法,有 PR,也没有任何回应。

解决后更新。这是我凑巧在出问题的时候,更新 vim 的插件。

issue 地址

pyflakes-vim Error: Invalid action: ‘ ‘

第二天 YCM 的pull request 合并,更新重新编译修复完成,第二天又出现了新的错误,Invalidaction,issue 中没有看到,晚上回家后才见到该 Issue,已经修复。

summarize

还不能精通 vim 的时候,不随便乱折腾了。

Let’s Encrypt

众所周知国内域名不备案是不能随意解析的,放到国外又会影响服务响应速度,使用云服务又增加了开发成本,备案呢又很麻烦,在上海还需要居住证。但是国内备案只检测 http 的80端口,而 https 的443端口幸免于难。更是响应 Google I/O 2016 的号召,我们都应该使用 https来加密我们的网站,这样也能避免 ISP 劫持,最重要的是被运营商劫持。

有很多家免费的 https 证书提供商,如StartSSL,有效期一年。国内有沃通,有效期也是一年。但是申请和维护都比较麻烦,毕竟是免费的。

但是我们也有其他的选择,Let’s Encrypt,有效期三个月,但是可以使用脚本或者安装 certbot 自动续期。

HTTPS

HTTPS 建立一条安全的网络通道,有效的防止被网络窃听以及中间人攻击

证书类型

HTTPS 证书分三类,浏览器会在地址栏给予不同证书不一样的展现。

  1. DV 域名验证证书
  2. OV 组织机构验证证书
  3. EV 增强的组织机构验证证书

每类证书在审核和验证方面要求严格程度不同,一般自签证书可以利用 Openssl生成公钥私钥,但是浏览器会有安全警告,12306就是自签证书。所以我们要使用 Let‘s Encrypt来创建证书。

配置我们的 SSL 证书

lua-resty-auto-ssl

在 Ubuntu 上配置很容易,最好的方案是用 lua,nginx 对 lua的支持极好性能优越,很多负载均衡脚本都是由 lua 编写的。推荐使用 OpenResty 部署服务器,也可以使用我的lua-jit 版本的 nginx-lua 来部署,lua-jit 安装 lua-resty-auto-ssl 不是很方便,lua-jit 通过 LuaRocks 安装需要进行一些配置。

Let’s Encrypt Without Sudo

这是提供的 Python 方案。具体可以看letsencrypt-nosudo,文档中已经详细的指出使用方法和 Openssl 的自签方案。

cretbot

自动化创建证书,自动续期。

其他

如果不懂 server 配置文件怎么写的可以使用 ssl-config-generator,是 Mozilla 搞出来的配置文件生成工具,着实方便。

Virtualenv

Virtualenv 之前也说过,Python 的虚拟环境管理软件,习惯使用这个原因是因为可以管理不同开发项目的 python 依赖。还有一个功能就是可以管理不同的 Python 版本。

1
virtualenv -p /usr/bin/python2.6 <path/to/new/virtualenv/>

这样可以创建 Python2 和 Python3 的两个环境,日常也在虚拟环境中,不会弄脏系统版本。

如果做开发建议还是远程开发,vagrant 或者 docker选择适合自己的一个。

VirtualenvWrapper

virtualenv 的管理工具,将你创建的 venv存放在同一目录,可以随意切换,使用简单方便。如果环境不是太多,创建脚本后,可以直接将版本配置在 bash 中。