BlueXIII's Blog

热爱技术,持续学习

0%

安装

yum install curl openssh-server openssh-clients postfix cronie
service postfix start
chkconfig postfix on
lokkit -s http -s ssh
rpm -i gitlab-ce-8.13.1-ce.0.el6.x86_64.rpm
gitlab-ctl reconfigure

配置脚本修改IP

vi /etc/gitlab/gitlab.rb
external_url ‘http://134.32.51.31'

访问

http://134.32.51.31

启停

service gitlab start

基本操作

docker for mac

https://docs.docker.com/docker-for-mac/
HyperKit VM

helloworld

docker –version
docker-compose –version
docker-machine –version
docker ps
docker run hello-world
docker run -d -p 80:80 –name webserver nginx
docker run -it ubuntu bash
docker run docker/whalesay cowsay boo
docker run docker/whalesay cowsay boo-boo

build image

vi Dockerfile
FROM docker/whalesay:latest
RUN apt-get -y update && apt-get install -y fortunes
CMD /usr/games/fortune -a | cowsay

docker build -t docker-whale .
docker images
docker run docker-whale

tag & push

docker tag 038cc8845778 bluexiii/docker-whale:latest
docker login
docker push maryatdocker/docker-whale
docker rmi -f docker-whale
docker run bluexiii/docker-whale

容器命名

docker run –name bob_the_container -i -t ubuntu /bin/bash

重新启动已停止的容器

docker start bob_the_container

附着到容器上

docker attach bob_the_container

创建守护式容器

docker run –name daemon_dave -d ubuntu /bin/sh -c “while true; do echo hello world; sleep 1;done”

查看日志

docker logs -ft daemon_dave

查看容器中的进程

docker top daemon_dave

查看统计信息

docker stats daemon_dave

停止守护式容器

docker stop daemon_dave

自动重启容器

–restart=always
–restart=on-failure:5
docker run –restart=always –name daemon_dave2 -d ubuntu /bin/sh -c “while true; do echo hello world; sleep 1;done”

获取容器详情

docker inspect daemon_dave2

删除容器

docker rm

删除所有容器

docker rm docker ps -a -q

本地镜像位置

$HOME/Library/Containers/com.docker.docker/Data/com.docker.driver.amd64-linux/Docker.qcow2

拉取镜像到本机

docker pull fedora

查找镜像

docker search fedora

构建镜像

使用commit构建镜像

基于Ubuntu构建

docker run -i -t ubuntu /bin/bash

内网环境下设置apt代理

cat > /etc/apt/apt.conf.d/10proxy
Acquire::http::Proxy “http://134.32.32.13:31315/";
^d

安装基础包

apt update
apt install nginx

commit提交

docker commit cb051b49a4cd bluexiii/mynginx
or
docker commit -m ‘message’ -a ‘author’ cb051b49a4cd bluexiii/mynginx:tag
类似git,只提交差异部分,速度很快

docker inspect bluexiii/mynginx

使用Dockfile构建镜像

vi Dockerfile
FROM ubuntu
RUN echo ‘Acquire::http::Proxy “http://IP:PORT/";' > /etc/apt/apt.conf.d/10proxy
RUN apt-get -y update && apt-get install -y nginx
RUN echo ‘hello world’ > /usr/share/nginx/html/index.html
EXPOSE 80
docker build -t bluexiii/mynginx2 .

运行镜像

docker run -d -p 40080:80 –name mynginx2 bluexiii/mynginx2 nginx -g “daemon off;”

查看端口映射情况

docker port mynginx2 80

配置yum的HTTP代理

vi /etc/yum.conf
export proxy=http://IP:PORT/

导入系统HTTP代理环境变量

export http_proxy=”IP:PORT”

配置yum使用163源

mkdir src
wget http://mirrors.163.com/centos/6/os/x86_64/Packages/yum-3.2.29-73.el6.centos.noarch.rpm
wget http://mirrors.163.com/centos/6/os/x86_64/Packages/yum-metadata-parser-1.1.2-16.el6.x86_64.rpm
wget http://mirrors.163.com/centos/6/os/x86_64/Packages/yum-plugin-fastestmirror-1.1.30-37.el6.noarch.rpm
wget http://mirrors.163.com/centos/6/os/x86_64/Packages/python-iniparse-0.3.1-2.1.el6.noarch.rpm
wget http://mirrors.163.com/centos/6/os/x86_64/Packages/python-urlgrabber-3.9.1-11.el6.noarch.rpm
rpm -aq|grep yum|xargs rpm -e –nodeps
rpm -ivh xxxxxx
cd /etc/yum.repos.d/
先编辑其它repo,将enabled=0
vi 163.repo

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
[base]
name=CentOS-$releasever - Base
baseurl=http://mirrors.163.com/centos/6/os/$basearch/
gpgcheck=1
gpgkey=http://mirrors.163.com/centos/RPM-GPG-KEY-CentOS-6
#released updates
[updates]
name=CentOS-$releasever - Updates
baseurl=http://mirrors.163.com/centos/6/updates/$basearch/
gpgcheck=1
gpgkey=http://mirrors.163.com/centos/RPM-GPG-KEY-CentOS-6
#packages used/produced in the build but not released
#[addons]
#name=CentOS-$releasever - Addons
#baseurl=http://mirrors.163.com/centos/$releasever/addons/$basearch/
#gpgcheck=1
#gpgkey=http://mirrors.163.com/centos/RPM-GPG-KEY-CentOS-6
#additional packages that may be useful
[extras]
name=CentOS-$releasever - Extras
baseurl=http://mirrors.163.com/centos/6/extras/$basearch/
gpgcheck=1
gpgkey=http://mirrors.163.com/centos/RPM-GPG-KEY-CentOS-6
#additional packages that extend functionality of existing packages
[centosplus]
name=CentOS-$releasever - Plus
baseurl=http://mirrors.163.com/centos/6/centosplus/$basearch/
gpgcheck=1
enabled=0
gpgkey=http://mirrors.163.com/centos/RPM-GPG-KEY-CentOS-6

Enable EPEL

rpm -Uvh http://www.gtlib.gatech.edu/pub/fedora-epel/6/i386/epel-release-6-8.noarch.rpm

使用yum安装Docker

yum -y install docker-io

启动

service docker start

查看日志

cd /var/log
tail -f docker

yum upgrade device-mapper-event-libs

设置Docker代理

vi /etc/init.d/docker
export HTTP_PROXY=”http://IP:PORT"
export HTTPS_PROXY=”http://IP:PORT"

export ALL_PROXY=socks5://127.0.0.1:1080

安装

sudo npm install -g cordova
cordova create AppName
cordova platform add browser
cordova run browser
cordova platform add android –save
cordova platform ls
cordova requirements

编译

cordova build android
cordova build android -release
cordova emulate android

配置XML

vi config.xml


签名

keytool -genkey -v -keystore release-key.keystore -alias cordova-demo -keyalg RSA -keysize 2048 -validity 10000
vi build.json

1
2
3
4
5
6
7
8
9
10
{
"android": {
"release": {
"keystore": "release-key.keystore",
"alias": "cordova-demo",
"storePassword": "password",
"password": "password"
}
}
}

禁用缓存

1
2
3
4
5
6
7
8
9
10
11
12
import android.webkit.WebSettings;
import android.webkit.WebView;
@Override
protected void onResume() {
super.onResume();
// Disable caching ..
WebView wv = (WebView) appView.getEngine().getView();
WebSettings ws = wv.getSettings();
ws.setAppCacheEnabled(false);
ws.setCacheMode(WebSettings.LOAD_NO_CACHE);
loadUrl(launchUrl); // launchUrl is the default url specified in Config.xml
}

场景

与某第三方厂商使用文件接口传递数据。我们是发送方,对方是接收方,对方的接口规范要求对于大于500M文件,必须使用zip格式分卷压缩。
本来在linux下使用zip分卷压缩轻而易举,直接调用系统的zip命令,加上-b参数即可,但恰巧我们的接口机是台IBM小机,AIX系统下的zip不支持分卷功能,需要与split命令结合实现分卷功能。

实现方案

  1. 先使用zip命令将目录打包成单独的zip文件
  2. 使用split命令将zip文件拆分
  3. 对于拆分出来的散乱文件,按格式要求批量重命名

格式要求

生成的文件名格式如下:
yyyymmdd_zzzz_RetentionPhotosSync_iiii_xxxx.zip
其中yyyymmdd为时间,xxxx为分卷序列号

分卷压缩脚本

下面以名为testfolder的目录为例,以5M大小,进行分卷

使用zip命令,将整个目录压缩成.zip(对于目录需要使用-r参数)

1
zip -r tmpfile.zip testfolder

使用split命令,以5M为单位,将.zip进行拆分,生成xaa、xab、xac…(以此类推)等多个文件

1
split -b 5m tmpfile.zip splitfile-

