Month: 五月 2018

使用 supervisord 部署服务

在某一刻你会意识到你需要写一个长期运行的服务。如果有错误,这些脚本不应该停止运行,而且当系统重启的时候应该自动把这些脚本拉起来。

为了实现这一点,我们需要一些东西来监控脚本。这些工具在脚本挂掉的时候重启他们,并且在系统启动的时候拉起他们。

# 脚本

这样的工具应该是怎样的呢?我们安装的大多数东西都带了某种进程监控的机制。比如说Upstart和Systemd。这些工具被许多系统用力啊监控重要的进程。当我们安装php5-fpm,Apache 和nginx的时候,他们通常已经和系统集成好了,以便于他们不会默默挂掉。

然而,我们有时候需要一些简单点儿的解决方案。比如说我经常写一些 nodejs 的脚本来监控github上的某个动态并作相应的动作。node可以处理 http请求并且同时处理他们,也就是很适合作为一个一次性运行的服务。

这些小的脚本可能不值得使用 Upstart 或者 Systemd 这种重量级的东西。

下面是我们用来举例的脚本 我们把它放在 /srv/http.js 中

“`
var http = require(‘http’);

function serve(ip, port) {
http.createServer(function (req, res) {
res.writeHead(200, {‘Content-Type’: ‘text/plain’});
res.write(“\nSome Secrets:”);
res.write(“\n”+process.env.SECRET_PASSPHRASE);
res.write(“\n”+process.env.SECRET_TWO);
res.end(“\nThere’s no place like “+ip+”:”+port+”\n”);
}).listen(port, ip);
console.log(‘Server running at http://’+ip+’:’+port+’/’);
}

// Create a server listening on all networks
serve(‘0.0.0.0′, 9000);
“`

All this service does is take a web request and print out a message. It’s not useful in reality, but good for our purposes. We just want a service to run and monitor.

Note that the service prints out two environmental variables: “SECRET_PASSPHRASE” and “SECRET_TWO”. We’ll see how we can pass these into a watched process.

这个服务仅仅是接受一个 http 请求并打印一条消息。在现实中并没有什么卵用,但是用来演示很好。我们只是需要一个服务来运行和监控。

注意到这个服务打印两个变量 “SECRET_PASSPHRASE” 和 “SECRET_TWO”。我们将会演示如何把这个传递个被监控的进程。

# Supervisord

Supervisord is a simple and popular choice for process monitoring.

Supervisord 是一个使用很广也很简单的进程监控工具。

## 安装

建议使用系统的包管理器安装,虽然可能版本稍微老一点,但是。不过 supervisor 还不支持 python 3,所以必须使用 python 2 版本。

“`
brew install supervisor
“`

在 linux 上可以通过apt-get来安装 supervisor,同样的命令。

“`
apt-get install supervisor
service supervisor start
“`

## 配置

下面我们来配置一个 supervisor 服务。

打开 /etc/supervisor/supervisord.conf,我们可以看到最后一行:

“`
[include]
files = /etc/supervisor/conf.d/*.conf
“`

所以我们只需要把我们的配置文件放在 /etc/supervisor/conf.d 文件夹下就好了。

“`
[program:nodehook]
command=/usr/bin/node /srv/http.js
directory=/srv
autostart=true
autorestart=true
startretries=3
stderr_logfile=/var/log/webhook/nodehook.err.log
stdout_logfile=/var/log/webhook/nodehook.out.log
user=www-data
environment=SECRET_PASSPHRASE=’this is secret’,SECRET_TWO=’another secret’
“`

每个选项如下:

* [program:nodehook] 定义运行的服务的名字。
* command 启动被监控的服务的命令,如果你需要传递命令行参数的话,也放在这里
* directory 设定进程的运行目录
* autostart 是否需要在 supervisord 启动的时候自动拉起
* autorestart 是否在程序挂掉的时候自动重新拉起
* startretries 如果启动失败,重试多少次
* stderr_logfile 标准错误输出写入到哪个文件
* stdout_logfile 标准输出写入到哪个文件
* user 运行进程的用户
* environment 传递给进程的环境变量

需要注意的是,supervisor 不会自动创建日志文件夹,所以需要我们首先创建好。

“`
sudo mkdir /var/log/webhook
“`

supervisor 配置文件的搜索路径包括:

“`
/usr/local/etc/supervisord.conf
/usr/local/supervisord.conf
supervisord.conf # 当前目录
etc/supervisord.conf
/etc/supervisord.conf
/etc/supervisor/supervisord.conf
“`

## 控制进程

可以使用 supervisorctl 来控制对应的服务了。不过需要首先启动 supervisord 的daemon 才行。

“`
supervisorctl reread
supervisorctl update
“`

这样就可以启动刚刚定义的服务。supervisorctl 的其他功能可以查看帮助

# Web 界面

We can configure a web interface which comes with Supervisord. This lets us see a list of all processes being monitored, as well as take action on them (restarting, stopping, clearing logs and checking output).

supervisor 自带了一个 web 界面。这样我们就可以通过浏览器来管理进程了。

Inside of /etc/supervisord.conf, add this:

在 /etc/supervisord.conf 中添加:

“`
[inet_http_server]
port = 9001
username = user # Basic auth username
password = pass # Basic auth password
“`

If we access our server in a web browser at port 9001, we’ll see the web interface:

