BlueXIII's Blog

热爱技术,持续学习

0%

Python3个人学习笔记

推荐学习书目

推荐网站

推荐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.')