对于生成的xaa、xab、xac…散乱文件,按格式重命名

1
nowdate=`date +%Y%m%d`;n=0; for filename in `ls splitfile* `; do n=`expr ${n} + 1`; suffix=`printf %04d ${n}`; mv ${filename} ${nowdate}_zzzz_RetentionPhotosSync_iiii_${suffix}.zip ; done

清理临时文件

1
rm tmpfile.zip

分卷解压脚本

如果对端恰巧也是AIX系统,可以用如下方式解压缩

使用cat命令合并文件

1
cat yyyymmdd_zzzz_RetentionPhotosSync_iiii_*.zip > yyyymmdd_zzzz_RetentionPhotosSync_iiii.zip

使用unzip命令解压

1
unzip yyyymmdd_zzzz_RetentionPhotosSync_iiii.zip

简介

最近在用Python写一些东西,各种第三方库好用极了,写起来也是非常酸爽,但生产环境部署上却略有些繁琐。
主要原因是公司的生产环境主机,大都是在内网中的,无法直连Internet,首先要花点力气搞定网络连接问题。
另外操作系统也基本上是RedHat,只预装了Python2.7,yum源中也没有3.X,只能通过源码编译的方式安装。如果是Debian系的,就不会这么麻烦了。

HTTP代理服务器搭建

要搞定网络连接,最简单的办法还是找一台能出得去的主机,搭一个HTTP代理服务。
这里我选择了srelay+polipo的组合。Srelay用于搭建Socks5代理,Polipo用于将Socks5端口转为HTTP端口。
PS:当然,还可以使用redsocks+polipo的组合,直接默翻墙,使用yum时可以快很多。

使用srelay搭建简易Socks5代理

我们先找一台可以直连外网的主机,注意要新建一个用户,不要以root直接操作。

官网地址:
http://socks-relay.sourceforge.net
下载srelay-0.4.8b6.tar.gz

解压后直接make即可,将编译出的二进制文件srelay随便找一个目录扔进去

启动:

1
./srelay -i xxx.xxx.xxx.xxx:31080  

无须配置,直接在启动时直接绑定IP+端口,注意IP一定要绑定内网的那个。

使用polipo将Socks5代理转为Http代理

下载地址:
https://www.irif.fr/~jch/software/polipo/polipo.html

解压后,修改一下Makefile,将PREFIX改为实际安装目录

1
2
3
4
5
6
7
vi Makefile
PREFIX = /path/to/your/app
BINDIR = $(PREFIX)/bin
MANDIR = $(PREFIX)/man
INFODIR = $(PREFIX)/info
LOCAL_ROOT = $(PREFIX)/www
DISK_CACHE_ROOT = $(PREFIX)/cache

然后直接make && make install即可

新建一个config文件

1
2
3
4
5
vi config
socksParentProxy = "xxx.xxx.xxx.xxx:31080"
socksProxyType = socks5
proxyAddress = "0.0.0.0"
proxyPort = 31081

其中socksParentProxy是Socks的IP+端口,proxyPort是新开的HTTP端口

启动:

1
nohup ./polipo -c config

环境变量设置

现在回到Python3所在的主机,设置一个http_proxy环境变量,指向刚才所搭建的IP:端口

1
export http_proxy="http://xxx.xxx.xxx.xxx:31081"

这时wget、yum等已经可以正常使用了

配置yum使用163的源(可选)

有时可能需要用yum安装一些依赖,配置成163的源会速度快一些

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
wget http://mirrors.163.com/centos/6/os/x86_64/Packages/python-iniparse-0.3.1-2.1.el6.noarch.rpm
wget http://mirrors.163.com/centos/6/os/x86_64/Packages/yum-metadata-parser-1.1.2-16.el6.x86_64.rpm
wget http://mirrors.163.com/centos/6/os/x86_64/Packages/python-urlgrabber-3.9.1-11.el6.noarch.rpm
wget http://mirrors.163.com/centos/6/os/x86_64/Packages/yum-3.2.29-73.el6.centos.noarch.rpm
wget http://mirrors.163.com/centos/6/os/x86_64/Packages/yum-plugin-fastestmirror-1.1.30-37.el6.noarch.rpm\

rpm -qa | grep yum | xargs rpm -e --nodeps
rpm -ivh python-iniparse-0.3.1-2.1.el6.noarch.rpm
rpm -ivh yum-metadata-parser-1.1.2-16.el6.x86_64.rpm
rpm -ivh python-urlgrabber-3.9.1-11.el6.noarch.rpm --force
rpm -ivh yum-3.2.29-73.el6.centos.noarch.rpm yum-plugin-fastestmirror-1.1.30-37.el6.noarch.rpm

wget http://mirrors.163.com/.help/CentOS6-Base-163.repo
yum clean all
yum makecache

源码方式安装python3

现在开始,万事俱备,可以编译安装python了。需要使用root权限操作。

首先,在官网下载最新的3.6源码:
https://www.python.org/ftp/python/3.6.0/Python-3.6.0.tgz

安装过程没有什么特别的,缺少依赖时可以直接yum安装

1
2
3
4
wget https://www.python.org/ftp/python/3.6.0/Python-3.6.0.tgz
tar -zxcf Python-3.6.0.tgz
./configure
make && make install

源码方式安装完成后,已经是包含pip3的了,不用再单独安装了。

让pip走代理

pip3可以通过使用--proxy参数来走代理,在这里我们为了方便,直接建一个alias,以后使用pip时,就不需要再添加参数了。

1
2
3
alias pip3="pip3 --proxy 134.32.32.13:31081"
export http_proxy=134.32.32.13:31081
export https_proxy=134.32.32.13:31081

安装依赖

大部分依赖可以直接通过pip3安装:

1
2
3
4
pip3 install requests
pip3 install beautifulsoup4
pip3 install mysql-connector
pip3 install fdfs_client-py

某些模块,比如FastDFS客户端,不能直接使用pip3安装,需要下载源码后并手工修改一些代码,操作过程大致如下:

1
2
3
4
5
git clone https://github.com/jefforeilly/fdfs_client-py.git
cd dfs_client-py
vi ./fdfs_client/storage_client.py
将第12行 from fdfs_client.sendfile import * 注释掉
python3 setup.py install

至此,环境搭建完毕,可以将python代码扔上去跑起来了。

什么是Redmine

Redmine是用Ruby On Rails开发的一款基于WEB的项目管理软件。
它集成了项目管理所需的各项功能:日历、甘特图、问题跟踪和版本控制,可以同时处理多个项目。

相比禅道等软件,Redmine的安装还是比较繁琐的,一方面是因为它没有一个自动化的安装脚本,另一方面主要是个人对于Ruby初次接触,需要花一些时间在Ruby的学习上。
本文仅讨论Redmine的部署方式,关于Redmine使用今后会单独写一篇博文来讲解。

相关资源

官网WIKI

主机环境

RedHat/CentOS 6.X

代理服务器配置

如果主机处于内网环境中,无法直连Internet时,需要首先配置可用的网络环境,最简单的方式是使用代理服务器。
关于代理服务器的搭建不再赘述了,可以使用srelay+polipo这样的组合,详细步骤请参考之前的一篇文章《内网主机Python3环境搭建》。

代理服务器配置好之后,我们导入两条环境变量即可。

1
2
3
export http_proxy="http://xxx.xxx.xxx.xxx:31081"
export https_proxy="http://xxx.xxx.xxx.xxx:31081"

这时wget、curl、yum等已经可以正常使用了。

安装依赖

首先使用yum安装一些基本依赖,以下只是一些参考,可以在具体编译时缺什么补什么。

1
yum -y install nano zip unzip libyaml-devel zlib-devel curl-devel openssl-devel httpd-devel apr-devel apr-util-devel mysql-devel gcc ruby-devel gcc-c++ make postgresql-devel ImageMagick-devel sqlite-devel perl-LDAP mod_perl perl-Digest-SHA

安装MySQL

这里为了省事,使用最简单的yum的方式,直接在本机安装一套MySQL。yumd源中的MySQL版本比较老旧,是5.1.73。如果时间充裕的话,建议使用源码或二进制方式进行安装。

1
yum -y install mysql mysql-server

将MySQL设置为开机自启,并手工启动服务:

1
2
chkconfig mysqld on
service mysqld start

初次运行,进行密码等安全性

1
/usr/bin/mysql_secure_installation

Disallow root login remotely这一项可以选NO

1
2
3
4
5
6
Enter current password for root (enter for none):
Set root password? [Y/n] y
Remove anonymous users? [Y/n] y
Disallow root login remotely? [Y/n] n
Remove test database and access to it? [Y/n] y
Reload privilege tables now? [Y/n] y

新建MySQL数据库及用户

直接在本机登录mysql,新建一个数据库及用户

1
2
3
4
mysql -uroot -p
> create database redmine_db character set utf8;
> create user 'redmine_admin'@'localhost' identified by 'your_new_password';
> grant all privileges on redmine_db.* to 'redmine_admin'@'localhost';

