我在学习完C++后开始学习的python,因此本文主要记录了在学习python时的笔记,以及python语法与c++的一些区别。
数据类型
- 字符串可以用
''
或者""
引起来,当使用三个
单引号/双引号时,字符串可以换行。 r''
表示’’内部的字符串默认不转义.- None表示空值
- and表示&& ,or表示|| , not 表示!
- 变量可以是任何数据类型,使用时不需要先声明。
=
可以把任意数据类型赋值给变量,同一个变量可以反复赋值,而且可以是不同类型的变量。这种变量本身类型不固定的语言称之为动态语言 - 在Python中,通常用全部大写的变量名表示常量,如
PI = 3.14159265359
。但事实上它仍然是一个变量,Python根本没有任何机制保证其不会被改变。 /
除法计算结果是浮点数,即使是两个整数恰好整除,结果也是浮点数。//
称为地板除,是两个整数的除法仍然是整数。- python中,字符串是以Unicode编码的,
b'abc'
表示bytes类型的数据,ord(str)
将获取字符的整数表示,char(num)
把编码转换为对应的字符.字符串.encode('类型')
用于编码,字符串.decode('类型')
用于解码 - 字符格式化:%d 整数 %f 浮点数 %s 字符串 %x 十六进制整数,还可以指定是否补0和整数与小数的位数:
print('%2d-%05d' % (3, 1))
->3-00001
,print('%.2f' % 3.1415926)
->3.14
, %%
为转义%
- 使用format也可以格式化。
'Hello, {0}, 成绩提升了 {1:.1f}%'.format('小明', 17.125)
- set是一组没有重复key的集合,不存储value,不可以放入可变对象。要创建一个set,需要提供一个list作为输入集合.通过
add(key)
添加元素,remove(key)
删除元素。s1 & s2
做交集,s1 | s2
做并集 对于不变对象来说,调用对象自身的任意方法,也不会改变该对象自身的内容。相反,这些方法会创建新的对象并返回。
1
2
3
4
5
6>>> a = 'abc'
>>> b = a.replace('a', 'A')
>>> b
'Abc'
>>> a
'abc'[l.lower() for l in L1 if isinstance(l ,str)]
[m + n for m in 'ABC' for n in 'XYZ']
列表生成式。前面写要生成的元素,后面写条件,中的的循环可以有多个。- 生成器的两种方法:(1)按列表生成式的方法,把中括号换为小括号。(2)函数中使用
yield
,此时要注意,函数在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。此外,但是用for循环调用generator时,发现拿不到generator的return语句的返回值。如果想要拿到返回值,必须捕获StopIteration错误,返回值包含在StopIteration的value中:1
2
3
4
5
6
7
8g = fib(6)
while True:
try:
x = next(g)
print('g:', x)
except StopIteration as e:
print('Generator return value:', e.value)
break
语句
if语句
1
2
3
4
5
6
7
8if <条件判断1>:
<执行1>
elif <条件判断2>:
<执行2>
elif <条件判断3>:
<执行3>
else:
<执行4>循环语句(只有for语句和while语句)
1
2(1) for x in ...:
(2) while ...:默认情况下,dict迭代的是key。如果要迭代value,可以用
for value in d.values()
,如果要同时迭代key和value,可以用for k, v in d.items()
。可以通过collections模块的Iterable类型判断对象是否可迭代(是否是可迭代对象):
1
2from collections import Iterable
isinstance('abc', Iterable) # str是否可迭代可以使用isinstance()判断一个对象是否是Iterator(迭代器)对象(可以被next()函数调用并不断返回下一个值的对象):
1
2from collections import Iterator
isinstance((x for x in range(10)), Iterator)生成器都是Iterator对象,但list、dict、str虽然是Iterable,却不是Iterator。把list、dict、str等Iterable变成Iterator可以使用iter()函数
- 可以内置的enumerate函数可以把一个list变成索引-元素对,这样就可以在for循环中同时迭代索引和元素本身。
1
2for i, value in enumerate(['A', 'B', 'C']):
print(i, value)
函数
- 函数名其实就是指向一个函数对象的引用,即函数名是一个变量!完全可以把函数名赋给一个变量,相当于给这个函数起了一个“别名”。
f=abs
。也可以abs=10
,注意,由于abs函数实际上是定义在import builtins
模块中的,所以要让修改abs变量的指向在其它模块也生效,要用import builtins; builtins.abs = 10
。 如果想定义一个什么事也不做的空函数,可以用pass语句:
1
2def nop():
pass数据类型检查可以用内置函数isinstance()实现:
1
2
3def my_abs(x):
if not isinstance(x, (int, float)):
raise TypeError('bad operand type')可以返回多个值!但事实上,返回值是一个tuple!但是,在语法上,返回一个tuple可以省略括号,而多个变量可以同时接收一个tuple,按位置赋给对应的值,所以,Python的函数返回多值其实就是返回一个tuple,但写起来更方便。
1
2
3>>> x, y = move(100, 100, 60, math.pi / 6)
>>> print(x, y)
151.96152422706632 70.0def power(x, n=2):
:默认参数,!!默认参数必须指向不变对象!!调用时如果不按顺序提供部分默认参数时,需要把参数名写上。def calc(*numbers):
:可变参数,此时,参数numbers接收到的是一个tuple。如果实参为list或tuple,可以在list或tuple实参前加*
,把它变为可变参数传进去。def person(name, age, **kw):
: 关键字参数,允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。可以扩展函数的功能。def person(name, age, *, city, job):
:命名关键字参数,用分隔,调用方式同上,但此时被限制了关键字参数的名字。命名关键字参数可以有缺省值。如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符了:def person(name, age, *args, city, job):
参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数。- 解决递归调用栈溢出的方法是通过尾递归优化,尾递归是指,在函数返回的时候,调用自身本身,并且,return语句不能包含表达式。这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。
- 一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。
- map()函数接收两个参数,一个是
函数
,一个是Iterable
,map将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator
返回 - reduce把一个函数作用在一个序列
[x1, x2, x3, ...]
上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算,其效果就是:reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
,要导入包from functools import reduce
- filter()接收一个函数和一个序列。和map()不同的是,filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素。
- sorted()函数也是一个高阶函数,它可以接收一个key函数来实现自定义的排序,可以接收reverse=True进行反向排序.
sorted([36, 5, -12, 9, -21], key=abs,reverse=True)
- 函数可以作为返回值
- 内部函数可以引用外部函数的参数和局部变量,当外部函回函数内部函数时,相关参数和变量都保存在返回的函数中,这种称为“闭包(Closure)”的程序结构拥有极大的威力。返回闭包时牢记一点:返回函数不要引用任何循环变量,或者后续会发生变化的变量。
- 关键字lambda表示匿名函数,冒号前面的x表示函数参数。匿名函数有个限制,就是只能有一个表达式,不用写return,返回值就是该表达式的结果。
- 函数对象有一个name属性,可以拿到函数的名字
在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)
1
2
3
4
5
6
7import functools
def log(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper1
2
3
4
5
6
7
8
9import functools
def log(text):
def decorator(func):
def wrapper(*args, **kw):
print('%s %s():' % (text, func.__name__))
return func(*args, **kw)
return wrapper
return decoratorfunctools.partial可以帮助我们创建偏函数。
int2 = functools.partial(int, base=2)
,max2 = functools.partial(max, 10)
模块
- 每一个包目录下面都会有一个init.py的文件,这个文件是必须存在的,否则,Python就把这个目录当成普通目录,而不是一个包。init.py可以是空文件,也可以有Python代码,因为init.py本身就是一个模块,而它的模块名就是mycompany。
- 当我们在命令行运行一个模块文件时,Python解释器把一个特殊变量name置为main,而如果在其他地方导入该模块时,if判断将失败,因此,这种if测试可以让一个模块通过命令行运行时执行一些额外的代码,最常见的就是运行测试。
- 正常的函数和变量名是公开的(public),可以被直接引用,比如:abc,x123,PI等。
类似xxx这样的变量是特殊变量,可以被直接引用,但是有特殊用途。
类似_xxx和__xxx这样的函数或变量就是非公开的(private),不应该被直接引用(不是不能!)。 import xxx
是从 Python 系统库,或者项目的最外层导入 xxx。from . import xxx
是从同一文件夹下导入 xxx。from .yyy import xxx
是从当前目录的子文件夹 yyy 中导入 xxx。
此外,import Module
#引入模块from Module import Other
#引入模块中的类、函数或变量from Module import *
#引入模块中的所有公开成员- 默认情况下,Python解释器会搜索当前目录、所有已安装的内置模块和第三方模块,搜索路径存放在sys模块的path变量中。
面向对象编程
- 创建类:
class Student(object):
可以自由地给一个实例变量绑定任何类型的属性。
bart.name='jerry'
,在init中绑定的属性则一定要有。也可以给实例绑定方法s.set_age = MethodType(set_age, s)
可以使用__slots__
变量来限制实例能添加的属性:__slots__
定义的属性仅对当前类实例起作用,对继承的子类是不起作用的,除非在子类中也定义slots,这样,子类实例允许定义的属性就是自身的slots加上父类的slots。1
2class Student(object):
__slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称和普通的函数相比,在类中定义的函数只有一点不同,就是第一个参数永远是实例变量self,并且,调用时,不用传递该参数。除此之外,类的方法和普通函数没有什么区别,所以,你仍然可以用默认参数、可变参数、关键字参数和命名关键字参数。
- 如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线,在Python中,实例的变量名如果以开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问。因为此时它已经改名为
_类名__属性名
- 动态语言的“鸭子类型”决定了它并不要求严格的继承体系。
type()
可以用来判断对象类型。type(123)==int True
type(fn)==types.FunctionType True
isinstance
也可用来判断isinstance([1, 2, 3], (list, tuple)) 判断是否是其中一种
dir()
可以获得一个对象的所有属性和方法。- 我们自己写的类,如果也想用len(myObj)的话,就自己写一个len()方法;
getattr()、setattr()以及hasattr(),我们可以直接操作一个对象的状态。 可以直接在class中定义属性,这种属性是类属性,归Student类所有。相同名称的实例属性将屏蔽掉类属性,但是当你
删除实例属性
后,再使用相同的名称,访问到的将是类属性。1
2class Student(object):
name = 'Student'类方法内调用时,可以使用
类名.变量名
调用。Python内置的@property装饰器就是负责把一个方法变成属性调用的。
把一个getter方法变成属性,只需要加上@property
,@property
本身又创建了另一个装饰器`@score.setter`,负责把一个setter方法变成属性赋值1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17class Student(object):
@property
def score(self):
return self._score
@score.setter
def score(self, value):
if not isinstance(value, int):
raise ValueError('score must be an integer!')
if value < 0 or value > 100:
raise ValueError('score must between 0 ~ 100!')
self._score = value
#调用时:
s.score
s.score=10python可以实现多重继承,为了看清关系,建议将除主继承以外的继承的类命名结尾为
MixIn
。MixIn的目的就是给一个类增加多个功能,这样,在设计类的时候,我们优先考虑通过多重继承来组合多个MixIn的功能,而不是设计多层次的复杂的继承关系。__str__()
用于print()类对象时,返回用户看到的字符串;__repr__()
用于直接调用类对象时,为调试服务的;因此,可以在定义了一个后,直接__repr__ = __str__
如果一个类想被用于for … in循环,类似list或tuple那样,就必须实现一个iter()方法,该方法返回一个迭代对象,然后,Python的for循环就会不断调用该迭代对象的next()方法拿到循环的下一个值,直到遇到StopIteration错误时退出循环。
1
2
3
4
5
6
7
8
9
10
11
12class Fib(object):
def __init__(self):
self.a, self.b = 0, 1 # 初始化两个计数器a,b
def __iter__(self):
return self # 实例本身就是迭代对象,故返回自己
def __next__(self):
self.a, self.b = self.b, self.a + self.b # 计算下一个值
if self.a > 100000: # 退出循环的条件
raise StopIteration()
return self.a # 返回下一个值__getitem__
可以将类实例表现的像list那样按照下标取出元素。注意!!__getitem__()
传入的参数可能是一个int,也可能是一个切片对象slice,所以要做判断。1
2
3
4
5
6class Fib(object):
def __getitem__(self, n):
if isinstance(n, int): # n是索引
pass
if isinstance(n, slice): # n是切片
pass与之对应的是
__setitem__()
方法,把对象视作list或dict来对集合赋值。最后,还有一个__delitem__()
方法,用于删除某个元素。__getattr__()
方法可以动态返回一个属性(此时调用方式为s.age
)。也可以返回一个函数(此时调用方式为s.age()
)。
注意,只有在没有找到属性的情况下,才调用getattr,已有的属性不会在getattr中查找。1
2
3def __getattr__(self, attr):
if attr=='score':
return 99如果要让class只响应特定的几个属性,我们就要按照约定,抛出AttributeError的错误。
__call__()
方法,就可以直接对实例进行调用。此外,他也可以定义参数,对实例进行直接调用就好比对一个函数进行调用一样,所以你完全可以把对象看成函数,把函数看成对象,因为这两者之间本来就没啥根本的区别。1
2s = Student('Michael')
s() # self参数不要传入需要判断一个对象是否能被调用时,可以通过
callable()
函数判断一个对象是否是“可调用”对象。1
callable(Student())
一个经典的定制类的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19class Chain():
def __init__(self,path=''):
self._path = path
def __getattr__(self, item):
return Chain('%s/%s' % (self._path,item))
def __str__(self):
return self._path
__repr__ = __str__
__call__ = __getattr__
print ( Chain().users('michael').repos )
#分析:
Chain()先调用了init;
然后调用getattr,此时相当于生成了一个新的Chain的实例,然后通过call,把实例当成一个函数进行调用;
接着再调用getattr;
最后调用str,输出当前的路径。
------
换句话说,. 就相当于调用 __getattr__,(...) 就相当于调用 __call__。定义枚举类实例:
1
2from enum import Enum
Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))调用枚举类成员
Month.Jan
,枚举所有成员:1
2
3
4for name, member in Month.__members__.items():
print(name, '=>', member, ',', member.value)
#value属性则是自动赋给成员的int常量,默认从1开始计数。如果需要更精确地控制枚举类型,可以从Enum派生出自定义类
1
2
3
4
5
6
7
8
9
10
11from enum import Enum, unique
@unique #帮助我们检查保证没有重复值
class Weekday(Enum):
Sun = 0 # Sun的value被设定为0
Mon = 1
Tue = 2
Wed = 3
Thu = 4
Fri = 5
Sat = 6调用:
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>>> day1 = Weekday.Mon
>>> print(day1)
Weekday.Mon
>>> print(Weekday.Tue)
Weekday.Tue
>>> print(Weekday['Tue'])
Weekday.Tue
>>> print(Weekday.Tue.value)
2
>>> print(day1 == Weekday.Mon)
True
>>> print(day1 == Weekday.Tue)
False
>>> print(Weekday(1))
Weekday.Mon
>>> print(day1 == Weekday(1))
True
>>> Weekday(7)
Traceback (most recent call last):
...
ValueError: 7 is not a valid Weekday
>>> for name, member in Weekday.__members__.items():
... print(name, '=>', member)
...
Sun => Weekday.Sun
Mon => Weekday.Mon
Tue => Weekday.Tue
Wed => Weekday.Wed
Thu => Weekday.Thu
Fri => Weekday.Fri
Sat => Weekday.Satclass的定义是运行时动态创建的,而创建class的方法就是使用type()函数。要创建一个class对象,type()函数依次传入3个参数:
class的名称
;继承的父类集合,注意Python支持多重继承,如果只有一个父类,别忘了tuple的单元素写法
;class的方法名称与函数绑定,这里我们把函数fn绑定到方法名hello上
。1
2
3def fn(self, name='world'): # 先定义函数
print('Hello, %s.' % name)
Hello = type('Hello', (object,), dict(hello=fn)) # 创建Hello class要控制类的创建行为,还可以使用metaclass(元类)。可以理解为:先定义metaclass,就可以创建类,最后创建实例。
1
2
3
4
5# metaclass是类的模板,所以必须从`type`类型派生:
class ListMetaclass(type):
def __new__(cls, name, bases, attrs):
attrs['add'] = lambda self, value: self.append(value)
return type.__new__(cls, name, bases, attrs)1
2
3#创建类
class MyList(list, metaclass=ListMetaclass):
pass当我们传入关键字参数metaclass时,魔术就生效了,它指示Python解释器在创建MyList时,要通过ListMetaclass.new()来创建,在此,我们可以修改类的定义,比如,加上新的方法,然后,返回修改后的定义。new()方法接收到的参数依次是:
当前准备创建的类的对象
;类的名字
;类继承的父类集合
;类的方法集合
。
错误、调试、测试
当我们认为某些代码可能会出错时,就可以用try来运行这段代码,如果执行出错,则后续代码不会继续执行,而是直接跳转至错误处理代码,即except语句块,执行完except后,如果有finally语句块,则执行finally语句块,至此,执行完毕。
1
2
3
4
5
6
7
8
9
10
11
12
13try:
print('try...')
r = 10 / int('2')
print('result:', r)
except ValueError as e:
print('ValueError:', e)
except ZeroDivisionError as e:
print('ZeroDivisionError:', e)
else:
print('no error!')
finally:
print('finally...')
print('END')Python的错误其实也是class,所有的错误类型都继承自BaseException,所以在使用except时需要注意的是,它不但捕获该类型的错误,还把其子类也“一网打尽”。常见的错误类型和继承关系
不需要在每个可能出错的地方去捕获错误,只要在合适的层次去捕获错误就可以了。这样一来,就大大减少了写try…except…finally的麻烦。Python内置的logging模块可以非常容易地记录错误信息,同时,
让程序继续执行下去
。通过配置,logging还可以把错误记录到日志文件里,方便事后排查。1
2
3
4
5
6
7import logging
.....
try:
bar('0')
except Exception as e:
logging.exception(e)logging允许你指定记录信息的级别,有debug,info,warning,error等几个级别,当我们指定level=INFO时,logging.debug就不起作用了。同理,指定level=WARNING后,debug和info就不起作用了。这样一来,你可以放心地输出不同级别的信息,也不用删除,最后统一控制输出哪个级别的信息。
1
2
3
4
5
6import logging
logging.basicConfig(level=logging.INFO)
s = '0'
n = int(s)
logging.info('n = %d' % n)
print(10 / n)用raise语句抛出一个错误的实例。
1
2
3
4
5
6
7
8
9
10class FooError(ValueError):
pass
def foo(s):
n = int(s)
if n==0:
raise FooError('invalid value: %s' % s)
return 10 / n
foo('0')断言:凡是用print()来辅助查看的地方,都可以用断言(assert)来替代:
assert n != 0, 'n is zero!'
。启动Python解释器时可以用-O参数来关闭assert。python -m pdb 文件名.py
启动Python的调试器pdb,让程序以单步方式运行,可以随时查看运行状态。
输入命令l
来查看代码。
输入命令n
可以单步执行代码
输入命令p 变量名
来查看变量
输入命令q
结束调试
此外,可以在程序中使用pdb.set_trace()
(要导入包import pdb
)来使程序会自动在pdb.set_trace()暂停并进入pdb调试环境,
命令p 变量名
查看变量
命令c
继续运行编写单元测试时,我们需要编写一个测试类,从
unittest.TestCase
继承。以test
开头的方法就是测试方法,不以test开头的方法不被认为是测试方法,测试的时候不会被执行。self.assertEqual(abs(-1), 1)
:断言函数返回的结果与1相等with self.assertRaises(KeyError): value = d['empty']
:访问不存在的key时,断言会抛出KeyError
一旦编写好单元测试,我们就可以运行单元测试。最简单的运行方式是在文件名.py
的最后加上两行代码。然后直接执行python 文件名.py
;另一种方法是在命令行通过参数-m unittest
直接运行单元测试1
2if __name__ == '__main__':
unittest.main()可以在单元测试中编写两个特殊的setUp()和tearDown()方法。这两个方法会分别在每调用一个测试方法的前后分别被执行。
可以编写文档测试,这些代码与其他说明可以写在注释中,然后,由一些工具来自动生成文档。既然这些代码本身就可以粘贴出来直接运行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21def fact(n):
'''
Calculate 1*2*...*n
>>> fact(1)
1
>>> fact(10)
3628800
>>> fact(-1)
Traceback (most recent call last):
...
ValueError
'''
if n < 1:
raise ValueError()
if n == 1:
return 1
return n * fact(n - 1)
if __name__ == '__main__':
import doctest
doctest.testmod()注意到最后3行代码。当模块正常导入时,doctest不会被执行。只有在命令行直接运行时,才执行doctest。所以,不必担心doctest会在非测试环境下执行。
IO处理
f = open('/Users/michael/test.txt', 'r',【encoding='gbk', errors='ignore'】)
打开文件,如果文件不存在,open()函数就会抛出一个IOError的错误,并且给出错误码和详细的信息告诉你文件不存在。
其中,r
表示读,rb
表示按二进制读。encoding参数表示编码类型,errors参数表示如果遇到编码错误后如何处理。最简单的方式是直接忽略w
表示写,wb
表示按二进制写,如果文件已存在,会直接覆盖(相当于删掉后新写入一个文件)a
以追加模式写入。f.read()
可以一次读取文件的全部内容,Python把内容读到内存,用一个str对象表示;read(size)
方法,每次最多读取size个字节的内容。readline()
可以每次读取一行内容,调用readlines()
一次读取所有内容并按行返回listf.write('Hello, world!')
,通过反复调用write()来写入文件。f.close()
关闭文件。
可以使用with语句简化书写。(只能在支持了上下文管理器的对象
使用,会自动调用close语句)1
2
3with open('/path/to/file', 'r') as f:
for line in f.readlines():
print(line.strip()) # 把末尾的'\n'删掉要把str写入StringIO,我们需要先创建一个StringIO,然后,
f.write('hello')
像文件一样写入即可。使用f.getvalue()
获得写入后的str.1
2
3
4from io import StringIO
f = StringIO()
f.write('world!')
f.getvalue()如果要操作二进制数据,就需要使用BytesIO.函数与StringIO相同。
os模块下的基本功能:
1
2
3
4
5
6
7
8
9
10
11
12
13import os
os.name #操作系统类型
os.uname() #详细的系统信息(windows没有)
os.environ #环境变量
os.environ.get('key') #获取某个环境变量的值
os.path.abspath('.') #查看当前目录的绝对路径
os.mkdir('/Users/michael/testdir') #创建一个目录
os.rmdir('/Users/michael/testdir') # 删掉一个目录
os.path.join('/Users/michael', 'testdir') #把两个路径合成一个时,要通过该函数,不要求目录和文件要真实存在
os.path.split('/Users/michael/testdir/file.txt') #拆分路径,不要求目录和文件要真实存在
os.path.splitext('/path/to/file.txt') #直接让你得到文件扩展名,不要求目录和文件要真实存在
os.rename('test.txt', 'test.py') # 对文件重命名
os.remove('test.py') # 删掉文件shutil模块可以作为os模块的补充:
1
copyfile() #复制文件
我们可以利用Python的特性来过滤文件,例如:
1
2[x for x in os.listdir('.') if os.path.isdir(x)] #列出当前目录下的所有目录
[x for x in os.listdir('.') if os.path.isfile(x) and os.path.splitext(x)[1]=='.py'] #列出所有的.py文件变量从内存中变成可存储或传输的过程称之为序列化.在Python中叫pickling.反过来,把变量内容从序列化的对象重新读到内存里称之为反序列化,即unpickling。Python提供了
pickle模块
来实现序列化。pickle.dumps()
方法把任意对象序列化成一个bytes;pickle.dump()
直接把对象序列化后写入一个file-like Object;pickle.loads()
方法反序列化出对象;pickle.load()
方法从一个file-like Object中直接反序列化出对象.
注意:Pickle的问题和所有其他编程语言特有的序列化问题一样,就是它只能用于Python,并且可能不同版本的Python彼此都不兼容,因此,只能用Pickle保存那些不重要的数据,不能成功地反序列化也没关系。Python内置的
json模块
提供了非常完善的Python对象到JSON格式的转换。函数与上述相同。
如果需要定制JSON序列化,则传参时,default
参数填写对应的转换函数。1
2
3
4
5
6
7def student2dict(std):
return {
'name': std.name,
'age': std.age,
'score': std.score
}
json.dumps(s, default=student2dict)如果偷懒,可以使用
json.dumps(s, default=lambda obj: obj.__dict__)
,因为通常class的实例都有一个dict属性,它就是一个dict,用来存储实例变量。也有少数例外,比如定义了slots的class。
同理,反序列化,也可以使用这种方法,同时传参时,object_hook
参数填写对应的函数。1
2
3def dict2student(d):
return Student(d['name'], d['age'], d['score'])
json.loads(json_str, object_hook=dict2student)此外,在出现中文时,
ensure_ascii
参数可以确定中文是否需要转换为ASCii码。
正则表达式
【注意】如果没有标明字符长度,只要从头开始完全匹配即可,后面是否匹配都算匹配。re.match('abc\d','abc22345ad')
是匹配的。
精确匹配
1
2
3
4
5\d 匹配数字
\w 匹配一个字母、数字
\s 匹配空格(也包括Tab等空白符)
. 匹配任意字符
\转义字符 匹配转义字符,如 - _变长字符
1
2
3
4
5* 表示任意个字符(包括0个)
+ 表示至少一个字符
? 表示0个或1个字符
{n} 表示n个字符,
{n,m} 表示n-m个字符范围。可以用[]表示范围,比如:
1
2
3
4[0-9a-zA-Z\_] 可以匹配一个数字、字母或者下划线
[0-9a-zA-Z\_]+ 可以匹配至少由一个数字、字母或者下划线组成的字符串
[a-zA-Z\_][0-9a-zA-Z\_]* 可以匹配由字母或下划线开头,后接任意个由一个数字、字母或者下划线组成的字符串,也就是Python合法的变量
[a-zA-Z\_][0-9a-zA-Z\_]{0, 19} 更精确地限制了变量的长度是1-20个字符python|java
可以匹配python或java
^
表示行的开头,^\d
表示必须以数字开头。
$
表示行的结束,\d$
表示必须以数字结束。- Python提供
re模块
,包含所有正则表达式的功能。
由于Python的字符串本身也用\转义,所以要特别注意,建议使用r
前缀,这样就不需要考虑转义了。match()
方法判断是否匹配,如果匹配成功,返回一个Match对象,否则返回None。如:re.match(r'^\d{3}\-\d{3,8}$', '010-12345')
split()
用来切分字符串。 如re.split(r'[\s\,]+', 'a,b, c d')
group()
用来提取分组,group(0)
表示原始字符串,之后表示第n个子串。groups()
表示分组后的元组。re.match(r'^(\d{3})-(\d{3,8})$', '010-12345') ==> m.group(1)
compile()
用于预编译正则表达式,为了效率。1
2re_telephone = re.compile(r'^(\d{3})-(\d{3,8})$')
re_telephone.match('010-12345').groups()
内建模块
datetime模块
1 | from datetime import datetime #导入datetime类 |
注意:datetime表示的时间需要时区信息才能确定一个特定的时间,否则只能视为本地时间。
collections模块
用namedtuple
可以很方便地定义一种数据类型,它具备tuple的不变性,又可以根据属性来引用。创建对象是tuple的一种子类1
2Point = namedtuple('Point', ['x', 'y']) #创建一个自定义的tuple对象并且规定了tuple元素的个数,
p = Point(1, 2) #可以用属性而不是索引来引用tuple的某个元素 p.x
用deque
可以高效实现插入和删除操作的双向列表,适合用于队列和栈。支持append()、pop()、appendleft()、popleft(),可以非常高效地往头部添加或删除元素。1
2
3
4from collections import deque
q = deque(['a', 'b', 'c'])
q.append('x')
q.appendleft('y')
使用dict时,如果引用的Key不存在,就会抛出KeyError。如果希望key不存在时,返回一个默认值,就可以用defaultdict
,其他行为与dict完全一样。注意默认值是调用函数返回的,而函数在创建defaultdict对象时传入。1
2from collections import defaultdict
dd = defaultdict(lambda: 'N/A')
使用dict时,Key是无序的。在对dict做迭代时,我们无法确定Key的顺序。如果要保持Key按插入的顺序,可以用OrderedDict
1
2od = OrderedDict([('a', 1), ('b', 2), ('c', 3)])
list(od.keys()) # 按照插入的Key的顺序返回
Counter
是一个简单的计数器,实际上也是dict的一个子类。例如,统计字符出现的个数。1
2
3
4from collections import Counter
c = Counter()
for ch in 'programming':
... c[ch] = c[ch] + 1
base64模块
Base64是一种用64个字符 {‘A’, ‘B’, ‘C’, … ‘a’, ‘b’, ‘c’, … ‘0’, ‘1’, … ‘+’, ‘/‘} 来表示任意二进制数据的方法。由于在URL中+和/不能直接作为参数,所以又有一种”url safe”的base64编码,其实就是把字符+和/分别变成-和_:
它的原理是:每3个字节一组,按照6bit划分为4组。如果编码不是3的倍数,就用\x00
字节补足,并添加对应数量的=
号。由于=字符也可能出现在Base64编码中,但=用在URL、Cookie里面会造成歧义,所以,很多Base64编码后会把=去掉.
Base64是一种通过查表的编码方法,不能用于加密,即使使用自定义的编码表也不行。Base64适用于小段内容的编码,比如数字证书签名、Cookie的内容等。1
2
3
4
5import base64
base64.b64encode(b'binary\x00string')
base64.b64decode(b'YmluYXJ5AHN0cmluZw==')
base64.urlsafe_b64encode(b'i\xb7\x1d\xfb\xef\xff')
base64.urlsafe_b64decode('abcd--__')
struct模块
解决bytes和其他二进制数据类型的转换。参数说明1
2
3import struct
struct.pack('>I', 10240099) #转换为二进制,>表示字节顺序是big-endian,也就是网络序,I表示4字节无符号整数
struct.unpack('>IH', b'\xf0\xf0\xf0\xf0\x80\x80') #后面的bytes依次变为I:4字节无符号整数和H:2字节无符号整数
hashlib模块
1 | import hashlib |
hmac模块
Hmac算法通过一个标准算法,在计算哈希的过程中,把key混入计算过程中。要注意传入的key和message都是bytes类型,str类型需要首先编码为bytes1
2
3
4
5
6import hmac
message = b'Hello, world!'
key = b'secret'
h = hmac.new(key, message, digestmod='MD5')
# 如果消息很长,可以多次调用h.update(msg)
h.hexdigest()
itertools模块
itertools提供了非常有用的用于操作迭代对象的函数。1
2
3
4
5
6
7
8
9natuals = itertools.count(1,2) #count()会创建一个无限的迭代器,从1开始,间隔为2
cs = itertools.cycle('ABC') #cycle()会把传入的一个序列无限重复下去
ns = itertools.repeat('A', 3) #repeat()负责把一个元素无限重复下去,不过如果提供第二个参数就可以限定重复次数
无限序列在for迭代时才会无限地迭代下去;但通过takewhile()等函数根据条件判断来截取出一个有限的序列
ns = itertools.takewhile(lambda x: x <= 10, natuals)
itertools.chain('ABC', 'XYZ') #把一组迭代对象串联起来,形成一个更大的迭代器
itertools.groupby('AaaBBbcCAAa', lambda c: c.upper()) #把迭代器中相邻的重复元素挑出来放在一起
contexlib模块
任何对象,只要正确实现了上下文管理,就可以用于with语句。
实现上下文管理是通过__enter__
和__exit__
这两个方法实现的。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#正常写法:
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()
#使用装饰器
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 #用yield语句把with ... as var把变量输出出去,然后,with语句就可以正常地工作了
print('End')
with create_query('Bob') as q:
q.query()
1 | @contextmanager |
如果一个对象没有实现上下文,我们就不能把它用于with语句。这个时候,可以用closing()来把该对象变为上下文对象。
closing也是一个经过@contextmanager装饰的generator1
2
3
4
5
6from contextlib import closing
from urllib.request import urlopen
with closing(urlopen('https://www.python.org')) as page:
for line in page:
print(line)
urllib模块
urllib的request模块可以非常方便地抓取URL内容,也就是发送一个GET请求到指定的页面,然后返回HTTP的响应1
2
3
4
5
6
7
8from urllib import request
with request.urlopen('http://madj.xin') 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'))
如果我们要想模拟浏览器发送GET请求,就需要使用Request对象,通过往Request对象添加HTTP头,我们就可以把请求伪装成浏览器。1
2
3
4
5
6
7req = request.Request('http://www.douban.com/')
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')
with request.urlopen(req) 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'))
如果要以POST发送一个请求,只需要把参数data以bytes形式传入。1
2
3
4
5
6
7
8
9
10
11
12login_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')
])
with request.urlopen(req, data=login_data.encode('utf-8')) as f:
.......
如果还需要更复杂的控制,比如通过一个Proxy去访问网站,我们需要利用ProxyHandler来处理,示例代码如下:1
2
3
4
5
6proxy_handler = urllib.request.ProxyHandler({'http': 'http://www.example.com:3128/'})
proxy_auth_handler = urllib.request.ProxyBasicAuthHandler()
proxy_auth_handler.add_password('realm', 'host', 'username', 'password')
opener = urllib.request.build_opener(proxy_handler, proxy_auth_handler)
with opener.open('http://www.example.com/login.html') as f:
pass
XML模块
操作XML有两种方法:DOM和SAX。DOM会把整个XML读入内存,解析为树,因此占用内存大,解析慢,优点是可以任意遍历树的节点。SAX是流模式,边读边解析,占用内存小,解析快,缺点是我们需要自己处理事件。
正常情况下,优先考虑SAX,因为DOM实在太占内存。
在Python中使用SAX解析XML非常简洁,通常我们关心的事件是start_element
,end_element
和char_data
,准备好这3个函数,然后就可以解析xml了。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25from xml.parsers.expat import ParserCreate
class DefaultSaxHandler(object):
def start_element(self, name, attrs):
print('sax:start_element: %s, attrs: %s' % (name, str(attrs)))
def end_element(self, name):
print('sax:end_element: %s' % name)
def char_data(self, text):
print('sax:char_data: %s' % text)
xml = r'''<?xml version="1.0"?>
<ol>
<li><a href="/python">Python</a></li>
<li><a href="/ruby">Ruby</a></li>
</ol>
'''
handler = DefaultSaxHandler()
parser = ParserCreate()
parser.StartElementHandler = handler.start_element
parser.EndElementHandler = handler.end_element
parser.CharacterDataHandler = handler.char_data
parser.Parse(xml)
最有效的生成XML的方法是拼接字符串1
2
3
4
5
6L = []
L.append(r'<?xml version="1.0"?>')
L.append(r'<root>')
L.append(encode('some & data'))
L.append(r'</root>')
return ''.join(L)
HTMLParser模块
HTML本质上是XML的子集,但是HTML的语法没有XML那么严格,所以不能用标准的DOM或SAX来解析HTML。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
33from html.parser import HTMLParser
from html.entities import name2codepoint
class MyHTMLParser(HTMLParser):
def handle_starttag(self, tag, attrs):
print('<%s>' % tag)
def handle_endtag(self, tag):
print('</%s>' % tag)
def handle_startendtag(self, tag, attrs):
print('<%s/>' % tag)
def handle_data(self, data):
print(data)
def handle_comment(self, data):
print('<!--', data, '-->')
def handle_entityref(self, name):
print('&%s;' % name)
def handle_charref(self, name):
print('&#%s;' % name)
parser = MyHTMLParser()
parser.feed('''<html>
<head></head>
<body>
<!-- test html parser -->
<p>Some <a href=\"#\">html</a> HTML tutorial...<br>END</p>
</body></html>''')
feed()方法可以多次调用,也就是不一定一次把整个HTML字符串都塞进去,可以一部分一部分塞进去。特殊字符有两种,一种是英文表示的 ,一种是数字表示的Ӓ,这两种字符都可以通过Parser解析出来。
常用第三方模块
Pillow模块
图像处理库。
缩放图像:1
2
3
4
5
6
7
8
9
10
11
12from 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
7from PIL import Image, ImageFilter
# 打开一个jpg图像文件,注意是当前路径:
im = Image.open('test.jpg')
# 应用模糊滤镜:
im2 = im.filter(ImageFilter.BLUR)
im2.save('blur.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
34from 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('/Library/Fonts/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')
多进程和线程
Unix/Linux下,
os.fork()
调用一次,然后,分别在父进程和子进程内返回。
此外,multiprocessing模块提供了一个Process类来代表一个进程对象。1
2
3
4
5
6
7
8
9
10
11
12
13
14from 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() #join()方法可以等待子进程结束后再继续往下运行
print('Child process end.')如果要启动大量的子进程,可以用进程池的方式批量创建子进程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19from 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.')对Pool对象调用join()方法会等待所有子进程执行完毕,调用join()之前必须先调用close(),调用close()之后就不能继续添加新的Process了。请注意输出的结果,task 0,1,2,3是立刻执行的,而task 4要等待前面某个task完成后才执行,这是因为Pool的默认大小在我的电脑上是4,因此,最多同时执行4个进程。这是Pool有意设计的限制,并不是操作系统的限制。如果改成:
p = Pool(5)
就可以同时跑5个进程。- 调用外部进程,可以使用
subprocess
模块。subprocess.call(['nslookup','www.python.org'])
,如果子进程还需要输入,则可以通过communicate()方法输入。 - Process之间肯定是需要通信的,操作系统提供了很多机制来实现进程间的通信。Python的multiprocessing模块包装了底层的机制,提供了Queue、Pipes等多种方式来交换数据。
使用threading模块使用线程
1
2
3t = threading.Thread(target=loop, name='LoopThread')
t.start()
t.join()可以使用同步锁:
1
2
3
4
5
6
7
8
9
10
11
12
13balance = 0
lock = threading.Lock()
def run_thread(n):
for i in range(100000):
# 先要获取锁:
lock.acquire()
try:
# 放心地改吧:
change_it(n)
finally:
# 改完了一定要释放锁:
lock.release()全局变量local_school就是一个ThreadLocal对象,每个Thread对它都可以读写student属性,但互不影响。你可以把local_school看成全局变量,但每个属性如local_school.student都是线程的局部变量,可以任意读写而互不干扰,也不用管理锁的问题,ThreadLocal内部会处理。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21import threading
# 创建全局ThreadLocal对象:
local_school = threading.local()
def process_student():
# 获取当前线程关联的student:
std = local_school.student
print('Hello, %s (in %s)' % (std, threading.current_thread().name))
def process_thread(name):
# 绑定ThreadLocal的student:
local_school.student = name
process_student()
t1 = threading.Thread(target= process_thread, args=('Alice',), name='Thread-A')
t2 = threading.Thread(target= process_thread, args=('Bob',), name='Thread-B')
t1.start()
t2.start()
t1.join()
t2.join()