![](https://ws4.sinaimg.cn/large/006tNc79ly1frmjham7zcj319i0c2mz7.jpg)

ref: https://serversforhackers.com/c/monitoring-processes-with-supervisord

mysql 基础知识(7) – JSON 字段

在前公司的时候,大家习惯在每个表加一个 extra 字段来表示一些额外的字段,然后在 ORM 中使用的时候再解析出来,方便了扩展字段,但是缺点也很明显,extra 字段只能读取而无法进行查询。MySQL 5.7 终于支持了 json 字段,相当于加入了一些 NoSQL 的特性,这样就可以很方便得查询了。

json 字段的使用

建表:

CREATE TABLE `book` (
  `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
  `title` varchar(200) NOT NULL,
  `tags` json DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB;

直接使用 json 类型就可以了。注意 json 字段不可以作为主键,不可以作为外键,不过既然是 json 字段了,谁会这么做呢。。

插入:

INSERT INTO `book` (`title`, `tags`)
VALUES (
  'ECMAScript 2015: A SitePoint Anthology',
  '["JavaScript", "ES2015", "JSON"]'
);

使用一个 json 字符串作为值插入即可。或者你也可以使用 json 相关的函数来表示json。

json 相关函数

json path

-- returns "SitePoint":
SELECT JSON_EXTRACT(
  '{"id": 1, "website": "SitePoint"}', 
  '$.website'
);

json path 的语法,用 $ 开头,然后跟着下面的选择器:

  • . 点后面跟着跟着一个字典里的名字, 比如 $.website
  • [N] 表示数组里的第 N 个元素
  • .[*] 表示选择字典里的所有元素
  • [*] 表示选择数组里的所有元素
  • prefix**suffix 表示以 prefix 开头,suffix 结尾的所有路径

举个栗子

{
  "a": 1,
  "b": 2,
  "c": [3, 4],
  "d": {
    "e": 5,
    "f": 6
  }
}
the following paths:

$.a returns 1
$.c returns [3, 4]
$.c[1] returns 4
$.d.e returns 5
$**.e returns [5]

构造、修改 json 的函数

函数都比较简单,看注释就明白了。

-- returns [1, 2, "abc"]:
SELECT JSON_ARRAY(1, 2, 'abc');

-- returns {"a": 1, "b": 2}:
SELECT JSON_OBJECT('a', 1, 'b', 2);

-- returns ["a", 1, {"key": "value"}]:
SELECT JSON_MERGE('["a", 1]', '{"key": "value"}');

-- returns ARRAY:
SELECT JSON_TYPE('[1, 2, "abc"]');

-- returns OBJECT:
SELECT JSON_TYPE('{"a": 1, "b": 2}');

-- returns an error:
SELECT JSON_TYPE('{"a": 1, "b": 2');

-- returns 1:
SELECT JSON_VALID('[1, 2, "abc"]');

还有其他一些函数,可以查看文档:

  • JSON_SET(doc, path, val[, path, val]…) —
    inserts or updates data in the document
  • JSON_INSERT(doc, path, val[, path, val]…) —
    inserts data into the document
  • JSON_REPLACE(doc, path, val[, path, val]…) —
    replaces data in the document
  • JSON_MERGE(doc, doc[, doc]…) —
    merges two or more documents
  • JSON_ARRAY_APPEND(doc, path, val[, path, val]…) —
    appends values to the end of an array
  • JSON_ARRAY_INSERT(doc, path, val[, path, val]…) —
    inserts an array within the document
  • JSON_REMOVE(doc, path[, path]…) —
    removes data from the document.

查询 json 函数

用于 where 子句中的函数

json_contains 用于选取数组中包含某个元素的行

-- all books with the 'JavaScript' tag:
SELECT * FROM `book` 
WHERE JSON_CONTAINS(tags, '["JavaScript"]');

json_search 用于选取字典中包含某个值的行

-- all books with tags starting 'Java':
SELECT * FROM `book` 
WHERE JSON_SEARCH(tags, 'one', 'Java%') IS NOT NULL;

用于 select 子句中的 json 函数

可以使用 json path 语法从得到的 json 文档中抽取出某个值。

select 语句

要想在select语句中使用 json path 抽取元素可以使用下面的语法,也就是 column->path

SELECT
  name,
  tags->"$[0]" AS `tag1`
FROM `book`;

一个更复杂一点的例子:

id name profile
1 Craig {“twitter”: “@craigbuckler”,“facebook”: “craigbuckler”,“googleplus”: “craigbuckler”}
2 SitePoint {“twitter”: “@sitepointdotcom”}
SELECT
  name, profile->"$.twitter" AS `twitter`
FROM `user`;
SELECT
  name, profile->"$.twitter" AS `twitter`
FROM `user`
WHERE
  profile->"$.twitter" IS NOT NULL;

REF:

  1. https://www.sitepoint.com/use-json-data-fields-mysql-databases/

如何发布 Python 代码到 PyPI 上(2018)

PyPI 是 Python 的集中仓库。通过把代码上传到 PyPI,其他人就可以使用使用 `pip install xxx` 安装代码了。

Python 的安装工具一直在不断演变,PyPI 的地址也从 pypi.python.org 变到了 pypi.org,因此网上的教程大多数都过时了,官方文档更新比较及时,但是也略过繁琐,这里写一篇简要的教程以飨读者。

本文以作者的库 [aioify](https://github.com/yifeikong/aioify) 为例

# 文件结构

“`
.
├── LICENSE.txt
├── MANIFEST.in
├── README.md
├── aioify
│   └── __init__.py
├── setup.cfg
└── setup.py
“`

## LICENSE.txt

库的开源协议,建议使用 Apache、MIT 等

## MANIFEST.in

打包要包含的文件,Python 文件会自动包括在内,但是 README.md 不包含在内,所以需要特别注明:

“`
include *.md
include LICENSE.txt
“`

具体语法参见

## README.md

说明文件,不多数了

## aioify

这个是具体的代码的仓库

## setup.cfg

用于指定 setup.py 的默认参数

“`
[metadata]
description-file = README.md
“`

## setup.py

这个文件是整个打包过程的关键所在了。请参考下面的注释

“`
import os
from distutils.core import setup

# 可选,读取 README 作为下面的 long_description
here = os.path.abspath(os.path.dirname(__file__))

with open(os.path.join(here, ‘README.md’)) as f:
long_description = f.read()

setup(
name = ‘aioify’, # 包的名字
packages = [‘aioify’], # 同上
version = ‘0.1.3’, # 当前版本
description = ‘Make every python function async/await’, # 描述
long_description = long_description, # 长描述,会显示在 PyPI 主页上
long_description_content_type = ‘text/markdown’, # 长描述的格式,不过好像markdown支持还不是很好
author = ‘Yifei Kong’, # 作者
author_email = ‘kongyifei@gmail.com’, # 作者邮件
url = ‘https://github.com/yifeikong/aioify’, # 项目地址
download_url = ‘https://github.com/yifeikong/aioify/archive/0.1.3.tar.gz’, # 下载链接,可选
keywords = [‘async’, ‘await’, ‘wrap’], # 关键词
# 分类器,可以认为是 PyPI 的一些栏目,建议参考文档填写,可选
classifiers = [
‘Development Status :: 3 – Alpha’,
‘Intended Audience :: Developers’,
‘License :: OSI Approved :: MIT License’,
‘Programming Language :: Python :: 3’,
‘Programming Language :: Python :: 3.5’,
‘Programming Language :: Python :: 3.6’,
‘Programming Language :: Python :: 3.7′,
],
python_requires=’>=3.5’ # 最低 Python 版本
)
“`

# 上传

## 本地测试

在项目的根目录,可以使用 pip 安装测试一下,看 setup.py 等文件是否有问题

“`
pip install -e .
“`

## 注册账户

在 pypi.org 注册一个账户。另外,在 test.pypi.org 再注册一个测试账户,因为两个站之间是独立的,所以得注册两次。

## 配置 .pypirc 文件

打开 ~/.pypirc 输入一下内容:

“`
[distutils]
index-servers =
pypi
testpypi

[pypi]
username: 你的用户名
password: 密码

[testpypi]
repository:https://test.pypi.org/legacy/
username: 同上
password: 同上
“`

## 上传到 test.pypi.org

test.pypi.org 是专门用来在正式上传前测试的服务器,以免操作失误。

打包

“`
python setup.py sdist
“`

然后可以看到多出了 dist/ 目录

上传

“`
pip install twine # 现在官方推荐使用twine工具
twine upload dist/* –repository testpypi
“`

然后到 test.pypi.org/projects/xxx 就可以看到你的代码了~

## 上传到 pypi

一切验证无误之后,就可以上传到 PyPI 了:

“`
twine upload dist/*
“`

更多细节介绍请参见官方文档:

1. https://packaging.python.org/tutorials/distributing-packages/
2. https://packaging.python.org/guides/using-testpypi/

Go 语言初体验

安装

安装 go 很简单,在 mac 上直接使用 brew install go 就可以了。注意的是需要设定GOPATH这个环境变量,这里设定到了 ~/go 这个目录。GOPATH 中也可以设置多个目录,默认安装包会安装到第一个路径。

export GOPATH=$HOME/go

语法和风格

Golang 的整个语法还是极其简单的,基本是 C 和 Python 的混合体。Go 语言详尽地规定了代码的风格,所以就不用为了大括号究竟是放在哪儿而开始圣战了。Go 语言对程序员的约束很强,不容易犯错。

Go 语言官方提供了一个工具 goimports 用来管理 import 语句,还不错。

import进来的包必须使用,声明的变量也必须使用。import 语句必须在 package 语句后面。虽然有大括号,但是大括号的位置也是指定的。

类型

几乎所有的值提供了默认都会初始化到对应的零值,即使没有初始化,不少函数处理 nil 的时候也是按照零值处理,这样避免了好多无谓的异常抛出。在go中也不会遇到len(None)这种问题,即使len(nil)也会返回0。

值得一提的是 Go 中的字符串,实际上是一个 byte 的只读数组,如果使用索引访问的话,是按照 byte 为单位来访问的。但是在打印和 range 的时候会直接按照 utf-8 解码输出。如果需要按照 rune(Go 语言对 unicode code point 的称呼)来遍历,需要使用 unicode/utf8 这个包中的函数。

即使字符串不是 utf-8的,或者不管怎样用错了编码,至少不会panic,而python中时不时就会抛出UnicodeDecodeError 。

不用思考蛋疼的 Unicode 问题,不过虽然 Go 的 string 是 utf-8 的,但是使用下标访问的是字节,而使用 for range 访问的又是 rune。

复合类型

像是 C 语言一样,Go 中也有定长数组,var a [3]int

在 Go 中经常作为变长数组使用的是 Slice。Slice 指的是一个数组的一个切片。在 Go 语言中,默认的函数调用都是值传递的,但是 Slice 做参数的时候传递的是一个 Slice Header,也就相当于按照引用传递。

Map 类型是引用类型,也就是函数调用的时候是按照引用传递的。nil map 可以像空map一样使用,但是插入的时候会 panic,因为没有给他分配内存,所以 map 类型一般使用 make 初始化。

m := make(map[string]int)

循环

go 还从 C 中 继承了 if (p = fopen("xxx", "w")) != NULL 这种在把赋值语句写在if中的写法,不过好在 Go 语言中可以写做两句。

if err := r.ParseForm(); err != nil {
      //...
}

另外 switch 语句默认就会 break 了,而不是 fall through 了。

循环语句很有意思,Go 语言直接把 while 关键字扔掉了,只用 for 本身就够了

for {
// ...
}

就相当于其他语言中的 while(true) 了。

不管是 Python 中的 for…in… 还是 JavaScript 中的 for…of… 语句,迭代数组和字典的时候多少感到一些不一致。在 Go 语言中,还是比较统一的,每次迭代都会返回两个值:key, value。

迭代 slice

words := []string{"a", "b"}
for i, word := range words {
    fmt.Printf("%d -> %s", i, word)
}

迭代 map

words := make(map[string]string)
words["a"] = "a"
words["b"] = "b"
for k, v := range words {
    fmt.Printf("%s -> %s", k, v)
}

goroutine

goroutine 相对于 Python 的 coroutine 的好处就在于它是抢占的,不用主动交出。

Python 的 coroutine 需要特别小心不要调用阻塞性的函数,比如 time.sleep,而要使用asyncio.sleep,所以写起来不是非常得方便。Python必须使用 await 来显式交出控制,而 Go 中则没有这种限制。

Timers and tickers

Timers 定义在你在未来的某个时间想要去做一次某件事。而 Tickers 则是定期执行某一个动作。这两个有点像是 js 里面的 setTimeout 和 setInterval 两个函数。

defer 实际上就相当于 C++ 中的RAII,和Python中的 with 语句

Go 的类型总体来说,和 Python 的 duck type 有点像,而和 Java 严格的继承则是完全背道而驰的。

发生赋值时候会不会检查类型呢?

interface{}

io

一般读取统一从 io.Reader 类型中读取

记得一定要使用append函数,而不要直接在slice的结尾通过下标添加字符,这样可能会panic

函数应该接受 interface 作为参数,并使用 struct 作为返回值。

目前为止有几个不爽的地方

  1. nil 字典不能直接赋值,但是 nil slice 可以 append
  2. interface 的 nil 始终没有搞明白。empty slice(a[0:0]) 和 nil 也不一样
  3. 没有一个统一的包管理工具,刚刚花一下午时间学习了 dep,号称是官方的试验,结果又看到一篇文章说 vgo 要取代 dep,WTF
  4. defer 执行的地方是函数的结尾,而不是块的结尾
var i int  // 声明一个变量
i = 42  // 赋值

或者直接使用简写:

i := 42  // 声明并赋值

数据类型

Go 语言有四种类型:基础类型、聚合类型、引用类型和接口类型。

基础类型

整型包括了: int, int8/16/32/64, uint, uint8/16/32/64 几种类型。另外 byte 是 uint8 的别名,rune 是 int32 的别名。

比如定义了 size 函数,虽然返回的合法值都是 uint范围内的,但是可能使用 -1 等表示非法值。所以除非要使用比特位操作,尽量使用 int,而不是 uint。

浮点型也包括了:float32 和 float64 两种类型,注意没有单独的 float/double 类型。其中 math.MaxFloat32 和 math.MaxFloat64 表示最大值。默认的浮点数是 float64。

Go 中还包含了复数类型:complex64 和 complex128,不过就像 Python 中的复数类型一样,从来没用过。

Go 中的字符串,实际上是一个 byte 的只读数组,如果使用索引访问的话,是按照 byte 为单位来访问的。但是在打印和 range 的时候会直接按照 utf-8 解码输出。如果需要按照 rune(Go 语言对 unicode code point 的称呼)来访问,需要使用 unicode/utf8 这个包中的函数。

s := "世界"

len(s)  // -> 6,utf-8 编码的中文一般是3个字节。
utf8.RuneCountInString(s)  // -> 2

// 使用 range 遍历
for r := range s {
    fmt.Printf("%s", r)
}

// 使用 DecodeRuneInString
for i := 0; i < len(s) {
    r, size := utf8.DecodeRuneInString(s[i:])
    fmt.Printf("%d\t%c\n", i, r)
}

// 全部转换成 rune
r := []rune(s)
fmt.Printf("%x\n", r)

即使字符串不是 utf-8的,或者不管怎样用错了编码,至少不会 panic,而是把不能解析的字符替换成 \uFFFD。而python中时不时就会抛出UnicodeDecodeError,非常蛋疼。

Go 语言中的字符串函数并没有作为 string 类型的方法,而是单独放在了 strings 包中,比如 strings.Splitstrings.Join 函数。因为 []byte 类型和 string 类型也比较类似,因此 strings 包中提供的方法,在 bytes 包中也可以找到类似的。

复合类型

数组

数组的类型是 [x]type,比如 [1]int[2]int 是不同的类型

var a [3]int = [3]int{1, 2, 3}
b := [3]int{1, 2, 3}
c := [...]int{1,2,3}
d := [...]{99: -1}

如果数组的元素是可以比较的(comparable),那么数组也是也以比较的,也就是可以用来做 map 的 key。

默认情况下,数组是按照值传递的,这一点和 C 语言默认传递指针不一样。

切片——变长数组

像是好多动态语言一样,Go 也支持切片操作:a[i:j]。不过和其他语言不一样的是,Go 的切片操作符产生了新的类型:切片,而不是数组。切片是对源数组的部分元素的一个引用。他们指向的是同一个内存单元。

切片可以当做一个变长数组使用,实际上我们不会每次都构造一个数组,然后获取切片,而是直接使用切片字面量。

a = []int{1, 2, 3}

和数组字面量很像,区别是没有指定长度。

和数组不同的一点是,切片是不能比较的,不管他内部的元素是什么。

Slice 底层引用了数组,但是并不会自动扩容,因此想往其中添加元素需要注意不能越界,提前扩充容量。可以使用 make 函数提前指定大小,或者使用 append 函数动态扩展切片大小。

make([]T, len)
make([]T, len, cap)

var x []int
x = append(x, 1)

Map

Go 语言也原生支持字典。

a := make(map[string]int)
a := map[string]int {
    "alice": 12,
    "bob": 12,
}

Go 语言和其他语言不同的是,尝试访问不存在的键也不会报错,而是会返回对应类型的零值,不过可以采用两个参数来验证是否存在这个键

v := a["foo"]
v, ok := a["foo"]

if v, ok : a["foo"]; ok {
    fmt.Print(v)
}

向 nil 的 slice 中直接存入元素是不合法的,向 nil 的 map 中直接存入元素也是不合法的。所以最好使用 make 来声明map。

Struct

定义一个 struct 如下:

type A struct {
    foo int
    bar int
}

Go 中也有指针的概念,不过没有 -> 这个关键字,统一使用 . 操作。如果一个 struct 中的所有字段都是可以比较的,整个 struct 就也是可以比较的。

Struct 嵌入

Go 语言支持一种特殊的骚操作,叫做 struct 嵌入。这样一个 struct 就可以直接调用被嵌入的 struct 的属性和方法,听起来有点像继承,而且的确实现了继承的功能,但的确不是继承,而是复合。

json

动态语言,比如 Python 和 JavaScript 会把 json 直接解析成数组和字典,而静态语言,比如 Java,则需要实现定义好和 Json 对象对应的类型,才能解析。Go 语言作为一种静态语言,自然也是需要定义对应的类型。

为了处理 Go 语言中的字段和 json 中的字段不对应的问题,Go 中可以使用 json:"xxx"
作为字段的 tag,来说明应该在 json 中使用什么字段。

import "encoding/json"

type Movie struct {
    Year int `json:"year"`
    Color bool `json:"color,omitempty"`
}

movies := []Movie {
    {
        Year: 1926,
        color: true,
    },
    {
        Year: 1027,
        Color: false,
    },
}

data, err := jso.Marshal(movies)
if err != nil {
    log.Fatalf("JSON marshaling failed: %s", err)
}
fmt.Printf("%s\n", data)

还可以使用 json.MarshalIndent 来格式化 json 字符串。

Go 语言是一门有 gc 的语言,所以所有变量的生命周期并不是严格限定于作用域的,由编译器来决定使用栈上还是堆上的空间。

在 Go 语言中没有 private 和 public 这些关键字。如果变量名字是大写字母开头的,那么就是导出的,如果是小写字母开头的,那么就是包内私有的变量。

参考:

https://stackoverflow.com/questions/18058164/is-a-go-goroutine-a-coroutine

Python urllib 模块

YN:网络访问的时候一定要记得设置一个合理的超时

在 Python2 中,有两个urllib:urllib 和 urllib2,urllib基本只使用urllib.urlencode(), urllib.quote函数,其他功能都被对应的 urllib2中的函数替代了。

Python3 把这两个模块进行了合并并拆分成了子模块,只使用 urllib 就好了。

# 发送 http 请求

## 直接使用 urlopen

“`
urllib.request.urlopen(url, data=None, [timeout, ] *, context) -> http.client.HTTPResponse
“`

用于发送 GET 或者 POST 请求,如果有data,则发送的是 POST 请求.返回一个 file-like的http.client.HTTPResponse对象,这个对象也可以作为一个 context manager。

info() | 返回headers
gerurl() | 返回 url,常用于判定是否被重定向
getcode() |
read()/readlines() | 返回文件内容

“`
>>> import urllib.request
>>> with urllib.request.urlopen(‘http://www.python.org/’) as f:
… print(f.read(300))

b’\n\n\n\n\n\n
\n
Python Programming ‘<br /> “`</p> <p>## 使用 Request 对象</p> <p>如果需要更改默认的 header 等数据或者使用 PUT、DELETE 等方法,urlopen 还可以接受一个 Request对象,可以在 Request 对象中更改。</p> <p>Request 对象的定义:</p> <p>“`<br /> class urllib.request.Request(url, data=None, headers={}, origin_req_host=None, unverifiable=False, method=None)<br /> “`</p> <p>一个 request可以指定 method, url,可以使用 req.add_header添加header。</p> <p>可以使用 add_header 方法再添加额外的header,但是实际上是 set_header,并不能添加重复的,不要被名字迷惑了。</p> <p>## 使用 Opener</p> <p>urilib2.build_opener 返回一个打开器(OpenerDirector),用于设定发出请求要经过的一些处理,可以设定代理,处理 cookie 等。OpenerDirector有一个属性addheaders,把他设定为一个包含键值 tuple 的 list,这样使用 opener 发送的每一个请求都会添加上这个 header。</p> <p>Then you could use opener.open instead of urllib2.urlopen</p> <p>Typical usage:</p> <p>“`<br /> import urllib2<br /> import urllib<br /> from cookielib import CookieJar<br /> cj = CookieJar()<br /> opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))<br /> # input-type values from the html form<br /> formdata = { “username” : username, “password”: password, “form-id” : “1234” }<br /> data_encoded = urllib.urlencode(formdata)<br /> response = opener.open(“https://page.com/login.php”, data_encoded)<br /> “`</p> <p>“`<br /> import urllib.request<br /> opener = urllib.request.build_opener()<br /> opener.addheaders = [(‘User-agent’, ‘Mozilla/5.0’)]<br /> opener.open(‘http://www.example.com/’)<br /> “`</p> <p>From <http://stackoverflow.com/questions/3334809/python-urllib2-how-to-send-cookie-with-urlopen-request> </p> <p># 异常:URLError HTTPError</p> <p>URLError是 IOError 的子类, HTTPError.code is the http code </p> <p># urllib.parse</p> <p>urlparse and urlunparse is not as good as urlsplit</p> <p>## urlsplit</p> <p>return a five element tuple by scheme://netloc/path?query#fragment</p> <p>Attribute Index Value Value if not present<br /> scheme 0 URL scheme specifier scheme parameter<br /> netloc 1 Network location part empty string<br /> path 2 Hierarchical path empty string<br /> query 3 Query component empty string<br /> fragment 4 Fragment identifier empty string<br /> username   User name None<br /> password   Password None<br /> hostname   Host name (lower case) None<br /> port   Port number as integer, if present None</p> <p>note,the netloc contains domain and port</p> <p>“`<br /> >>> urlsplit(‘www.cwi.nl/%7Eguido/Python.html’)<br /> SplitResult(scheme=”, netloc=”, path=’www.cwi.nl/%7Eguido/Python.html’, query=”, fragment=”)<br /> # notice the netloc will be wrong if “//” is missing<br /> >>> urlsplit(‘//www.cwi.nl/%7Eguido/Python.html’)<br /> SplitResult(scheme=”, netloc=’www.cwi.nl’, path=’/%7Eguido/Python.html’, query=”, fragment=”)<br /> “`</p> <p>urlunsplit join the parse result。</p> <p>“`<br /> parse_qs(qs, keep_blank_values=False, encoding=’utf-8′)<br /> “`</p> <p>parse a query string to { key: [values] } pair, note without ‘?’</p> <p>parse_qsl</p> <p>return a list of k,v tuple</p> <p>quote/quote_plus</p> <p>unquote</p> <p>urlencode</p> </div><!-- .entry-content --> <footer class="entry-meta"> <ul class="entry-meta-taxonomy"> <li class="comments-link"><a href="https://yifei.me/note/671/#respond">Leave a comment</a></li> </ul> </footer><!-- .entry-meta --> </article><!-- #post-## --> <article id="post-628" class="post-628 post type-post status-publish format-standard hentry category-python"> <header class="entry-header"> <h1 class="entry-title"><a href="https://yifei.me/note/628/" rel="bookmark">Python 中的 iterator 和 generator</a></h1> <div class="entry-meta"> <span class="posted-on">Posted on <a href="https://yifei.me/note/628/" rel="bookmark"><time class="entry-date published" datetime="2018-05-01T06:19:00+08:00">2018年5月1日</time></a></span><span class="byline"> by <span class="author vcard"><a class="url fn n" href="https://yifei.me/note/author/admin/">逸飞</a></span></span> </div><!-- .entry-meta --> </header><!-- .entry-header --> <div class="entry-content"> <p>这篇文章里面有不少概念都是错的,需要改进</p> <p># 迭代器</p> <p>In Python, iterable and iterator have specific meanings.</p> <p>An iterable is an object that has an __iter__ method which returns an iterator, or which defines a __getitem__ method that can take sequential indexes starting from 0 (and raises an IndexError when the indexes are no longer valid). So an iterable is an object that you can get an iterator from.</p> <p>calling iter(iterable) will return a iterator</p> <p>An iterator is an object with a next (Python 2) or __next__ (Python 3) method.<br /> Whenever you use a for loop, or map, or a list comprehension, etc. in Python, the next method is called automatically to get each item from the iterator, thus going through the process of iteration.</p> <p># Generators</p> <p>Generators are iterators, but you can only iterate over them once. It’s because they do not store all the values in memory, they generate the values on the fly. </p> <p>“`<br /> def generator_function():<br /> for i in [0, 1, 2]:<br /> yield i * 2<br /> for item in generator_function():<br /> print(item)<br /> # Output: 0<br /> # 2<br /> # 4<br /> “`</p> <p>as you can see, generators are typically a filter or mapper between sequences</p> <p>“`<br /> def fib(n):<br /> a = b = 1<br /> for i in range(n):<br /> yield a<br /> a, b = b, a + b<br /> “`</p> <p>From <http://book.pythontips.com/en/latest/generators.html></p> <p># 迭代器协议</p> <p>with open(‘file’) as f:<br /> try:<br /> while True:<br /> value = next(f)<br /> print value<br /> except StopIteration: pass</p> <p>The word “generator” is confusingly used to mean both the function that generates and what it generates. In this chapter, I’ll use the word “generator” to mean the genearted object and “generator function” to mean the function that generates it.</p> <p>Can you think about how it is working internally?</p> <p>When a generator function is called, it returns a generator object without even beginning execution of the function. When next method is called for the first time, the function starts executing until it reaches yield statement. The yielded value is returned by the next call.<br /> The following example demonstrates the interplay between yield and call to next method on generator object.</p> </div><!-- .entry-content --> <footer class="entry-meta"> <ul class="entry-meta-taxonomy"> <li class="comments-link"><a href="https://yifei.me/note/628/#respond">Leave a comment</a></li> </ul> </footer><!-- .entry-meta --> </article><!-- #post-## --> <article id="post-696" class="post-696 post type-post status-publish format-standard hentry category-django"> <header class="entry-header"> <h1 class="entry-title"><a href="https://yifei.me/note/696/" rel="bookmark">django forms</a></h1> <div class="entry-meta"> <span class="posted-on">Posted on <a href="https://yifei.me/note/696/" rel="bookmark"><time class="entry-date published" datetime="2018-05-01T04:52:00+08:00">2018年5月1日</time></a></span><span class="byline"> by <span class="author vcard"><a class="url fn n" href="https://yifei.me/note/author/admin/">逸飞</a></span></span> </div><!-- .entry-meta --> </header><!-- .entry-header --> <div class="entry-content"> <p>django 中的 form 和 model 的用法很像,都是定义一个类,然后指定一些字段就可以了</p> <p>最简单的form</p> <p>“`<br /> from django import forms</p> <p>class ContactForm(forms.Form):<br /> subject = forms.CharField(max_length=100)<br /> email = forms.EmailField(required=False)<br /> message = forms.CharField(widget=forms.Textarea)</p> <p> def clean_message(self):<br /> message = self.cleaned_data[‘message’]<br /> num_words = len(message.split())<br /> if num_words < 4: raise forms.ValidationError("Not enough words!") return message ``` ``` def contact(request): if request.method == 'POST': form = ContactForm(request.POST) if form.is_valid(): cd = form.cleaned_data send_mail( cd['subject'], cd['message'], cd.get('email', 'noreply@example.com'), ['siteowner@example.com'], ) return HttpResponseRedirect('/contact/thanks/') else: form = ContactForm() return render(request, 'contact_form.html', {'form': form}) ``` ``` <form action="" method="post"> <table> {{ form.as_table }}<br /> </table> <p> {% csrf_token %}<br /> <input type="submit" value="Submit"><br /> </form> <p>“`</p> <p>方法 | 用法<br /> ——|——<br /> form.__str__() | return table representation<br /> form.as_p() | return p representation<br /> form.as_li() | return li representation<br /> form.__getitem__() | return element tag<br /> form.__init__(dict) | fill values<br /> form.is_bound |<br /> form.is_valid() |<br /> form.cleaned_data | </p> <p>Note not include table/ul/form tags, just the inside tags</p> <p># ajax</p> <p>## ajax 中如何指定 crsf token</p> <p>axios 中:</p> <p> import axios from ‘axios’;<br /> axios.defaults.xsrfHeaderName = “X-CSRFTOKEN”;<br /> axios.defaults.xsrfCookieName = “csrftoken”;</p> <p>settings.py 中</p> <p> CSRF_COOKIE_NAME = “csrftoken”</p> <p>[参考](https://stackoverflow.com/questions/39254562/csrf-with-django-reactredux-using-axios)</p> </div><!-- .entry-content --> <footer class="entry-meta"> <ul class="entry-meta-taxonomy"> <li class="comments-link"><a href="https://yifei.me/note/696/#respond">Leave a comment</a></li> </ul> </footer><!-- .entry-meta --> </article><!-- #post-## --> </div><!-- #main --> </section><!-- #primary --> <div id="secondary" class="widget-area" role="complementary"> <aside id="search-3" class="widget widget_search"><form role="search" method="get" class="search-form" action="https://yifei.me/"> <label> <span class="screen-reader-text">Search for:</span> <input type="search" class="search-field" placeholder="Search" value="" name="s" title="Search for:" /> </label> <input type="submit" class="fa search-submit" value="" /> </form> </aside><aside id="categories-4" class="widget widget_categories"><h1 class="widget-title">分类目录</h1> <ul> <li class="cat-item cat-item-57"><a href="https://yifei.me/category/startup/">创业</a> (21) </li> <li class="cat-item cat-item-1"><a href="https://yifei.me/category/uncategorized/">未分类</a> (5) </li> <li class="cat-item cat-item-32"><a href="https://yifei.me/category/talks/">演讲与笔记</a> (18) </li> <li class="cat-item cat-item-2"><a href="https://yifei.me/category/computer-science/">计算机</a> (344) <ul class='children'> <li class="cat-item cat-item-24"><a href="https://yifei.me/category/computer-science/android/">Android</a> (25) </li> <li class="cat-item cat-item-35"><a href="https://yifei.me/category/computer-science/bitcoin/">Bitcoin</a> (1) </li> <li class="cat-item cat-item-39"><a href="https://yifei.me/category/computer-science/c/">C 语言</a> (9) </li> <li class="cat-item cat-item-45"><a href="https://yifei.me/category/computer-science/css/">CSS</a> (4) </li> <li class="cat-item cat-item-43"><a href="https://yifei.me/category/computer-science/django/">Django</a> (14) </li> <li class="cat-item cat-item-23"><a href="https://yifei.me/category/computer-science/docker/">Docker</a> (7) </li> <li class="cat-item cat-item-11"><a href="https://yifei.me/category/computer-science/git/">git</a> (1) </li> <li class="cat-item cat-item-30"><a href="https://yifei.me/category/computer-science/golang/">Golang</a> (6) </li> <li class="cat-item cat-item-48"><a href="https://yifei.me/category/computer-science/http/">HTTP</a> (4) </li> <li class="cat-item cat-item-36"><a href="https://yifei.me/category/computer-science/iot/">IoT</a> (1) </li> <li class="cat-item cat-item-19"><a href="https://yifei.me/category/computer-science/javascript/">JavaScript</a> (17) </li> <li class="cat-item cat-item-50"><a href="https://yifei.me/category/computer-science/linux/">Linux</a> (1) </li> <li class="cat-item cat-item-51"><a href="https://yifei.me/category/computer-science/lua/">lua</a> (5) </li> <li class="cat-item cat-item-28"><a href="https://yifei.me/category/computer-science/python/">Python</a> (57) </li> <li class="cat-item cat-item-22"><a href="https://yifei.me/category/computer-science/rust/">Rust</a> (1) </li> <li class="cat-item cat-item-38"><a href="https://yifei.me/category/computer-science/streaming-system/">Streaming System</a> (1) </li> <li class="cat-item cat-item-54"><a href="https://yifei.me/category/computer-science/tricks/">Tricks</a> (9) </li> <li class="cat-item cat-item-56"><a href="https://yifei.me/category/computer-science/vim/">vim</a> (5) </li> <li class="cat-item cat-item-26"><a href="https://yifei.me/category/computer-science/backend/">Web 后端</a> (21) </li> <li class="cat-item cat-item-44"><a href="https://yifei.me/category/computer-science/frontend/">前端</a> (2) </li> <li class="cat-item cat-item-16"><a href="https://yifei.me/category/computer-science/cli/">命令行技巧</a> (16) </li> <li class="cat-item cat-item-18"><a href="https://yifei.me/category/computer-science/database/">数据库</a> (25) </li> <li class="cat-item cat-item-21"><a href="https://yifei.me/category/computer-science/server-side/">服务端工具</a> (6) </li> <li class="cat-item cat-item-52"><a href="https://yifei.me/category/computer-science/machine-learning/">机器学习</a> (5) </li> <li class="cat-item cat-item-37"><a href="https://yifei.me/category/computer-science/arch/">架构</a> (15) </li> <li class="cat-item cat-item-47"><a href="https://yifei.me/category/computer-science/browser-plugin/">浏览器插件</a> (8) </li> <li class="cat-item cat-item-17"><a href="https://yifei.me/category/computer-science/crawler/">爬虫</a> (27) </li> <li class="cat-item cat-item-53"><a href="https://yifei.me/category/computer-science/monitoring/">监控</a> (4) </li> <li class="cat-item cat-item-49"><a href="https://yifei.me/category/computer-science/lib/">第三方库</a> (6) </li> <li class="cat-item cat-item-15"><a href="https://yifei.me/category/computer-science/algorithm/">算法</a> (9) </li> <li class="cat-item cat-item-40"><a href="https://yifei.me/category/computer-science/compiler/">编译原理</a> (2) </li> <li class="cat-item cat-item-20"><a href="https://yifei.me/category/computer-science/network/">网络</a> (8) </li> <li class="cat-item cat-item-41"><a href="https://yifei.me/category/computer-science/computer-graphics/">计算机图形学</a> (1) </li> <li class="cat-item cat-item-14"><a href="https://yifei.me/category/computer-science/reverse-engineering/">逆向工程</a> (3) </li> </ul> </li> <li class="cat-item cat-item-31"><a href="https://yifei.me/category/finance/">金融</a> (4) <ul class='children'> <li class="cat-item cat-item-29"><a href="https://yifei.me/category/finance/stock/">股票</a> (4) </li> </ul> </li> </ul> </aside><aside id="archives-3" class="widget widget_archive"><h1 class="widget-title">文章归档</h1> <ul> <li><a href='https://yifei.me/note/date/2019/10/'>2019年十月</a> (10)</li> <li><a href='https://yifei.me/note/date/2019/09/'>2019年九月</a> (3)</li> <li><a href='https://yifei.me/note/date/2019/08/'>2019年八月</a> (3)</li> <li><a href='https://yifei.me/note/date/2019/07/'>2019年七月</a> (3)</li> <li><a href='https://yifei.me/note/date/2019/06/'>2019年六月</a> (29)</li> <li><a href='https://yifei.me/note/date/2019/05/'>2019年五月</a> (1)</li> <li><a href='https://yifei.me/note/date/2019/03/'>2019年三月</a> (2)</li> <li><a href='https://yifei.me/note/date/2019/02/'>2019年二月</a> (3)</li> <li><a href='https://yifei.me/note/date/2019/01/'>2019年一月</a> (3)</li> <li><a href='https://yifei.me/note/date/2018/12/'>2018年十二月</a> (1)</li> <li><a href='https://yifei.me/note/date/2018/11/'>2018年十一月</a> (7)</li> <li><a href='https://yifei.me/note/date/2018/10/'>2018年十月</a> (9)</li> <li><a href='https://yifei.me/note/date/2018/09/'>2018年九月</a> (6)</li> <li><a href='https://yifei.me/note/date/2018/08/'>2018年八月</a> (3)</li> <li><a href='https://yifei.me/note/date/2018/07/'>2018年七月</a> (26)</li> <li><a href='https://yifei.me/note/date/2018/06/'>2018年六月</a> (33)</li> <li><a href='https://yifei.me/note/date/2018/05/'>2018年五月</a> (7)</li> <li><a href='https://yifei.me/note/date/2018/04/'>2018年四月</a> (35)</li> <li><a href='https://yifei.me/note/date/2018/03/'>2018年三月</a> (3)</li> <li><a href='https://yifei.me/note/date/2018/02/'>2018年二月</a> (1)</li> <li><a href='https://yifei.me/note/date/2018/01/'>2018年一月</a> (6)</li> <li><a href='https://yifei.me/note/date/2017/12/'>2017年十二月</a> (6)</li> <li><a href='https://yifei.me/note/date/2017/11/'>2017年十一月</a> (22)</li> <li><a href='https://yifei.me/note/date/2017/09/'>2017年九月</a> (4)</li> <li><a href='https://yifei.me/note/date/2017/08/'>2017年八月</a> (14)</li> <li><a href='https://yifei.me/note/date/2017/07/'>2017年七月</a> (22)</li> <li><a href='https://yifei.me/note/date/2017/06/'>2017年六月</a> (42)</li> <li><a href='https://yifei.me/note/date/2017/05/'>2017年五月</a> (87)</li> <li><a href='https://yifei.me/note/date/2016/12/'>2016年十二月</a> (1)</li> </ul> </aside> <aside id="recent-posts-3" class="widget widget_recent_entries"> <h1 class="widget-title">近期文章</h1> <ul> <li> <a href="https://yifei.me/note/789/">在 IPython 中自动重新导入包</a> </li> <li> <a href="https://yifei.me/note/788/">Go 语言 Map 实战</a> </li> <li> <a href="https://yifei.me/note/787/">本周股票复盘(2019-10-20)</a> </li> <li> <a href="https://yifei.me/note/786/">Baelish: An Introspection II</a> </li> <li> <a href="https://yifei.me/note/784/">为什么说小公司的沟通效率反而是低下的?</a> </li> </ul> </aside><aside id="meta-3" class="widget widget_meta"><h1 class="widget-title">功能</h1> <ul> <li><a href="https://yifei.me/wp-login.php">登录</a></li> <li><a href="https://yifei.me/feed/">文章<abbr title="Really Simple Syndication">RSS</abbr></a></li> <li><a href="https://yifei.me/comments/feed/">评论<abbr title="Really Simple Syndication">RSS</abbr></a></li> <li><a href="https://cn.wordpress.org/" title="基于WordPress,一个优美、先进的个人信息发布平台。">WordPress.org</a></li> </ul> </aside> </div><!-- #secondary --> </div><!-- #content --> </div><!-- .col-width --> <footer class="site-footer no-widgets" role="contentinfo"> <div class="col-width"> Powered by <a href="http://govpress.org/">GovPress</a>, the <a href="http://wordpress.org/">WordPress</a> theme for government. </div><!-- .col-width --> </footer><!-- .site-footer --> </div><!-- #page --> <script> (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); ga('create', 'UA-101694992-1', 'auto'); ga('require', 'displayfeatures'); ga('require', 'linkid'); ga('send', 'pageview'); </script> <script type='text/javascript' src='https://yifei.me/wp-content/themes/govpress/js/combined-min.js?ver=1.5.2'></script> <script type='text/javascript' src='https://yifei.me/wp-includes/js/clipboard.min.js?ver=5.2.4'></script> <script type='text/javascript' src='https://yifei.me/wp-includes/js/wp-embed.min.js?ver=5.2.4'></script> <script id="module-katex"> (function($) { $(function() { if (typeof katex !== "undefined") { if ($(".language-katex").length > 0) { $(".language-katex").parent("pre").attr("style", "text-align: center; background: none;"); $(".language-katex").addClass("katex-container").removeClass("language-katex"); $(".katex-container").each(function() { var katexText = $(this).text(); var el = $(this).get(0); if ($(this).parent("code").length == 0) { try { katex.render(katexText, el) } catch (err) { $(this).html("<span class='err'>" + err) } } }); } if ($(".katex-inline").length > 0) { $(".katex-inline").each(function() { var katexText = $(this).text(); var el = $(this).get(0); if ($(this).parent("code").length == 0) { try { katex.render(katexText, el) } catch (err) { $(this).html("<span class='err'>" + err) } } }); } } }); })(jQuery); </script> <script id="module-prism-line-number"> (function($) { $(function() { $("code").each(function() { var parent_div = $(this).parent("pre"); var pre_css = $(this).attr("class"); if (typeof pre_css !== "undefined" && -1 !== pre_css.indexOf("language-")) { parent_div.addClass("line-numbers"); } }); }); })(jQuery); </script> <script id="module-clipboard"> (function($) { $(function() { var pre = document.getElementsByTagName("pre"); var pasteContent = document.getElementById("paste-content"); var hasLanguage = false; for (var i = 0; i < pre.length; i++) { var codeClass = pre[i].children[0].className; var isLanguage = codeClass.indexOf("language-"); var excludedCodeClassNames = [ "language-katex", "language-seq", "language-sequence", "language-flow", "language-flowchart", "language-mermaid", ]; var isExcluded = excludedCodeClassNames.indexOf(codeClass); if (isExcluded !== -1) { isLanguage = -1; } if (isLanguage !== -1) { var button = document.createElement("button"); button.className = "copy-button"; button.textContent = "Copy"; pre[i].appendChild(button); hasLanguage = true; } }; if (hasLanguage) { var copyCode = new ClipboardJS(".copy-button", { target: function(trigger) { return trigger.previousElementSibling; } }); copyCode.on("success", function(event) { event.clearSelection(); event.trigger.textContent = "Copied"; window.setTimeout(function() { event.trigger.textContent = "Copy"; }, 2000); }); } }); })(jQuery); </script> </body> </html>