关闭SELinux

1
2
3
4
vi /etc/selinux/config
SELINUX=disabled

/usr/sbin/setenforce 0 立刻关闭

配置iptables

配置iptables防火墙,将3000端口打开

1
2
3
vi /etc/sysconfig/iptables

-A INPUT -m state --state NEW -m tcp -p tcp --dport 3000 -j ACCEPT

重启iptables:

1
/etc/init.d/iptables restart

安装Ruby

使用RVM进行Ruby的安装。RVM是一个命令行工具,可以提供一个便捷的多版本Ruby环境的管理和切换。

1
2
curl -L https://get.rvm.io | bash
source /etc/profile.d/rvm.sh

列出已知的Ruby版本:

1
rvm list known

这里我们选择安装2.3.0:

1
rvm install 2.3.0

等待编译安装完成之后,可以查看一下版本号,确认安装成功:

1
2
ruby -v
ruby 2.3.0p0 (2015-12-25 revision 53290) [x86_64-linux]

安装Rubygems

RubyGems简称gems,是一个用于对Ruby组件进行打包的Ruby打包系统。

1
yum -y install rubygems

下载Redmine

我们新建一个redmine用户来进行接下来的操作。首先是从官网下载redmine的安装包并解压:

1
2
3
wget http://www.redmine.org/releases/redmine-3.3.2.tar.gz
tar -zxvf redmine-3.3.2.tar.gz
mv redmine-3.3.2 redmine

配置数据库连接

从示例中复制一份database.yml出来:

1
2
cd /path/to/redmine/config
cp database.yml.example database.yml

编辑database.yml,填写刚才新建的数据库的各项参数:

1
2
3
4
5
6
7
8
9
vi database.yml

production:
adapter: mysql2
database: redmine_db
host: localhost
username: redmine_admin
password: "redmine_password"
encoding: utf8

使用Bundle设置Rails

使用Bundle安装依赖的库

1
2
3
gem install bundler
bundle install
rake generate_secret_token

初始化数据库表及数据

1
2
RAILS_ENV=production rake db:migrate
RAILS_ENV=production rake redmine:load_default_data

设置文件系统权限

1
2
3
mkdir -p tmp tmp/pdf public/plugin_assets
sudo chown -R redmine:redmine files log tmp public/plugin_assets
sudo chmod -R 755 files log tmp public/plugin_assets

运行

1
bundle exec rails server -b 0.0.0.0 webrick -e production -d

推荐学习书目

推荐网站

推荐IDE

其它资料

好用的第三方库

  • requests - HTTP库
  • Flask - RestfulAPI
  • peewee - 读写数据库,替代SQLAlchemy
  • aiohttp - 替代requests
  • BeautifulSoup - HTML解析
  • scipy - 科学计算
  • pandas - 处理csv及excel
  • fdfs_client-py - FastDFS客户端

代码风格规范

参考文档:
Google开源项目风格指南
Python代码规范小结

分号

不要在行尾加分号, 也不要用分号将两条命令放在同一行.

行长度

每行不超过80个字符
例外:

  1. 长的导入模块语句
  2. 注释里的URL

括号

宁缺毋滥的使用括号

1
2
3
4
5
6
7
8
9
10
Yes: if foo:
bar()
while x:
x = bar()
if x and y:
bar()
if not x:
bar()
return foo
for (x, y) in dict.items(): ...
1
2
3
4
5
No:  if (x):
bar()
if not(x):
bar()
return (foo)

缩进

用4个空格来缩进代码,绝对不要用tab, 也不要tab和空格混用

空行

顶级定义之间空两行, 方法定义之间空一行

空格

按照标准的排版规范来使用标点两边的空格
括号内不要有空格

1
2
Yes: spam(ham[1], {eggs: 2}, [])

1
2
No:  spam( ham[ 1 ], { eggs: 2 }, [ ] )

不要在逗号, 分号, 冒号前面加空格, 但应该在它们后面加(除了在行尾).

1
2
3
Yes: if x == 4:
print x, y
x, y = y, x
1
2
3
No:  if x == 4 :
print x , y
x , y = y , x

参数列表, 索引或切片的左括号前不应加空格.

1
Yes: spam(1)
1
2
no: spam (1)

1
Yes: dict['key'] = list[index]
1
No:  dict ['key'] = list [index]

在二元操作符两边都加上一个空格, 比如赋值(=), 比较(==, <, >, !=, <>, <=, >=, in, not in, is, is not), 布尔(and, or, not). 至于算术操作符两边的空格该如何使用, 需要你自己好好判断. 不过两侧务必要保持一致.

1
Yes: x == 1
1
No:  x<1

当’=’用于指示关键字参数或默认参数值时, 不要在其两侧使用空格.

1
2
Yes: def complex(real, imag=0.0): return magic(r=real, i=imag)

1
No:  def complex(real, imag = 0.0): return magic(r = real, i = imag)

不要用空格来垂直对齐多行间的标记, 因为这会成为维护的负担(适用于:, #, =等):

1
2
3
4
5
6
7
8
Yes:
foo = 1000 # comment
long_name = 2 # comment that should not be aligned

dictionary = {
"foo": 1,
"long_name": 2,
}
1
2
3
4
5
6
7
8
No:
foo = 1000 # comment
long_name = 2 # comment that should not be aligned

dictionary = {
"foo" : 1,
"long_name": 2,
}

Shebang

大部分.py文件不必以#!作为文件的开始. 根据 PEP-394 , 程序的main文件应该以 #!/usr/bin/python2或者 #!/usr/bin/python3开始.

注释

文档字符串

Python有一种独一无二的的注释方式: 使用文档字符串. 文档字符串是包, 模块, 类或函数里的第一个语句. 这些字符串可以通过对象的__doc__成员被自动提取, 并且被pydoc所用. 一个文档字符串应该这样组织: 首先是一行以句号, 问号或惊叹号结尾的概述(或者该文档字符串单纯只有一行). 接着是一个空行. 接着是文档字符串剩下的部分, 它应该与文档字符串的第一行的第一个引号对齐.

模块

每个文件应该包含一个许可样板. 根据项目使用的许可(例如, Apache 2.0, BSD, LGPL, GPL), 选择合适的样板.

函数和方法

关于函数的几个方面应该在特定的小节中进行描述记录, 这几个方面如下文所述. 每节应该以一个标题行开始. 标题行以冒号结尾. 除标题行外, 节的其他内容应被缩进2个空格.
Args:
列出每个参数的名字, 并在名字后使用一个冒号和一个空格, 分隔对该参数的描述.如果描述太长超过了单行80字符,使用2或者4个空格的悬挂缩进(与文件其他部分保持一致). 描述应该包括所需的类型和含义. 如果一个函数接受foo(可变长度参数列表)或者**bar (任意关键字参数), 应该详细列出foo和**bar.
Returns: (或者 Yields: 用于生成器)
描述返回值的类型和语义. 如果函数返回None, 这一部分可以省略.
Raises:
列出与接口有关的所有异常.

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
def fetch_bigtable_rows(big_table, keys, other_silly_variable=None):
"""Fetches rows from a Bigtable.

Retrieves rows pertaining to the given keys from the Table instance
represented by big_table. Silly things may happen if
other_silly_variable is not None.

Args:
big_table: An open Bigtable Table instance.
keys: A sequence of strings representing the key of each table row
to fetch.
other_silly_variable: Another optional variable, that has a much
longer name than the other args, and which does nothing.

Returns:
A dict mapping keys to the corresponding table row data
fetched. Each row is represented as a tuple of strings. For
example:

{'Serak': ('Rigel VII', 'Preparer'),
'Zim': ('Irk', 'Invader'),
'Lrrr': ('Omicron Persei 8', 'Emperor')}

If a key from the keys argument is missing from the dictionary,
then that row was not found in the table.

Raises:
IOError: An error occurred accessing the bigtable.Table object.
"""
pass

类应该在其定义下有一个用于描述该类的文档字符串. 如果你的类有公共属性(Attributes), 那么文档中应该有一个属性(Attributes)段. 并且应该遵守和函数参数相同的格式.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class SampleClass(object):
"""Summary of class here.

Longer class information....
Longer class information....

Attributes:
likes_spam: A boolean indicating if we like SPAM or not.
eggs: An integer count of the eggs we have laid.
"""

def __init__(self, likes_spam=False):
"""Inits SampleClass with blah."""
self.likes_spam = likes_spam
self.eggs = 0

def public_method(self):
"""Performs operation blah."""

块注释和行注释

1
2
3
4
5
6
# We use a weighted dictionary search to find out where i is in
# the array. We extrapolate position based on the largest num
# in the array and the array size and then do binary search to
# get the exact number.

if i & (i-1) == 0: # true iff i is a power of 2

如果一个类不继承自其它类, 就显式的从object继承. 嵌套类也一样.

1
2
3
4
5
6
7
8
9
10
11
12
Yes: class SampleClass(object):
pass


class OuterClass(object):

class InnerClass(object):
pass


class ChildClass(ParentClass):
"""Explicitly inherits from another class already."""
1
2
3
4
5
6
7
8
No: class SampleClass:
pass


class OuterClass:

class InnerClass:
pass

字符串

即使参数都是字符串, 使用%操作符或者格式化方法格式化字符串. 不过也不能一概而论, 你需要在+和%之间好好判定.

1
2
3
4
5
Yes: x = a + b
x = '%s, %s!' % (imperative, expletive)
x = '{}, {}!'.format(imperative, expletive)
x = 'name: %s; score: %d' % (name, n)
x = 'name: {}; score: {}'.format(name, n)
1
2
3
4
No: x = '%s%s' % (a, b)  # use + in this case
x = '{}{}'.format(a, b) # use + in this case
x = imperative + ', ' + expletive + '!'
x = 'name: ' + name + '; score: ' + str(n)

避免在循环中用+和+=操作符来累加字符串. 由于字符串是不可变的, 这样做会创建不必要的临时对象, 并且导致二次方而不是线性的运行时间. 作为替代方案, 你可以将每个子串加入列表, 然后在循环结束后用 .join 连接列表. (也可以将每个子串写入一个 cStringIO.StringIO 缓存中.)

1
2
3
4
5
Yes: items = ['<table>']
for last_name, first_name in employee_list:
items.append('<tr><td>%s, %s</td></tr>' % (last_name, first_name))
items.append('</table>')
employee_table = ''.join(items)
1
2
3
4
No: employee_table = '<table>'
for last_name, first_name in employee_list:
employee_table += '<tr><td>%s, %s</td></tr>' % (last_name, first_name)
employee_table += '</table>'

在同一个文件中, 保持使用字符串引号的一致性. 使用单引号’或者双引号”之一用以引用字符串, 并在同一文件中沿用. 在字符串内可以使用另外一种引号, 以避免在字符串中使用.

1
2
3
4
Yes:
Python('Why are you hiding your eyes?')
Gollum("I'm scared of lint errors.")
Narrator('"Good!" thought a happy Python reviewer.')
1
2
3
4
No:
Python("Why are you hiding your eyes?")
Gollum('The lint. It burns. It burns us.')
Gollum("Always the great lint. Watching. Watching.")

为多行字符串使用三重双引号”“”而非三重单引号’‘’. 当且仅当项目中使用单引号’来引用字符串时, 才可能会使用三重’‘’为非文档字符串的多行字符串来标识引用. 文档字符串必须使用三重双引号”“”. 不过要注意, 通常用隐式行连接更清晰, 因为多行字符串与程序其他部分的缩进方式不一致.

1
2
3
Yes:
print ("This is much nicer.\n"
"Do it this way.\n")
1
2
3
4
No:
print """This is pretty ugly.
Don't do this.
"""

文件和sockets

在文件和sockets结束时, 显式的关闭它.
推荐使用 “with”语句 以管理文件:

1
2
3
4
with open("hello.txt") as hello_file:
for line in hello_file:
print line

对于不支持使用”with”语句的类似文件的对象,使用 contextlib.closing():

1
2
3
4
5
import contextlib

with contextlib.closing(urllib.urlopen("http://www.python.org/")) as front_page:
for line in front_page:
print line

TODO注释

1
2
# TODO(kl@gmail.com): Use a "*" here for string repetition.
# TODO(Zeke) Change this to use relations.

导入格式

每个导入应该独占一行

1
2
Yes: import os
import sys
1
No:  import os, sys

每种分组中, 应该根据每个模块的完整包路径按字典序排序, 忽略大小写.

1
2
3
4
5
import foo
from foo import bar
from foo.bar import baz
from foo.bar import Quux
from Foob import ar

语句

通常每个语句应该独占一行
不过, 如果测试结果与测试语句在一行放得下, 你也可以将它们放在同一行. 如果是if语句, 只有在没有else时才能这样做. 特别地, 绝不要对 try/except 这样做, 因为try和except不能放在同一行.

1
2
3
Yes:

if foo: bar(foo)
1
2
3
4
5
6
7
8
9
10
11
No:

if foo: bar(foo)
else: baz(foo)

try: bar(foo)
except ValueError: baz(foo)

try:
bar(foo)
except ValueError: baz(foo)

访问控制

在Python中, 对于琐碎又不太重要的访问函数, 你应该直接使用公有变量来取代它们, 这样可以避免额外的函数调用开销. 当添加更多功能时, 你可以用属性(property)来保持语法的一致性.
另一方面, 如果访问更复杂, 或者变量的访问开销很显著, 那么你应该使用像 get_foo() 和 set_foo() 这样的函数调用. 如果之前的代码行为允许通过属性(property)访问 , 那么就不要将新的访问函数与属性绑定. 这样, 任何试图通过老方法访问变量的代码就没法运行, 使用者也就会意识到复杂性发生了变化.

命名

1
2
3
4
5
6
7
8
9
10
module_name
package_name
ClassName
method_name
ExceptionName
function_name
GLOBAL_VAR_NAME
instance_var_name
function_parameter_name
local_var_name

应该避免的名称:

  1. 单字符名称, 除了计数器和迭代器.
  2. 包/模块名中的连字符(-)
  3. 双下划线开头并结尾的名称(Python保留, 例如init)

命名约定:

  1. 所谓”内部(Internal)”表示仅模块内可用, 或者, 在类内是保护或私有的.
  2. 用单下划线( _ )开头表示模块变量或函数是protected的(使用import * from时不会包含).
  3. 用双下划线( __ )开头的实例变量或方法表示类内私有.
  4. 将相关的类和顶级函数放在同一个模块里. 不像Java, 没必要限制一个类一个模块.
  5. 对类名使用大写字母开头的单词(如CapWords, 即Pascal风格), 但是模块名应该用小写加下划线的方式(如lower_with_under.py). 尽管已经有很多现存的模块使用类似于CapWords.py这样的命名, 但现在已经不鼓励这样做, 因为如果模块名碰巧和类名一致, 这会让人困扰.

Main

即使是一个打算被用作脚本的文件, 也应该是可导入的. 并且简单的导入不应该导致这个脚本的主功能(main functionality)被执行, 这是一种副作用. 主功能应该放在一个main()函数中.

在Python中, pydoc以及单元测试要求模块必须是可导入的. 你的代码应该在执行主程序前总是检查 if name == ‘main‘ , 这样当模块被导入时主程序就不会被执行.

1
2
3
4
5
6
def main():
...

if __name__ == '__main__':
main()

所有的顶级代码在模块导入时都会被执行. 要小心不要去调用函数, 创建对象, 或者执行那些不应该在使用pydoc时执行的操作.

语法备忘

Python解释器

  • CPython - 官方版本的解释,C开发
  • IPython - 基于CPython之上的一个交互式解释器
  • PyPy - 采用JIT技术,对Python代码进行动态编译,提高执行速度
  • Jython - 运行在Java平台
  • IronPython - 运行在微软.Net平台

基本输入输出

1
2
print('hello, world')
name = input()

数据类型

整数

1,100,-8080,0
十六进制 0xff00,0xa5b4c3d2

浮点数

1.23,3.14,-9.01
浮点数运算有误差

字符串

字符串是以单引号’或双引号”括起来的任意文本,比如’abc’,”xyz”
如果字符串内部既包含’又包含”,可以用转义字符\来标识

\n表示换行,\t表示制表符,字符 \ 本身也要转义

Python允许用’’’…’’’的格式表示多行内容

1
2
3
print('''line1
line2
line3''')

布尔值

True False

空值

None

变量

1
2
3
4
a = 123 # a是整数
print(a)
a = 'ABC' # a变为字符串
print(a)

常量

通常用全部大写的变量名表示常量:
PI = 3.14159265359
但事实上PI仍然是一个变量,Python根本没有任何机制保证PI不会被改变

除法

有两种除法,一种除法是/,即使是两个整数恰好整除,结果也是浮点数:

1
2
3
4
5
>>> 10 / 3
3.3333333333333335

>>> 9 / 3
3.0

还有一种除法是//,称为地板除,两个整数的除法仍然是整数:

1
2
>>> 10 // 3
3

余数运算:

1
2
>>> 10 % 3
1

字符串

1
2
3
4
5
6
7
8
>>> ord('A')
65
>>> ord('中')
20013
>>> chr(66)
'B'
>>> chr(25991)
'文'
1
2
3
4
>>> len('ABC')
3
>>> len('中文')
2
1
2
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
1
2
3
4
>>> 'Hello, %s' % 'world'
'Hello, world'
>>> 'Hi, %s, you have $%d.' % ('Michael', 1000000)
'Hi, Michael, you have $1000000.'

%d 整数
%f 浮点数
%s 字符串
%x 十六进制整数

list

list是一种有序的集合,可以随时添加和删除其中的元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> classmates = ['Michael', 'Bob', 'Tracy']
>>> classmates
['Michael', 'Bob', 'Tracy']

>>> len(classmates)
3

>>> classmates[0]
'Michael'
>>> classmates[1]
'Bob'
>>> classmates[2]
'Tracy'
>>> classmates[-1]
'Tracy'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
>>> classmates.append('Adam')
>>> classmates
['Michael', 'Bob', 'Tracy', 'Adam']

>>> classmates.insert(1, 'Jack')
>>> classmates
['Michael', 'Jack', 'Bob', 'Tracy', 'Adam']

>>> classmates.pop()
'Adam'
>>> classmates
['Michael', 'Jack', 'Bob', 'Tracy']

>>> classmates.pop(1)
'Jack'
>>> classmates
['Michael', 'Bob', 'Tracy']

>>> classmates[1] = 'Sarah'
>>> classmates
['Michael', 'Sarah', 'Tracy']

list里面的元素的数据类型也可以不同

1
2
>>> L = ['Apple', 123, True]

list元素也可以是另一个list,比如

1
2
3
>>> s = ['python', 'java', ['asp', 'php'], 'scheme']
>>> len(s)
4

tuple

tuple和list非常类似,但是tuple一旦初始化就不能修改

1
2
>>> classmates = ('Michael', 'Bob', 'Tracy')

只有1个元素的tuple定义时必须加一个逗号,,来消除歧义:

1
2
3
>>> t = (1,)
>>> t
(1,)

条件

1
2
3
4
5
6
7
age = 3
if age >= 18:
print('adult')
elif age >= 6:
print('teenager')
else:
print('kid')

循环

1
2
3
names = ['Michael', 'Bob', 'Tracy']
for name in names:
print(name)
1
2
3
4
5
6
sum = 0
n = 99
while n > 0:
sum = sum + n
n = n - 2
print(sum)

dict

1
2
3
>>> d = {'Michael': 95, 'Bob': 75, 'Tracy': 85}
>>> d['Michael']
95
1
2
>>> 'Thomas' in d
False
1
2
3
4
>>> d.pop('Bob')
75
>>> d
{'Michael': 95, 'Tracy': 85}

set

set和dict类似,也是一组key的集合,但不存储value。由于key不能重复,所以,在set中,没有重复的key。

1
2
3
>>> s = set([1, 1, 2, 2, 3, 3])
>>> s
{1, 2, 3}
1
2
3
4
5
6
>>> s.add(4)
>>> s
{1, 2, 3, 4}
>>> s.add(4)
>>> s
{1, 2, 3, 4}
1
2
3
>>> s.remove(4)
>>> s
{1, 2, 3}

内置函数

1
2
3
4
>>> abs(-20)
20
max(2, 3, 1, -5)
3
1
2
3
4
5
6
7
8
9
10
11
12
>>> int(12.34)
12
>>> float('12.34')
12.34
>>> str(1.23)
'1.23'
>>> str(100)
'100'
>>> bool(1)
True
>>> bool('')
False

定义函数

1
2
def power(x):
return x * x

默认参数

1
2
3
4
5
6
def power(x, n=2):
s = 1
while n > 0:
n = n - 1
s = s * x
return s

可变参数

1
2
3
4
5
6
7
8
9
10
11
12
13
def calc(*numbers):
sum = 0
for n in numbers:
sum = sum + n * n
return sum

>>> calc(1, 2)
5
>>> calc()
0
>>> nums = [1, 2, 3]
>>> calc(*nums)
14

关键字参数

1
2
3
4
5
6
7
8
9
10
11
def person(name, age, **kw):
print('name:', name, 'age:', age, 'other:', kw)

>>> person('Bob', 35, city='Beijing')
name: Bob age: 35 other: {'city': 'Beijing'}
>>> person('Adam', 45, gender='M', job='Engineer')
name: Adam age: 45 other: {'gender': 'M', 'job': 'Engineer'}

>>> extra = {'city': 'Beijing', 'job': 'Engineer'}
>>> person('Jack', 24, **extra)
name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'}

命名关键字参数

1
2
3
4
5
6
7
8
def person(name, age, **kw):
if 'city' in kw:
# 有city参数
pass
if 'job' in kw:
# 有job参数
pass
print('name:', name, 'age:', age, 'other:', kw)

参数组合

1
2
3
4
5
def f1(a, b, c=0, *args, **kw):
print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw)

def f2(a, b, c=0, *, d, **kw):
print('a =', a, 'b =', b, 'c =', c, 'd =', d, 'kw =', kw)

递归函数

1
2
3
4
5
6
7
def fact(n):
return fact_iter(n, 1)

def fact_iter(num, product):
if num == 1:
return product
return fact_iter(num - 1, num * product)

切片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> L = ['Michael', 'Sarah', 'Tracy', 'Bob', 'Jack']

>>> L[0:3]
['Michael', 'Sarah', 'Tracy']

>>> L[:3]
['Michael', 'Sarah', 'Tracy']

>>> L[-2:]
['Bob', 'Jack']

>>> L[-2:-1]
['Bob']

1
2
3
4
5
6
7
8
9
10
11
12
>>> L = list(range(100))
>>> L
[0, 1, 2, 3, ..., 99]

>>> L[:10:2] # 前10个数,每两个取一个:
[0, 2, 4, 6, 8]

>>> L[::5] # 所有数,每5个取一个
[0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95]

>>> L[:] # 原样复制一个list
[0, 1, 2, 3, ..., 99]

迭代

1
2
3
4
5
6
7
>>> d = {'a': 1, 'b': 2, 'c': 3}
>>> for key in d:
... print(key)
...
a
c
b
1
2
3
4
5
6
7
>>> d = {'x': 'A', 'y': 'B', 'z': 'C' }
>>> for k, v in d.items():
... print(k, '=', v)
...
y = B
x = A
z = C
1
2
3
4
5
6
>>> for ch in 'ABC':
... print(ch)
...
A
B
C

判断一个对象是可迭代对象:

1
2
3
4
5
6
7
>>> from collections import Iterable
>>> isinstance('abc', Iterable) # str是否可迭代
True
>>> isinstance([1,2,3], Iterable) # list是否可迭代
True
>>> isinstance(123, Iterable) # 整数是否可迭代
False

列表生成

1
2
3
>>> list(range(1, 11))
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

1
2
>>> [x * x for x in range(1, 11)]
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
1
2
>>> [x * x for x in range(1, 11) if x % 2 == 0]
[4, 16, 36, 64, 100]
1
2
3
>>> d = {'x': 'A', 'y': 'B', 'z': 'C' }
>>> [k + '=' + v for k, v in d.items()]
['y=B', 'x=A', 'z=C']
1
2
3
>>> L = ['Hello', 'World', 'IBM', 'Apple']
>>> [s.lower() for s in L]
['hello', 'world', 'ibm', 'apple']

生成器

这种一边循环一边计算的机制,称为生成器:generator。
只要把一个列表生成式的[]改成(),就创建了一个generator

1
2
3
4
5
6
>>> L = [x * x for x in range(10)]
>>> L
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> g = (x * x for x in range(10))
>>> g
<generator object <genexpr> at 0x1022ef630>

定义模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

' a test module '

__author__ = 'Michael Liao'

import sys

def test():
args = sys.argv
if len(args)==1:
print('Hello, world!')
elif len(args)==2:
print('Hello, %s!' % args[1])
else:
print('Too many arguments!')

if __name__=='__main__':
test()

第三方模块

1
pip install Pillow

默认情况下,Python解释器会搜索当前目录、所有已安装的内置模块和第三方模块,搜索路径存放在sys模块的path变量中:

1
2
3
>>> import sys
>>> sys.path
['', '/Library/Frameworks/Python.framework/Versions/3.4/lib/python34.zip', '/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4', '/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/plat-darwin', '/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/lib-dynload', '/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/site-packages']

添加自己的搜索目录,一是直接修改sys.path,添加要搜索的目录:

1
2
>>> import sys
>>> sys.path.append('/Users/michael/my_py_scripts')

第二种方法是设置环境变量PYTHONPATH,该环境变量的内容会被自动添加到模块搜索路径中。

面向对象

1
2
3
4
5
6
7
8
9
10
11
12
class Student(object):

def __init__(self, name, score):
self.name = name
self.score = score

>>> bart = Student('Bart Simpson', 59)
>>> bart.name
'Bart Simpson'
>>> bart.score
59

1
2
3
4
5
6
7
8
9
10
11
class Student(object):

def __init__(self, name, score):
self.name = name
self.score = score

def print_score(self):
print('%s: %s' % (self.name, self.score))

>>> bart.print_score()
Bart Simpson: 59

访问限制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Student(object):

def __init__(self, name, score):
self.__name = name
self.__score = score

def print_score(self):
print('%s: %s' % (self.__name, self.__score))

def get_name(self):
return self.__name

def get_score(self):
return self.__score

def set_score(self, score):
self.__score = score

继承和多态

1
2
3
class Animal(object):
def run(self):
print('Animal is running...')
1
2
3
4
5
6
7
8
9
class Dog(Animal):

def run(self):
print('Dog is running...')

class Cat(Animal):

def run(self):
print('Cat is running...')
1
2
3
4
5
6
7
>>> isinstance(a, list)
True
>>> isinstance(b, Animal)
True
>>> isinstance(c, Dog)
True

获取对象信息

1
2
3
4
5
6
7
8
9
10
11
>>> type(123)
<class 'int'>
>>> type('str')
<class 'str'>
>>> type(None)
<type(None) 'NoneType'>
>>> type(abs)
<class 'builtin_function_or_method'>
>>> type(a)
<class '__main__.Animal'>

1
2
3
4
5
6
7
8
9
10
11
12
>>> import types
>>> def fn():
... pass
...
>>> type(fn)==types.FunctionType
True
>>> type(abs)==types.BuiltinFunctionType
True
>>> type(lambda x: x)==types.LambdaType
True
>>> type((x for x in range(10)))==types.GeneratorType
True
1
2
3
4
5
6
7
8
>>> isinstance(h, Husky)
True
>>> isinstance('a', str)
True
>>> isinstance(123, int)
True
>>> isinstance(b'a', bytes)
True
1
2
3

>>> dir('ABC')
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']

实例属性和类属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
>>> class Student(object):
... name = 'Student'
...
>>> s = Student() # 创建实例s
>>> print(s.name) # 打印name属性,因为实例并没有name属性,所以会继续查找class的name属性
Student
>>> print(Student.name) # 打印类的name属性
Student
>>> s.name = 'Michael' # 给实例绑定name属性
>>> print(s.name) # 由于实例属性优先级比类属性高,因此,它会屏蔽掉类的name属性
Michael
>>> print(Student.name) # 但是类属性并未消失,用Student.name仍然可以访问
Student
>>> del s.name # 如果删除实例的name属性
>>> print(s.name) # 再次调用s.name,由于实例的name属性没有找到,类的name属性就显示出来了
Student

错误处理

1
2
3
4
5
6
7
8
9
10
try:
print('try...')
r = 10 / 0
print('result:', r)
except ZeroDivisionError as e:
print('except:', e)
finally:
print('finally...')
print('END')

调试

1
2
3
4
5
6
7
def foo(s):
n = int(s)
assert n != 0, 'n is zero!'
return 10 / n

def main():
foo('0')
1
2
3
4
5
6
import logging

s = '0'
n = int(s)
logging.info('n = %d' % n)
print(10 / n)
1
2
$ python3 -m pdb err.py

文件读写

读文件

1
2
3
4
>>> f = open('/Users/michael/test.txt', 'r')
>>> f.read()
'Hello, world!'
>>> f.close()
1
2
with open('/path/to/file', 'r') as f:
print(f.read())
1
2
for line in f.readlines():
print(line.strip())

二进制文件

1
2
3
>>> f = open('/Users/michael/test.jpg', 'rb')
>>> f.read()
b'\xff\xd8\xff\xe1\x00\x18Exif\x00\x00...' # 十六进制表示的字节

指定字符编码

1
2
3
>>> f = open('/Users/michael/gbk.txt', 'r', encoding='gbk')
>>> f.read()
'测试'

写文件

1
2
3
>>> f = open('/Users/michael/test.txt', 'w')
>>> f.write('Hello, world!')
>>> f.close()
1
2
with open('/Users/michael/test.txt', 'w') as f:
f.write('Hello, world!')

操作文件与目录

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
33
34
35
36
37
>>> import os
>>> os.name # 操作系统类型
'posix'

>>> os.uname()
posix.uname_result(sysname='Darwin', nodename='MichaelMacPro.local', release='14.3.0', version='Darwin Kernel Version 14.3.0: Mon Mar 23 11:59:05 PDT 2015; root:xnu-2782.20.48~5/RELEASE_X86_64', machine='x86_64')

>>> os.environ
environ({'VERSIONER_PYTHON_PREFER_32_BIT': 'no', 'TERM_PROGRAM_VERSION': '326', 'LOGNAME': 'michael', 'USER': 'michael', 'PATH': '/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/opt/X11/bin:/usr/local/mysql/bin', ...})

>>> os.environ.get('PATH')
'/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/opt/X11/bin:/usr/local/mysql/bin'
>>> os.environ.get('x', 'default')
'default'

# 查看当前目录的绝对路径:
>>> os.path.abspath('.')
'/Users/michael'
# 在某个目录下创建一个新目录,首先把新目录的完整路径表示出来:
>>> os.path.join('/Users/michael', 'testdir')
'/Users/michael/testdir'
# 然后创建一个目录:
>>> os.mkdir('/Users/michael/testdir')
# 删掉一个目录:
>>> os.rmdir('/Users/michael/testdir')

>>> os.path.split('/Users/michael/testdir/file.txt')
('/Users/michael/testdir', 'file.txt')

>>> os.path.splitext('/path/to/file.txt')
('/path/to/file', '.txt')


# 对文件重命名:
>>> os.rename('test.txt', 'test.py')
# 删掉文件:
>>> os.remove('test.py')

内建模块datatime

获取当前日期和时间

1
2
3
4
>>> from datetime import datetime
>>> now = datetime.now() # 获取当前datetime
>>> print(now)
2015-05-18 16:28:07.198690

获取指定日期和时间

1
2
3
4
>>> from datetime import datetime
>>> dt = datetime(2015, 4, 19, 12, 20) # 用指定日期时间创建datetime
>>> print(dt)
2015-04-19 12:20:00

datetime转换为timestamp

1
2
3
4
>>> from datetime import datetime
>>> dt = datetime(2015, 4, 19, 12, 20) # 用指定日期时间创建datetime
>>> dt.timestamp() # 把datetime转换为timestamp
1429417200.0

timestamp转换为datetime

1
2
3
4
>>> from datetime import datetime
>>> t = 1429417200.0
>>> print(datetime.fromtimestamp(t))
2015-04-19 12:20:00

str转换为datetime

1
2
3
4
>>> from datetime import datetime
>>> cday = datetime.strptime('2015-6-1 18:19:59', '%Y-%m-%d %H:%M:%S')
>>> print(cday)
2015-06-01 18:19:59

datetime转换为str

1
2
3
4
>>> from datetime import datetime
>>> now = datetime.now()
>>> print(now.strftime('%a, %b %d %H:%M'))
Mon, May 05 16:28

datetime加减

1
2
3
4
5
6
7
8
9
10
>>> from datetime import datetime, timedelta
>>> now = datetime.now()
>>> now
datetime.datetime(2015, 5, 18, 16, 57, 3, 540997)
>>> now + timedelta(hours=10)
datetime.datetime(2015, 5, 19, 2, 57, 3, 540997)
>>> now - timedelta(days=1)
datetime.datetime(2015, 5, 17, 16, 57, 3, 540997)
>>> now + timedelta(days=2, hours=12)
datetime.datetime(2015, 5, 21, 4, 57, 3, 540997)

内建模块base64

Base64是一种用64个字符来表示任意二进制数据的方法。
Base64编码会把3字节的二进制数据编码为4字节的文本数据,长度增加33%。
如果二进制数据不是3的倍数,最后剩下1个或2个字节,Base64用\x00字节在末尾补足后,再在编码的末尾加上1个或2个=号,表示补了多少字节,解码的时候,会自动去掉。

1
2
3
4
5
>>> import base64
>>> base64.b64encode(b'binary\x00string')
b'YmluYXJ5AHN0cmluZw=='
>>> base64.b64decode(b'YmluYXJ5AHN0cmluZw==')
b'binary\x00string'

内建模块hashlib

hashlib提供了常见的摘要算法,如MD5,SHA1等等

1
2
3
4
5
import hashlib
md5 = hashlib.md5()
md5.update('how to use md5 in python hashlib?'.encode('utf-8'))
print(md5.hexdigest())
d26a53750bc40b38b65a520292f69306

内建模块contextlib

使用try...finally关闭资源

1
2
3
4
5
6
try:
f = open('/path/to/file', 'r')
f.read()
finally:
if f:
f.close()

使用with关闭资源,必须实现了上下文管理(通过__enter__和__exit__)

1
2
with open('/path/to/file', 'r') as f:
f.read()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

class Query(object):

def __init__(self, name):
self.name = name

def __enter__(self):
print('Begin')
return self

def __exit__(self, exc_type, exc_value, traceback):
if exc_type:
print('Error')
else:
print('End')

def query(self):
print('Query info about %s...' % self.name)

with Query('Bob') as q:
q.query()

@contextmanager,用于简化enter__和__exit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from contextlib import contextmanager

class Query(object):

def __init__(self, name):
self.name = name

def query(self):
print('Query info about %s...' % self.name)

@contextmanager
def create_query(name):
print('Begin')
q = Query(name)
yield q
print('End')


with create_query('Bob') as q:
q.query()

望在某段代码执行前后自动执行特定代码,也可以用@contextmanager实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@contextmanager
def tag(name):
print("<%s>" % name)
yield
print("</%s>" % name)

with tag("h1"):
print("hello")
print("world")

<h1>
hello
world
</h1>

@closing,把对象变为上下文对象

1
2
3
4
5
6
from contextlib import closing
from urllib.request import urlopen

with closing(urlopen('https://www.python.org')) as page:
for line in page:
print(line)

内建模块urllib

1
2
3
4
5
6
7
from urllib import request
with request.urlopen('https://api.douban.com/v2/book/2129650') as f:
data = f.read()
print('Status:', f.status, f.reason)
for k, v in f.getheaders():
print('%s: %s' % (k, v))
print('Data:', data.decode('utf-8'))
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
from urllib import request, parse

print('Login to weibo.cn...')
email = input('Email: ')
passwd = input('Password: ')
login_data = parse.urlencode([
('username', email),
('password', passwd),
('entry', 'mweibo'),
('client_id', ''),
('savestate', '1'),
('ec', ''),
('pagerefer', 'https://passport.weibo.cn/signin/welcome?entry=mweibo&r=http%3A%2F%2Fm.weibo.cn%2F')
])

req = request.Request('https://passport.weibo.cn/sso/login')
req.add_header('Origin', 'https://passport.weibo.cn')
req.add_header('User-Agent', 'Mozilla/6.0 (iPhone; CPU iPhone OS 8_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/8.0 Mobile/10A5376e Safari/8536.25')
req.add_header('Referer', 'https://passport.weibo.cn/signin/login?entry=mweibo&res=wel&wm=3349&r=http%3A%2F%2Fm.weibo.cn%2F')

with request.urlopen(req, data=login_data.encode('utf-8')) as f:
print('Status:', f.status, f.reason)
for k, v in f.getheaders():
print('%s: %s' % (k, v))
print('Data:', f.read().decode('utf-8'))

urllib可以用requests替代

第三方模块PIL

安装

1
sudo pip3 install pillow

图像缩放

1
2
3
4
5
6
7
8
9
10
11
12
from PIL import Image

# 打开一个jpg图像文件,注意是当前路径:
im = Image.open('test.jpg')
# 获得图像尺寸:
w, h = im.size
print('Original image size: %sx%s' % (w, h))
# 缩放到50%:
im.thumbnail((w//2, h//2))
print('Resize image to: %sx%s' % (w//2, h//2))
# 把缩放后的图像用jpeg格式保存:
im.save('thumbnail.jpg', 'jpeg')

生成字母验证码

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
33
from PIL import Image, ImageDraw, ImageFont, ImageFilter
import random

# 随机字母:
def rndChar():
return chr(random.randint(65, 90))

# 随机颜色1:
def rndColor():
return (random.randint(64, 255), random.randint(64, 255), random.randint(64, 255))

# 随机颜色2:
def rndColor2():
return (random.randint(32, 127), random.randint(32, 127), random.randint(32, 127))

# 240 x 60:
width = 60 * 4
height = 60
image = Image.new('RGB', (width, height), (255, 255, 255))
# 创建Font对象:
font = ImageFont.truetype('Arial.ttf', 36)
# 创建Draw对象:
draw = ImageDraw.Draw(image)
# 填充每个像素:
for x in range(width):
for y in range(height):
draw.point((x, y), fill=rndColor())
# 输出文字:
for t in range(4):
draw.text((60 * t + 10, 10), rndChar(), font=font, fill=rndColor2())
# 模糊:
image = image.filter(ImageFilter.BLUR)
image.save('code.jpg', 'jpeg')

图形界面

Python支持多种图形界面的第三方库,包括:

  • Tk
  • wxWidgets
  • Qt
  • GTK
    Python自带的库是支持Tk的Tkinter,使用Tkinter,无需安装任何包
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from tkinter import *
import tkinter.messagebox as messagebox

class Application(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
self.pack()
self.createWidgets()

def createWidgets(self):
self.nameInput = Entry(self)
self.nameInput.pack()
self.alertButton = Button(self, text='Hello', command=self.hello)
self.alertButton.pack()

def hello(self):
name = self.nameInput.get() or 'world'
messagebox.showinfo('Message', 'Hello, %s' % name)

app = Application()
# 设置窗口标题:
app.master.title('Hello World')
# 主消息循环:
app.mainloop()

发送邮件

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
33
34
35
36
37
38
39
40
41
42
43
44
from email import encoders
from email.header import Header
from email.mime.text import MIMEText
from email.utils import parseaddr, formataddr

import smtplib

def _format_addr(s):
name, addr = parseaddr(s)
return formataddr((Header(name, 'utf-8').encode(), addr))

from_addr = input('From: ')
password = input('Password: ')
to_addr = input('To: ')
smtp_server = input('SMTP server: ')

msg = MIMEText('hello, send by Python...', 'plain', 'utf-8')
msg['From'] = _format_addr('Python爱好者 <%s>' % from_addr)
msg['To'] = _format_addr('管理员 <%s>' % to_addr)
msg['Subject'] = Header('来自SMTP的问候……', 'utf-8').encode()

# 邮件正文是MIMEText:
msg.attach(MIMEText('send with file...', 'plain', 'utf-8'))

# 添加附件就是加上一个MIMEBase,从本地读取一个图片:
with open('/Users/michael/Downloads/test.png', 'rb') as f:
# 设置附件的MIME和文件名,这里是png类型:
mime = MIMEBase('image', 'png', filename='test.png')
# 加上必要的头信息:
mime.add_header('Content-Disposition', 'attachment', filename='test.png')
mime.add_header('Content-ID', '<0>')
mime.add_header('X-Attachment-Id', '0')
# 把附件的内容读进来:
mime.set_payload(f.read())
# 用Base64编码:
encoders.encode_base64(mime)
# 添加到MIMEMultipart:
msg.attach(mime)

server = smtplib.SMTP(smtp_server, 25)
server.set_debuglevel(1)
server.login(from_addr, password)
server.sendmail(from_addr, [to_addr], msg.as_string())
server.quit()

MySQL

1
sudo pip3 install mysql-connector
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 导入MySQL驱动:
>>> import mysql.connector
# 注意把password设为你的root口令:
>>> conn = mysql.connector.connect(user='root', password='password', database='test')
>>> cursor = conn.cursor()
# 创建user表:
>>> cursor.execute('create table user (id varchar(20) primary key, name varchar(20))')
# 插入一行记录,注意MySQL的占位符是%s:
>>> cursor.execute('insert into user (id, name) values (%s, %s)', ['1', 'Michael'])
>>> cursor.rowcount
1
# 提交事务:
>>> conn.commit()
>>> cursor.close()
# 运行查询:
>>> cursor = conn.cursor()
>>> cursor.execute('select * from user where id = %s', ('1',))
>>> values = cursor.fetchall()
>>> values
[('1', 'Michael')]
# 关闭Cursor和Connection:
>>> cursor.close()
True
>>> conn.close()

多进程

1
2
3
4
5
6
7
8
9
import os

print('Process (%s) start...' % os.getpid())
# Only works on Unix/Linux/Mac:
pid = os.fork()
if pid == 0:
print('I am child process (%s) and my parent is %s.' % (os.getpid(), os.getppid()))
else:
print('I (%s) just created a child process (%s).' % (os.getpid(), pid))

multiprocessing模块就是跨平台版本的多进程模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from multiprocessing import Process
import os

# 子进程要执行的代码
def run_proc(name):
print('Run child process %s (%s)...' % (name, os.getpid()))

if __name__=='__main__':
print('Parent process %s.' % os.getpid())
p = Process(target=run_proc, args=('test',))
print('Child process will start.')
p.start()
p.join()
print('Child process end.')

用进程池的方式批量创建子进程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from multiprocessing import Pool
import os, time, random

def long_time_task(name):
print('Run task %s (%s)...' % (name, os.getpid()))
start = time.time()
time.sleep(random.random() * 3)
end = time.time()
print('Task %s runs %0.2f seconds.' % (name, (end - start)))

if __name__=='__main__':
print('Parent process %s.' % os.getpid())
p = Pool(4)
for i in range(5):
p.apply_async(long_time_task, args=(i,))
print('Waiting for all subprocesses done...')
p.close()
p.join()
print('All subprocesses done.')

peewee简介

peewee是Python一个ORM框架,相比于著名的SQLAlchemy,peewee更为轻量,且更加简单易上手。
peewee原生支持sqlite、mysql以及postgresql。

学习资源

peewee官网
GitHub页面

数据建模

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from peewee import *
import datetime
from playhouse.sqlite_ext import SqliteExtDatabase

db = MySQLDatabase(host='localhost', user='playground', passwd='playground', database='playground')
# db = SqliteExtDatabase('my_database.db')

class BaseModel(Model):
class Meta:
database = db

# 用户表
class User(BaseModel):
username = CharField(unique=True)

# Twitter表
class Tweet(BaseModel):
user = ForeignKeyField(User, related_name='tweets')
message = TextField()
created_date = DateTimeField(default=datetime.datetime.now)
is_published = BooleanField(default=True)

支持的字段类型

  • CharField varchar varchar varchar
  • FixedCharField char char char
  • TextField text text longtext
  • DateTimeField datetime timestamp datetime
  • IntegerField integer integer integer
  • BooleanField integer boolean bool
  • FloatField real real real
  • DoubleField real double precision double precision
  • BigIntegerField integer bigint bigint
  • SmallIntegerField integer smallint smallint
  • DecimalField decimal numeric numeric
  • PrimaryKeyField integer serial integer
  • ForeignKeyField integer integer integer
  • DateField date date date
  • TimeField time time time
  • TimestampField integer integer integer
  • BlobField blob bytea blob
  • UUIDField text uuid varchar(40)
  • BareField untyped not supported not supported

支持的参数

  • null = False – boolean indicating whether null values are allowed to be stored
  • index = False – boolean indicating whether to create an index on this column
  • unique = False – boolean indicating whether to create a unique index on this column. See - also adding composite indexes.
  • verbose_name = None – string representing the “user-friendly” name of this field
  • help_text = None – string representing any helpful text for this field
  • db_column = None – string representing the underlying column to use if different, useful - for legacy databases
  • default = None – any value to use as a default for uninitialized models
  • choices = None – an optional iterable containing 2-tuples of value, display
  • primary_key = False – whether this field is the primary key for the table
  • sequence = None – sequence to populate field (if backend supports it)
  • constraints = None - a list of one or more constraints, e.g. [Check(‘price > 0’)]
  • schema = None – optional name of the schema to use, if your db supports this.

    特殊的参数

  • CharField max_length
  • FixedCharField max_length
  • DateTimeField formats
  • DateField formats
  • TimeField formats
  • TimestampField resolution, utc
  • DecimalField max_digits, decimal_places, auto_round, rounding
  • ForeignKeyField rel_model, related_name, to_field, on_delete, on_update, extra
  • BareField coerce

连接数据库并生成表

1
2
3
def create_table():
db.connect()
db.create_tables([User, Tweet])

常用操作

插入记录

1
2
3
4
5
6
7
charlie = User.create(username='charlie')

huey = User(username='huey')
huey.save()

# 不需要设置 `is_published` 或 `created_date`,保存时会自动使用模型中指定的默认值
Tweet.create(user=charlie, message='My first tweet')

查询单条记录

1
user = User.get(User.id == 1)
1
user = User.get(User.username == 'charlie')

查询多条记录

1
2
for user in User.select():
print(user.username)

多条件查询

1
Tweet.select().where(Tweet.user == user, Tweet.is_published == True)

in查询,使用”<<”来代入多个条件

1
2
usernames = ['charlie', 'huey', 'mickey']
users = User.select().where(User.username << usernames)

join查询

1
2
3
4
tweets = (Tweet
.select()
.join(User)
.where(User.username << usernames))

count

1
Tweet.select().where(Tweet.id > 50).count()

count今天发布了几条Twitter

1
2
3
4
5
6
tweets_today = (Tweet
.select()
.where(
(Tweet.created_date >= datetime.date.today()) &
(Tweet.is_published == True))
.count())

排序及分页

1
2
User.select().order_by(User.username).paginate(3, 20)
Tweet.select().join(User).order_by(User.username, Tweet.created_date.desc())

join和gruopby,将用户按Twitter数排序

1
2
3
4
5
6
tweet_ct = fn.Count(Tweet.id)
users = (User
.select(User, tweet_ct.alias('ct'))
.join(Tweet, JOIN.LEFT_OUTER)
.group_by(User)
.order_by(tweet_ct.desc()))

更新

1
2
3
4
5
User.update(username='foo').where(User.id == 2).execute()

user = User.get(User.id == 1)
user.username='bar'
user.save()

删除

1
2
3
4
Tweet.delete().where(id==1).execute()

user = User.get(User.id == 8)
user.delete_instance()

现象

  1. 直接使用foobar用户SSH登录正常
  2. 在root用户下,执行su - foobar,报错:
1
su: cannot set user id: Resource temporarily unavailable

只有su的时候报错,虽然不影响foobar用户正常的SSH远程登录,但这种资源受限的错误,一般都是系统崩溃的前兆,必须要较点真。

系统:RHEL 6.5

排查过程

先按套路出牌

首先直接一通free -mtopvmstat等等,试试运气
不过也不出所料,并没有什么收获。但这也算是个好消息,至少说明不像是系统级别的资源问题了。

感觉八成是线程数或者文件打开数搞的鬼了,懒得监控日志,接着排查。

查看线程数

1
2
cat /proc/sys/kernel/threads-max
254529

最大线程数254529,没有问题

1
2
ps -u foobar -L | wc -l
1045

用户当前线程数1045,虽然挺高了,但这个用户下部署的应用也非常多,算是正常吧。

检查ulimit

  1. 先用ulimit看一下设置的最大文件打开数
    用foobar直接登录,然后执行:

    1
    2
    ulimit -n
    655360

    显示655360,没有问题

  2. 然后用lsof统计一下当前的文件打开数

    1
    2
    lsof | grep foobar | wc -l
    1690

    1690,大于1024有点多了,接下来仔细看一下。

  3. lsof大概的逐条分析一下

    1
    2
    3
    4
    lsof | grep foobar
    blablabla...
    blablabla...
    blablabla...

    既有文件,也有网络连接。不像是那种同时发起了大量网络连接造成的故障。这里算是PASS了。

检查limits.conf配置

走到这里,还没查出问题,就有点意外了,索性再检查一下limits.conf

1
2
3
4
5
6
7
8
cat /etc/security/limits.conf

* soft nofile 655360
* hard nofile 655360
* soft noproc 655360
* hard noproc 655360
* soft core 0
* hard core 0

都是655360,一眼看上去没有问题(但这里有个坑,稍后会提到)

检查各个profile配置

再检查各个profile的配置,例如.bash_profile里是不是手工设置了过小的ulimit,导致登录失败。

1
2
cat /etc/profile
cat /foobar/.bash_profile

都很正常,并没有添加多余的东西进去

监控系统secure日志

到这时都没发现出问题,只能监控系统日志/var/log/secure了。
一边监控,一边执行su - foobar

1
2
3
4
5
6
tail -f /var/log/secure
blablabla...

Mar 30 12:08:02 localhost crond[29226]: pam_limits(crond:session): unknown limit item 'noproc'

blablabla...

终于抓到一个问题:noproc参数是个什么鬼,应该是nproc才对吧,估计管理员当时是直接手撸的,没有复制粘帖。

于是赶紧修改limits.conf

1
vi /etc/security/limits.conf

1
2
*    soft    noproc    655360
* hard noproc 655360

改为

1
2
*    soft    nproc    655360
* hard nproc 655360

山穷水尽

解决完上面那个问题,以为这下可以su成功了吧,然而并没有,报错依旧。

于是继续监控/var/log/secure日志。现在报错变成了:

1
2
Mar 30 12:10:25 localhost su: pam_unix(su-l:session): session opened for user foobar by monitor(uid=0)
Mar 30 12:10:25 localhost su: pam_unix(su-l:session): session closed for user foobar

这种报错,相当于什么也没说啊。看来接下来,想要解决问题,只能靠猜了。

一通google之后,找到了这么一句话:

In Red Hat Enterprise Linux 6, there’s a default setting in /etc/security/limits.d/90-nproc.conf. Change the default limit for all users or add a new limit for the affected user.

这个limits.d/90-nproc.conf之前从没有注意过,难道它会覆盖 limits.conf 吗?

1
2
3
cat /etc/security/limits.d/90-nproc.conf
* soft nproc 1024
root soft nproc unlimited

这里的值是1024,尝试改大一点,碰碰运气:

1
2
3
vi /etc/security/limits.d/90-nproc.conf
* soft nproc 10240
root soft nproc unlimited

然后su - foobar ,竟然成功了。

经验值+3

到此为止,两个坑都已经被填上了。耗时15分钟,经验值+3:

  1. 遇到类似问题,直接监控/var/log/secure,可以少走些弯路
  2. 注意/etc/security/limits.conf的参数拼写
  3. 竟然还有/etc/security/limits.d/90-nproc.conf这么个东西,且生效的时机诡异(只影响su,直接登录不受影响)