Python复习 基础知识
什么是动态语言呢? 可以简单的理解为直接被解释执行的语言称为动态语言。
而需要编译环境将程序转换为其它的编码再执行的语言称为静态语言。
当前静态语言有:java、C/C++、C#、DELPHI、VB等。
动态语言有:asp、php、cgi、lisp、Perl、python,Smalltalk、Ruby等。
数据类型:
dict:无序,可变,大括号,唯一内置的映射类型,字典是作为哈希表(支持快速检索的数据结构)来实现的。
tuple:有序,不可变,有in,无append、extend;无remove、pop;无index
list:有序,可变,小括号
字典 dict:
2.7文档:https://docs.python.org/2/library/stdtypes.html#mapping-types-dict
key,键必须独一无二,可以hash,支持 __hash__方法。list无 __hash__ 方法,所以不能作为key.
values containing lists, dictionaries or other mutable types (that are compared by value rather than by object identity) may not be used as keys.
Python中可哈希的对象有:
- 数值、字符串,以及只含有数值或字符串的元组
- 用户自定义类的实例(默认是可哈希的,也可以通过实现
__hash__()
和__cmp__()
来修改默认行为)
value,值可以取任何数据类型,但必须是不可变的,如字符串,数或元组。
dict:和list比较有以下几个特点:
- 查找和插入的速度极快,不会随着key的增加而增加;
- 需要占用大量的内存,内存浪费多。
list:
- 查找和插入的时间随着元素的增加而增加;
- 占用空间小,浪费内存很少。
所以,dict是用空间来换取时间的一种方法。
tuple:
用 Tuple 的好处
Tuple 比 list 操作速度快.如果您定义了一个值的常量集,并且唯一要用它做的是不断地遍历它,请使用 tuple 代替 list.
如果对不需要修改的数据进行 “写保护”,可以使代码更安全.使用 tuple 而不是 list 如同拥有一个隐含的 assert 语句,说明这一数据是常量.如果必须要改变这些值,则需要执行 tuple 到 list 的转换
Tuple 与 list 的转换
Tuple 可以转换成 list,反之亦然.内置的 tuple 函数接收一个 list,并返回一个有着相同元素的 tuple
而 list 函数接收一个 tuple 返回一个 list.从效果上看,tuple 冻结一个 list,而 list 解冻一个 tuple.
Tuple 的其他应用
一次赋多值
>>> v = ('a','b','e')
>>> (x,y,z) = v
参数传值
函数:Python允许你在list或tuple前面加一个*
号,把list或tuple的元素变成可变参数传进去:
参数组合:
参数定义的顺序必须是:必选参数、默认参数、可变参数和关键字参数。
Python的函数具有非常灵活的参数形态,既可以实现简单的调用,又可以传入非常复杂的参数。
默认参数一定要用不可变对象,如果是可变对象,运行会有逻辑错误!
要注意定义可变参数和关键字参数的语法:
*args
是可变参数,args接收的是一个tuple;
**kw
是关键字参数,kw接收的是一个dict。
以及调用函数时如何传入可变参数和关键字参数的语法:
可变参数既可以直接传入:func(1, 2, 3)
,又可以先组装list或tuple,再通过*args
传入:func(*(1, 2, 3))
;
关键字参数既可以直接传入:func(a=1, b=2)
,又可以先组装dict,再通过**kw
传入:func(**{'a': 1, 'b': 2})
。
使用*args
和**kw
是Python的习惯写法,当然也可以用其他参数名,但最好使用习惯用法。
递归函数
防止栈溢出
解决递归调用栈溢出的方法是通过尾递归优化
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)
可以看到,return fact_iter(num - 1, num * product)
仅返回递归函数本身,num - 1
和num * product
在函数调用前就会被计算,不影响函数调用。
尾递归事实上和循环是等价的,没有循环语句的编程语言只能通过尾递归实现循环。
切片
L[0:3]
//若第一个索引是0,还可以省略
L[:3] //取倒数第一个元素
L[-1]
迭代
dict默认迭代是key
可以用 for value in d.itervalues()
若要同时迭代 key和value
可以用 for k,v in d.iteritems()
判断一个对象是否是可迭代对象(返回BOOL类型):isinstance('abc', Iterable)
列表生成式(List Comprehensions)
[x*x for x in range(1,11)if x % 2 == 0]
[4,16,36,64,100]
[m + n for m in 'ABC' for n in 'XYZ']
['AX','AY','AZ','BX','BY','BZ','CX','CY','CZ']
生成器(generator)
一边循环一边计算,把列表生成器 的[] 改为 ()
>>> 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 0x104feab40>
generator 保存的是算法
斐波那契数列 Fibonacci
递归
def fib2(n):
if n == 0:
return 0
elif n ==1:
return 1
return fib2(n-1) + fib2(n-2) print(fib2(7)) memo = {0:0, 1:1}
def fib3(n):
if not n in memo:
memo[n] = fib3(n-1) + fib3(n-2) return memo[n] print(fib3(7))
(迭代解法)
def fib(max):
n, a, b = 0, 0, 1
while n < max:
print b
a, b = b, a+b
n = n + 1
generator :
def fib(max):
n, a, b = 0, 0, 1
while n < max:
yield b
a, b = b, a + b
n = n+1
与函数区别:
函数是顺序执行,遇到return或最后就返回。而变成generator的函数后,在调用 next()的时候执行,遇到 yield 语句 返回,再次执行时从上次返回的yield 语句处继续执行
函数式编程(Functional Programming)
Python对函数式编程提供部分支持。由于Python允许使用变量,因此,Python不是纯函数式编程语言。
高阶函数(Higher -order function)
1,变量可以指向函数
2,函数名也是变量
3,传入函数
函数可以接受另一个函数作为参数,称为高阶函数
map / reduce 函数
map(),高阶函数,接收两个参数,一个是函数,一个是序列,map将传入的函数依次作用到序列的每个元素,并把结果作为新的list返回。
将list所有数字转为字符串
>>>map(str, [1,2,3,4,5,6,7,8,9])
['', '', '', '', '', '', '', '', '']
reduce 必须接收两个参数
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
Python提供的 sum() 函数可以接受一个list并求和,请编写一个 prod() 函数,可以接受一个list并利用 reduce() 求 积。
def capi(w):
return w.capitalize()
print map(capi,L)
filter:
def is_odd(n):
return n % 2 == 1 filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15])
# 结果: [1, 5, 9, 15]
sorted:
高阶函数,可以接受一个比较函数来实现自定义的排序。
比如倒序函数:
def rebversed_cmp(x, y):
if x > y:
return -1
if x < y:
return 1
return 0
传入自定义的比较函数 reversed_cmp,就可以实现倒序排序:
sorted([36, 5, 12, 9, 21], reversed_cmp)
返回函数 ,闭包
注意到返回的函数在其定义内部引用了局部变量args。
所以,当一个函数返回了一个函数后,其内部的局部变量还被新函数引用,所以,闭包用起来简单,实现起来可不容易。
另一个需要注意的问题是,返回的函数并没有立刻执行,而是直到调用了f()
才执行。我们来看一个例子:
def count():
fs = []
for i in range(1, 4):
def f():
return i*i
fs.append(f)
return fs
f1, f2, f3 = count()
在上面的例子中,每次循环,都创建了一个新的函数,然后,把创建的3个函数都返回了。
你可能认为调用f1()
,f2()
和f3()
结果应该是1
,4
,9
,但实际结果是:
>>> f1()
9
>>> f2()
9
>>> f3()
9
全部都是9
!(即都循环了三次)。原因就在于返回的函数引用了变量i
,但它并非立刻执行。等到3个函数都返回时,它们所引用的变量i
已经变成了3,因此最终结果为9
。
返回闭包时牢记的一点就是:返回函数不要引用任何循环变量,或者后续会发生变化的变量。
如果一定要引用循环变量怎么办?方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变:
>>> def count():
... fs = []
... for i in range(1, 4):
... def f(j):
... def g():
... return j*j
... return g
... fs.append(f(i))
... return fs
...
>>> f1, f2, f3 = count()
>>> f1()
1
>>> f2()
4
>>> f3()
9
缺点是代码较长,可利用lambda函数缩短代码。:
f1, f2 = [(lambda j = i : j ** 2) for i in range(1, 3)]
for后面的i 是个全局变量,在传递进函数参数时已经被作为某个数字了,分解开就是
f1() = lambda j = 1 : j**2
f2() = lambda j = 2 : j**2
lambda:匿名函数
将一个List里的每个元素都平方,
map(lambda x: x*x, [y for y in range(10)])
装饰器:
将函数赋值给变量
函数对象有一个 __name__ 属性 ,可以拿到函数的名字
在代码运行期间 动态 增加功能的方式,称之为“装饰器”(Decorator)
def log(func):
def wrapper(*args, **kw):
print 'call %s():' % func.__name__
return func(*args, **kw)
return wrapper @log
def now()
print ''
调用now()函数
>>>now() call now(): 123
@log
def now()
print ''
相当于:
now = log(now)
log()是一个decorator,所以,原来的now()函数仍然存在,只是现在同名的now变量指向了新的函数,于是调用now()将执行新函数,即在log()函数中返回的wrapper()函数。
wrapper()函数的参数定义是(*args, **kw),因此,wrapper()函数可以接受任意参数的调用。在wrapper()函数内,首先打印日志,再紧接着调用原始函数。
在面向对象(OOP)的设计模式中,decorator被称为装饰模式。OOP的装饰模式需要通过继承和组合来实现,而Python除了能支持OOP的decorator外,直接从语法层次支持decorator。Python的decorator可以用函数实现,也可以用类实现。
decorator可以增强函数的功能,定义起来虽然有点复杂,但使用起来非常灵活和方便。
偏函数(partial function)
属于 functools 模块里边。把一个函数的某些参数给固定住(即设置默认值),返回一个新的函数。
>>> import functools
>>> int2 = functools.partial(int, base = 2)
>>> int2('')
64
>>> int('')
85
相当于:
kw = {base:2)
int('10010', **kw)
模块:
一个 .py 文件就称之为一个模块(module)
#!/usr/bin/env python
# -*- 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行和第2行是标准注释,第1行注释可以让这个hello.py
文件直接在Unix/Linux/Mac上运行,第2行注释表示.py文件本身使用标准UTF-8编码;
第4行是一个字符串,表示模块的文档注释,任何模块代码的第一个字符串都被视为模块的文档注释;
第6行使用__author__
变量把作者写进去,这样当你公开源代码后别人就可以瞻仰你的大名;
以上就是Python模块的标准文件模板
使用sys
模块的第一步,就是导入该模块:import sys
sys
模块有一个argv
变量,用list存储了命令行的所有参数。argv
至少有一个元素,因为第一个参数永远是该.py文件的名称。例如:
运行python hello.py
获得的sys.argv
就是['hello.py']
;
运行python hello.py Michael
获得的sys.argv
就是['hello.py', 'Michael]
。
if __name__=='__main__':
test()
当我们在命令行运行hello
模块文件时,Python解释器把一个特殊变量__name__
置为__main__
,而如果在其他地方导入该hello
模块时,if
判断将失败,因此,这种if
测试可以让一个模块通过命令行运行时执行一些额外的代码,最常见的就是运行测试。
Python是动态语言,函数签名一致接口就一样,因此,无论导入哪个模块后续代码都能正常工作。
函数签名就是函数的声明信息,包括参数(个数,类型)、返回值、调用约定之类。
安装第三方模块:
例如图片:
先安装pip: sudo easy_install pip 然后安装PIL: sudo -H pip install Pillow 最后导入Image模块: import PIL.Image 或者 from PIL import Image
#!/usr/bin/python
#coding:utf-8 #获取当前路径
import os
print os.getcwd() from PIL import Image
im = Image.open('test.png')
#打印格式、大小、类型
print im.format, im.size, im.mode #缩略图
im.thumbnail((200,100))
im.save('thumb.jpg', 'JPEG')
使用 __future__(python3.x 和 2.x 的区别)
1,编码
python2.x里的字符串用'xxx'
表示str,Unicode字符串用u'xxx'
表示unicode
而在3.x中,所有字符串都被视为unicode,因此,写u'xxx'
和'xxx'
是完全一致的,而在2.x中以'xxx'
表示的str就必须写成b'xxx'
,以此表示“二进制字符串”。
为了适应Python 3.x的新的字符串的表示方法,在2.7版本的代码中,可以通过unicode_literals
来使用Python 3.x的新的语法:
#still running on Python 2.7 from __future__ import unicode_literals
print '\'xxx\' is unicode?', isinstance('xxx',unicode)
print 'u\'xxx\' is unicode?', isinstance(u'xxx', unicode)
print '\'xxx\' is str?', isinstance('xxx', str)
print 'b\'xxx\' is str?', isinstance(b'xxx', str)
'xxx' is unicode? True
u'xxx' is unicode? True
'xxx' is str? False
b'xxx' is str? True
2,除法
Python2.x 都是地板除
Python3.x 普通除法是精确除法。地板除用//
from __future__ import division print '10 / 3 = ', 10 / 3
print '10.0 / 3 = ', 10.0 / 3
print '10 // 3 = ', 10 //3
10 / 3 = 3.33333333333
10.0 / 3 = 3.33333333333
10 // 3 = 3
3,其他。如 Python3 中无xrange
http://chenqx.github.io/2014/11/10/Key-differences-between-Python-2-7-x-and-Python-3-x/
面向对象编程
Object Oriented Programming, OOP。 数据封装、继承、多态
把对象作为程序的基本单元,一个对象包含了数据和操作数据段函数
OOP: 对象、消息
面向过程:一组函数的顺序执行
给对象发消息实际上就是调用对象对应的关联函数,我们称之为对象的方法(Method)。面向对象的程序写出来就像这样:
bart = Student('Bart Simpson', 59)
lisa = Student('Lisa Simpson', 87)
bart.print_score()
lisa.print_score()
类、实例、对象、方法
封装
访问限制:
要让内部属性不被外部访问,可以把属性的名称前加上两个下划线 __
变量名如果以 __ (两个_) 开头,就变成了一个私有变量 (private), 只有内部可以访问,外部不能访问。
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)
改完后,对于外部代码来说,没什么变动,但是已经无法从外部访问实例变量.__name
和实例变量.__score
了
如果外部代码要获取 name 和 score 可以 给 Student 类增加 get_name 和 get_score 这样的方法:
class Student(object):
...
def get_name(self):
return self.__name def get_score(self):
return self.__score
修改属性,使用set
class Student(object):
...
def set_score(self, score):
self.__score = score
使用方法修改name的优点:可以对参数做检查,避免传入无效的参数
在Python中,以双下划线开头并结尾的,是特殊变量。可以直接访问,不是private变量。
继承和多态
继承:好处,获得父类的全部功能
多态:具体调用的方法,由运行时该对象的确切类型决定。
开闭原则:
对扩展开放:允许新增Animal子类;
对修改封闭:不需要修改依赖Animal类型的run_twice()
等函数。
有继承才能有多态
获取对象信息:
使用type
>>>type(123) <type 'int'> >>> type(abs) <type 'builtin_function_or_method'> >>> import types >>> type('abc') == types.StringType True
使用dir(), 获取一个对象的所有属性和方法。
在len()内部,自动去调用该对象中的__len__()方法,下面代码是等价的
>>> len('ABC')
3
>>> 'ABC'.__len__()
3
使用 __slots__
限制Class的 属性,只允许对Student 实例添加name和age属性
>>> class Student(object):
... __slots__ = ('name', 'age') #用tuple定义允许绑定的属性名称
...
使用@property
在set_score里边设置检查参数
class Student(object):
def get_score(self):
return self._score
def set_score(self, value):
if not isinstance(value, int):
raise ValueError('score must be an integer!')
if value < 0 and value > 100:
raise ValueError('score must between 0 ~ 100!')
self._score = value
以上复杂,可以使用 @property 装饰器(即把函数当成参数传入,增加方法),把一个方法变成属性调用。
class Student(object)
@property #负责把一个getter 方法变成属性
def score(self):
return self._score @score.setter #负责把一个setter方法变成属性赋值
def score(self,value):
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 = Student()
>>> s.score = 60 # OK,实际转化为s.set_score(60)
>>> s.score # OK,实际转化为s.get_score()
60
>>> s.score = 9999
Traceback (most recent call last):
...
ValueError: score must between 0 ~ 100!
多重继承
class Bat(Mammal, Flyable):
pass
Mixin
非单独的属性,例如RunnableMixin
Python自带了TCPServer
和UDPServer
这两类网络服务,而要同时服务多个用户就必须使用多进程或多线程模型,这两种模型由ForkingMixin
和ThreadingMixin
提供。通过组合,我们就可以创造出合适的服务来。
由于Python允许使用多重继承,因此,Mixin就是一种常见的设计。
只允许单一继承的语言(如Java)不能使用Mixin的设计。
定制类:
__str__ 方法
class Student (object)
def __int__(self, name):
self.name = name
def __str__(self):
return 'Student object (name: %s)' % self.name
>>> print Student('Michael')
Student object (name:Michael)
直接敲变量不用print
,打印出来的实例还是不好看:
>>> s = Student('Michael')
>>> s
<__main__.Student object at 0x109afb310>
这是因为直接显示变量调用的不是__str__()
,而是__repr__()
,两者的区别是__str__()
返回用户看到的字符串,而__repr__()
返回程序开发者看到的字符串,也就是说,__repr__()
是为调试服务的。
解决办法是再定义一个__repr__()
。但是通常__str__()
和__repr__()
代码都是一样的,所以,有个偷懒的写法:
class Student(object):
def __init__(self, name):
self.name = name
def __str__(self):
return 'Student object (name=%s)' % self.name
__repr__ = __str__
__iter__ 迭代
__getitem__按照下标取出元素
__getattr__ 动态返回一个属性
__call__
class Student(object):
def __init__(self, name):
self.name = name def __call__(self):
print('My name is %s.' % self.name)
>>> s = Student('Michael')
>>> s()
My name is Michael.
使用元类,metaclass
可以使用 type() 动态创建类以外,还可以使用 元类
即当我们定义了类以后,就可以根据这个类创建出实例。所以先定义类,然后创建实例。
但是如果我们想创建出类呢?那就必须根据metaclass创建出类,所以:先定义metaclass,然后创建类。
连接起来就是:先定义metaclass,就可以创建类,最后创建实例。
错误处理:
try...except...finally
Python 所有的错误都是从 BaseException 类派生的
调用堆栈如果错误没有被捕获,它就会一直往上抛,最后被Python解释器捕获,打印一个错误信息,然后程序退出。
logging,可以记录错误信息。打印错误后会继续执行,并正常退出。
def main():
try:
bar('')
except StandardError, e:
logging.exception(2)
使用 raise 抛出错误。
调试:
使用assert
# err.py
def foo(s):
n = int(s)
assert n != 0, 'n is zero!'
return 10/n
def main():
foo('')
使用 -0 来关闭 assert
$ python -O err.py
使用 logging
使用pdb
pdb.set_trace()
# err.py
import pdb s = ''
n = int(s)
pdb.set_trace() # 运行到这里会自动暂停
print 10 / n
运行代码,程序会自动在pdb.set_trace()
暂停并进入pdb调试环境,可以用命令p
查看变量,或者用命令c
继续运行:
$ python err.py
> /Users/michael/Github/sicp/err.py(7)<module>()
-> print 10 / n
(Pdb) p n
0
(Pdb) c
Traceback (most recent call last):
File "err.py", line 7, in <module>
print 10 / n
ZeroDivisionError: integer division or modulo by zero
虽然用IDE调试起来比较方便,但是最后你会发现,logging才是终极武器。
单元测试:
测试驱动开发,TDD:Test-Driven Development
单元测试是是用来对一个模块、一个函数或者一个类来进行正确性检验的测试工作
编写单元测试时,我们需要编写一个测试类,从unittest.TestCase
继承。
运行单元测试:
$ python -m unittest mydict_test
在命令行通过参数 -m unittest 直接运行单元测试
setUp与tearDown
可以在单元测试中编写两个特殊的setUp()
和tearDown()
方法。这两个方法会分别在每调用一个测试方法的前后分别被执行。
setUp()
和tearDown()
方法有什么用呢?设想你的测试需要启动一个数据库,这时,就可以在setUp()
方法中连接数据库,在tearDown()
方法中关闭数据库,这样,不必在每个测试方法中重复相同的代码:
文档测试
Python 内置的“文档测试”(doctest)模块可以直接提取注释中的代码并执行测试。
IO编程
异步IO与同步IO
异步性能好,效率高,复杂(包括轮询模式和回调模式)
文件读写:
>>> f = open('/User/michael/test.txt', 'r')
使用try。。finally
try:
f = open('/path/to/file', 'r')
print f.read()
finally:
if f:
f.close()
简洁方法,使用with语句自动调用close() 方法:
with open('/path/to/file', 'r') as f:
print f.read()
调用read()
会一次性读取文件的全部内容,如果文件有10G,内存就爆了,所以,要保险起见,可以反复调用read(size)
方法,每次最多读取size个字节的内容。另外,调用readline()
可以每次读取一行内容,调用readlines()
一次读取所有内容并按行返回list
。因此,要根据需要决定怎么调用。
如果文件很小,read()
一次性读取最方便;如果不能确定文件大小,反复调用read(size)
比较保险;如果是配置文件,调用readlines()
最方便:
for line in f.readlines():
print(line.strip()) # 把末尾的'\n'删掉
读文件
要以读文件的模式打开一个文件对象,使用Python内置的open()
函数,传入文件名和标示符:
>>> f = open('/Users/michael/test.txt', 'r')
标示符'r'表示读,这样,我们就成功地打开了一个文件。
如果文件不存在,open()
函数就会抛出一个IOError
的错误,并且给出错误码和详细的信息告诉你文件不存在:
>>> f=open('/Users/michael/notfound.txt', 'r')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IOError: [Errno 2] No such file or directory: '/Users/michael/notfound.txt'
如果文件打开成功,接下来,调用read()
方法可以一次读取文件的全部内容,Python把内容读到内存,用一个str
对象表示:
>>> f.read()
'Hello, world!'
最后一步是调用close()
方法关闭文件。文件使用完毕后必须关闭,因为文件对象会占用操作系统的资源,并且操作系统同一时间能打开的文件数量也是有限的:
>>> f.close()
由于文件读写时都有可能产生IOError
,一旦出错,后面的f.close()
就不会调用。所以,为了保证无论是否出错都能正确地关闭文件,我们可以使用try ... finally
来实现:
try:
f = open('/path/to/file', 'r')
print f.read()
finally:
if f:
f.close()
但是每次都这么写实在太繁琐,所以,Python引入了with
语句来自动帮我们调用close()
方法:
with open('/path/to/file', 'r') as f:
print f.read()
这和前面的try ... finally
是一样的,但是代码更佳简洁,并且不必调用f.close()
方法。
调用read()
会一次性读取文件的全部内容,如果文件有10G,内存就爆了,所以,要保险起见,可以反复调用read(size)
方法,每次最多读取size个字节的内容。另外,调用readline()
可以每次读取一行内容,调用readlines()
一次读取所有内容并按行返回list
。因此,要根据需要决定怎么调用。
如果文件很小,read()
一次性读取最方便;如果不能确定文件大小,反复调用read(size)
比较保险;如果是配置文件,调用readlines()
最方便:
for line in f.readlines():
print(line.strip()) # 把末尾的'\n'删掉
file-like Object
像open()
函数返回的这种有个read()
方法的对象,在Python中统称为file-like Object。除了file外,还可以是内存的字节流,网络流,自定义流等等。file-like Object不要求从特定类继承,只要写个read()
方法就行。
StringIO
就是在内存中创建的file-like Object,常用作临时缓冲。
二进制文件
前面讲的默认都是读取文本文件,并且是ASCII编码的文本文件。要读取二进制文件,比如图片、视频等等,用'rb'
模式打开文件即可:
>>> f = open('/Users/michael/test.jpg', 'rb')
>>> f.read()
'\xff\xd8\xff\xe1\x00\x18Exif\x00\x00...' # 十六进制表示的字节
字符编码
要读取非ASCII编码的文本文件,就必须以二进制模式打开,再解码。比如GBK编码的文件:
>>> f = open('/Users/michael/gbk.txt', 'rb')
>>> u = f.read().decode('gbk')
>>> u
u'\u6d4b\u8bd5'
>>> print u
测试
如果每次都这么手动转换编码嫌麻烦(写程序怕麻烦是好事,不怕麻烦就会写出又长又难懂又没法维护的代码),Python还提供了一个codecs
模块帮我们在读文件时自动转换编码,直接读出unicode:
import codecs
with codecs.open('/Users/michael/gbk.txt', 'r', 'gbk') as f:
f.read() # u'\u6d4b\u8bd5'
写文件
写文件和读文件是一样的,唯一区别是调用open()
函数时,传入标识符'w'
或者'wb'
表示写文本文件或写二进制文件:
>>> f = open('/Users/michael/test.txt', 'w')
>>> f.write('Hello, world!')
>>> f.close()
你可以反复调用write()
来写入文件,但是务必要调用f.close()
来关闭文件。当我们写文件时,操作系统往往不会立刻把数据写入磁盘,而是放到内存缓存起来,空闲的时候再慢慢写入。只有调用close()
方法时,操作系统才保证把没有写入的数据全部写入磁盘。忘记调用close()
的后果是数据可能只写了一部分到磁盘,剩下的丢失了。所以,还是用with
语句来得保险:
with open('/Users/michael/test.txt', 'w') as f:
f.write('Hello, world!')
要写入特定编码的文本文件,请效仿codecs
的示例,写入unicode,由codecs
自动转换成指定编码。
操作文件和目录
# 查看当前目录的绝对路径:
>>> 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')
过滤文件:
>>> [x for x in os.listdir('.') if os.path.isdir(x)]
['.lein', '.local', '.m2', '.npm', '.ssh', '.Trash', '.vim', 'Adlm', 'Applications', 'Desktop', ...]
要列出所有的.py
文件,也只需一行代码:
>>> [x for x in os.listdir('.') if os.path.isfile(x) and os.path.splitext(x)[1]=='.py']
['apis.py', 'config.py', 'models.py', 'pymonitor.py', 'test_db.py', 'urls.py', 'wsgiapp.py']
序列化:(pickling)
把变量从内存中变成可存储或传输的过程称之为序列化
把变量从内存中变成可存储或传输的过程称之为反序列化, unpickling
序列化,两个模块,cPickle、pickle,cPickle是C语言写的pickle是Python写的
序列化后就可以写入磁盘,或者网络传输。
>>> d = dict(name='Bob', age=20, score=88)
>>> pickle.dumps(d) # 把任意一个对象序列化成一个 str,然后,就可以把这个str写入文件。或者用 pickle.dump()
>>> f = open('dump.txt', 'wb')
>>> pickle.dump(d, f) #把一个对象序列化后写入一个 file-like Object:
>>> f.close()
当我们要把对象从磁盘读到内存时,可以先把内容读到一个str
,然后用pickle.loads()
方法反序列化出对象,也可以直接用pickle.load()
方法从一个file-like Object
中直接反序列化出对象。我们打开另一个Python命令行来反序列化刚才保存的对象:
>>> f = open('dump.txt', 'rb')
>>> d = pickle.load(f)
>>> f.close()
>>> d
{'age': 20, 'score': 88, 'name': 'Bob'}
JSON
JSON不仅是标准格式,并且比XML更快,而且可以直接在Web页面中读取,非常方便。
JSON表示的对象就是标准的JavaScript语言的对象
JSON和Python内置的数据类型对应如下:
JSON类型 | Python类型 |
{} | dict |
[] | list |
"string" | 'str'或u'unicode' |
1234.56 | int或float |
true/false | True/False |
null | None |
dumps()
方法还提供了一大堆的可选参数:
https://docs.python.org/2/library/json.html#json.dumps
将对象转为 JSON
进程和线程
多任务的实现有3种方式:
- 多进程模式;
- 多线程模式;
- 多进程+多线程模式。
多进程(multiprocessing):
Unix/Linux操作系统提供了一个fork()
系统调用,它非常特殊。普通的函数调用,调用一次,返回一次,但是fork()
调用一次,返回两次,因为操作系统自动把当前进程(称为父进程)复制了一份(称为子进程),然后,分别在父进程和子进程内返回。
子进程永远返回0
,而父进程返回子进程的ID。这样做的理由是,一个父进程可以fork出很多子进程,所以,父进程要记下每个子进程的ID,而子进程只需要调用getppid()
就可以拿到父进程的ID。
#Python的os模块封装了常见的系统调用,其中就包括fork,可以在Python程序中轻松创建子进程: # multiprocessing.py
import os print 'Process (%s) start...' % os.getpid()
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) #运行结果如下: Process (876) start...
I (876) just created a child process (877).
I am child process (877) and my parent is 876. #有了fork调用,一个进程在接到新任务时就可以复制出一个子进程来处理新任务,
常见的Apache服务器就是由父进程监听端口,每当有新的http请求时,
就fork出子进程来处理新的http请求。
multiprocessing
multiprocessing
模块提供了一个Process
类来代表一个进程对象,
下面的例子演示了启动一个子进程并等待其结束:
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 'Process will start.'
p.start()#创建一个Process实例,用start()方法启动,这样创建进程比fork()还要简单。
p.join() #join() 方法可以等待子进程结束后再继续往下运行,通常用于进程间的同步。
print 'Process end.'
Parent process 928.
Process will start.
Run child process test (929)...
Process end.
Pool
如果要启动大量的子进程,可以用进程池的方式批量创建子进程:
# -*- coding:utf-8 -*- 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()
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.'
Parent process 862.
Waiting for all subprocesses done...
Run task 0 (864)...
Run task 1 (865)...
Run task 2 (866)...
Run task 3 (867)...
Task 1 runs 0.42 seconds.
Run task 4 (865)...
Task 0 runs 1.41 seconds.
Task 4 runs 1.65 seconds.
Task 3 runs 2.42 seconds.
Task 2 runs 2.64 seconds.
All subprocesses done. ***Repl Closed***
解读:
对Pool
对象调用join()
方法会等待所有子进程执行完毕,调用join()
之前必须先调用close()
,调用close()
之后就不能继续添加新的Process
了。
请注意输出的结果,task 0
,1
,2
,3
是立刻执行的,而task 4
要等待前面某个task完成后才执行,这是因为Pool
的默认大小在我的电脑上是4,因此,最多同时执行4个进程。这是Pool
有意设计的限制,并不是操作系统的限制。如果改成:
p = Pool(5)
就可以同时跑5个进程。
由于Pool
的默认大小是CPU的核数,如果你不幸拥有8核CPU,你要提交至少9个子进程才能看到上面的等待效果。
多线程
Python的标准库提供了两个模块:thread
和threading
,thread
是低级模块,threading
是高级模块,对thread
进行了封装。绝大多数情况下,我们只需要使用threading
这个高级模块。
启动一个线程就是把一个函数传入并创建Thread
实例,然后调用start()
开始执行:
import time, threading #新线程执行的代码:
def loop():
print 'thread %s is running...' % threading.current_thread().name
n = 0
while n < 5:
n = n + 1
print 'thread %s >>> %s' % (threading.current_thread().name, n)
time.sleep(1)
print 'thread %s ended.' % threading.current_thread().name print 'thread %s is running...' % threading.current_thread().name
t = threading.Thread(target = loop, name = 'LoopThread')
t.start()
t.join() #join作用是阻塞主进程(挡住,无法执行join以后的语句
print 'thread %s ended.' % threading.current_thread().name
thread MainThread is running...
thread LoopThread is running...
thread LoopThread >>> 1
thread LoopThread >>> 2
thread LoopThread >>> 3
thread LoopThread >>> 4
thread LoopThread >>> 5
thread LoopThread ended.
thread MainThread ended. ***Repl Closed***
join ()方法:
主线程A中,创建了子线程B,并且在主线程A中调用了B.join(),那么,
主线程A会在调用的地方等待,直到子线程B完成操作后,才可以接着往下执行,
那么在调用这个线程时可以使用被调用线程的join方法。
原型join([timeout]) 里面的参数时可选的,代表线程运行的最大时间,
即如果超过这个时间,不管这个此线程有没有执行完毕都会被回收,
然后主线程或函数都会接着执行的。
由于任何进程默认就会启动一个线程,我们把该线程称为主线程,主线程又可以启动新的线程,
Python的threading
模块有个current_thread()
函数,它永远返回当前线程的实例。
主线程实例的名字叫MainThread
,子线程的名字在创建时指定,我们用LoopThread
命名子线程。
名字仅仅在打印时用来显示,完全没有其他意义,
如果不起名字Python就自动给线程命名为Thread-1
,Thread-2
……
Lock
多线程和多进程最大的不同在于,
多进程中,同一个变量,各自有一份拷贝存在于每个进程中,互不影响,
多线程中,所有变量都由所有进程共享,所以,任何一个变量都可以被任何一个线程修改,
因此,线程之间共享数据最大的危险在于多个线程同时改一个变量,把内容改乱了。
锁,当某个线程开始执行change_it()
时,我们说,该线程因为获得了锁,
因此其他线程不能同时执行change_it()
,只能等待,直到锁被释放后,获得该锁以后才能改。
由于锁只有一个,无论多少线程,同一时刻最多只有一个线程持有该锁,
所以,不会造成修改的冲突。创建一个锁就是通过threading.Lock()
来实现:
balance = 0
lock = threading.Lock() def run_thread(n):
for i in range(100000):
# 先要获取锁:
lock.acquire()
try:
# 放心地改吧:
change_it(n)
finally:
# 改完了一定要释放锁:
lock.release()
锁的好处就是确保了某段关键代码只能由一个线程从头到尾完整地执行,坏处当然也很多,首先是阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模 式执行,效率就大大地下降了。其次,由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁,导致多个线程全部挂起,既 不能执行,也无法结束,只能靠操作系统强制终止。
用C、C++或Java来改写相同的死循环,直接可以把全部核心跑满,4核就跑到400%,8核就跑到800%,为什么Python不行呢?
因为Python的线程虽然是真正的线程,但解释器执行代码时,有一个GIL锁:Global Interpreter Lock,任何Python线程执行前,必须先获得GIL锁,然后,每执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行。这个GIL全局锁实际上把所有线程的执行代码都给上了锁,所以,多线程在Python中只能交替执行,即使100个线程跑在100核CPU上,也只能用到1个核。
GIL是Python解释器设计的历史遗留问题,通常我们用的解释器是官方实现的CPython,要真正利用多核,除非重写一个不带GIL的解释器。
所以,在Python中,可以使用多线程,但不要指望能有效利用多核。如果一定要通过多线程利用多核,那只能通过C扩展来实现,不过这样就失去了Python简单易用的特点。
不过,也不用过于担心,Python虽然不能利用多线程实现多核任务,但可以通过多进程实现多核任务。多个Python进程有各自独立的GIL锁,互不影响。
ThreadLocal
目的:
在多线程环境下,每个线程都有自己的数据。一个线程使用自己的局部变量比使用全局变量好,因为局部变量只有线程自己能看见,不会影响其他线程,而全局变量的修改必须加锁。
但是局部变量也有问题,就是在函数调用的时候,传递起来很麻烦
# -*- coding:utf-8 -*- import threading #创建全局ThreadLocal对象:
local_school = threading.local() def process_student():
print 'Hello, %s (in %s)' % (local_school.student, 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()
Hello, Alice (in Thread-A)
Hello, Bob (in Thread-B)
全局变量local_school
就是一个ThreadLocal
对象,每个Thread
对它都可以读写student
属性,但互不影响。你可以把local_school
看成全局变量,但每个属性如local_school.student
都是线程的局部变量,可以任意读写而互不干扰,也不用管理锁的问题,ThreadLocal
内部会处理。
可以理解为全局变量local_school
是一个dict
,不但可以用local_school.student
,还可以绑定其他变量,如local_school.teacher
等等。
ThreadLocal
最常用的地方就是为每个线程绑定一个数据库连接,HTTP请求,用户身份信息等,这样一个线程的所有调用到的处理函数都可以非常方便地访问这些资源。
多线程vs多进程
多线程:优点,效率高,但是任何一个线程挂掉都有可能直接造成整个进程奔溃,
因为所有线程共享进程的内存。
多进程:优点,稳定,创建进程的代价大
计算密集型 vs. IO密集型
是否采用多任务的第二个考虑是任务的类型。我们可以把任务分为计算密集型和IO密集型。
对于计算密集型任务,最好用C语言编写。
涉及到网络、磁盘IO的任务都是IO密集型任务,这类任务的特点是CPU消耗很少,任务的大部分时间都在等待IO操作完成(因为IO的速度远远低于CPU 和内存的速度)。对于IO密集型任务,任务越多,CPU效率越高,但也有一个限度。常见的大部分任务都是IO密集型任务,比如Web应用。
对于IO密集型任务,最合适的语言就是开发效率最高(代码量最少)的语言,脚本语言是首选,C语言最差。
现代操作系统对IO操作已经做了巨大的改进,最大的特点就是支持异步IO。如果充分利用操作系统提供的异步IO支持,就可以用单进程单线程模型来执行多任 务,这种全新的模型称为事件驱动模型,Nginx就是支持异步IO的Web服务器,它在单核CPU上采用单进程模型就可以高效地支持多任务。在多核CPU 上,可以运行多个进程(数量与CPU核心数相同),充分利用多核CPU。由于系统总的进程数量十分有限,因此操作系统调度非常高效。用异步IO编程模型来 实现多任务是一个主要的趋势。
分布式进程
# -*- coding:utf-8 -*- taskmanager.py
import random, time, Queue
from multiprocessing.managers import BaseManger #发送任务的队列:
task_queue = Queue.Queue()
#接受结果的队列:
result_queue = Queue.Queue() #从BaseManager继承的QueueManager:
class QueueManager(BaseManger):
pass
#把两个Queue都注册到网络上,callable参数关联了Queue对象:
QueueManager.register('get_task_queue', callable=lambda: task_queue)
QueueManager.register('get_result_queue', callable=lambda: result_queue)
# 绑定端口5000, 设置验证码'abc':
manager = QueueManager(address=('', 5000), authkey='abc')
# 启动Queue:
manager.start()
manager.start()
# 获得通过网络访问的Queue对象:
task = manager.get_task_queue()
result = manager.get_result_queue()
# 放几个任务进去:
for i in range(10):
n = random.randint(0, 10000)
print('Put task %d...' % n)
task.put(n)
# 从result队列读取结果:
print('Try get results...')
for i in range(10):
r = result.get(timeout=10)
print('Result: %s' % r)
# 关闭:
manager.shutdown()
请注意,当我们在一台机器上写多进程程序时,创建的Queue
可以直接拿来用,但是,在分布式多进程环境下,添加任务到Queue
不可以直接对原始的task_queue
进行操作,那样就绕过了QueueManager
的封装,必须通过manager.get_task_queue()
获得的Queue
接口添加。
然后,在另一台机器上启动任务进程(本机上启动也可以):
# taskworker.py import time, sys, Queue
from multiprocessing.managers import BaseManager # 创建类似的QueueManager:
class QueueManager(BaseManager):
pass # 由于这个QueueManager只从网络上获取Queue,所以注册时只提供名字:
QueueManager.register('get_task_queue')
QueueManager.register('get_result_queue') # 连接到服务器,也就是运行taskmanager.py的机器:
server_addr = '127.0.0.1'
print('Connect to server %s...' % server_addr)
# 端口和验证码注意保持与taskmanager.py设置的完全一致:
m = QueueManager(address=(server_addr, 5000), authkey='abc')
# 从网络连接:
m.connect()
# 获取Queue的对象:
task = m.get_task_queue()
result = m.get_result_queue()
# 从task队列取任务,并把结果写入result队列:
for i in range(10):
try:
n = task.get(timeout=1)
print('run task %d * %d...' % (n, n))
r = '%d * %d = %d' % (n, n, n*n)
time.sleep(1)
result.put(r)
except Queue.Empty:
print('task queue is empty.')
# 处理结束:
print('worker exit.')
任务进程要通过网络连接到服务进程,所以要指定服务进程的IP。
现在,可以试试分布式进程的工作效果了。先启动taskmanager.py
服务进程:
$ python taskmanager.py
Put task 3411...
Put task 1605...
Put task 1398...
Put task 4729...
Put task 5300...
Put task 7471...
Put task 68...
Put task 4219...
Put task 339...
Put task 7866...
Try get results...
taskmanager进程发送完任务后,开始等待result
队列的结果。现在启动taskworker.py
进程:
$ python taskworker.py 127.0.0.1
Connect to server 127.0.0.1...
run task 3411 * 3411...
run task 1605 * 1605...
run task 1398 * 1398...
run task 4729 * 4729...
run task 5300 * 5300...
run task 7471 * 7471...
run task 68 * 68...
run task 4219 * 4219...
run task 339 * 339...
run task 7866 * 7866...
worker exit.
taskworker进程结束,在taskmanager进程中会继续打印出结果:
Result: 3411 * 3411 = 11634921
Result: 1605 * 1605 = 2576025
Result: 1398 * 1398 = 1954404
Result: 4729 * 4729 = 22363441
Result: 5300 * 5300 = 28090000
Result: 7471 * 7471 = 55815841
Result: 68 * 68 = 4624
Result: 4219 * 4219 = 17799961
Result: 339 * 339 = 114921
Result: 7866 * 7866 = 61873956
这个简单的Manager/Worker模型有什么用?其实这就是一个简单但真正的分布式计算,把代码稍加改造,启动多个worker,就可以把任务分布到几台甚至几十台机器上,比如把计算n*n
的代码换成发送邮件,就实现了邮件队列的异步发送。
Queue对象存储在哪?注意到taskworker.py
中根本没有创建Queue的代码,所以,Queue对象存储在taskmanager.py
进程中:
而Queue
之所以能通过网络访问,就是通过QueueManager
实现的。由于QueueManager
管理的不止一个Queue
,所以,要给每个Queue
的网络调用接口起个名字,比如get_task_queue
。
authkey
有什么用?这是为了保证两台机器正常通信,不被其他机器恶意干扰。如果taskworker.py
的authkey
和taskmanager.py
的authkey
不一致,肯定连接不上。
正则表达式
[0-9a-zA-Z\_]
可以匹配一个数字、字母或者下划线;[0-9a-zA-Z\_]+
可以匹配至少由一个数字、字母或者下划线组成的字符串,比如'a100'
,'0_Z'
,'Py3000'
等等;[a-zA-Z\_][0-9a-zA-Z\_]*
可以匹配由字母或下划线开头,后接任意个由一个数字、字母或者下划线组成的字符串,也就是Python合法的变量;[a-zA-Z\_][0-9a-zA-Z\_]{0, 19}
更精确地限制了变量的长度是1-20个字符(前面1个字符+后面最多19个字符)。
A|B
可以匹配A或B,所以(P|p)ython
可以匹配'Python'
或者'python'
。
表示行的开头,^
^\d
表示必须以数字开头。
$
表示行的结束,\d$
表示必须以数字结束。
你可能注意到了,py
也可以匹配'python'
,
但是加上^py$
就变成了整行匹配,就只能匹配'py'
了
re模块
Python提供re
模块,包含所有正则表达式的功能。
>>> import re
>>> re.match(r'^\d{3}\-\d{3,8}$', '010-12345')
<_sre.SRE_Match object at 0x1026e18b8>
>>> re.match(r'^\d{3}\-\d{3,8}$', '010 12345')
>>>
match()
方法判断是否匹配,如果匹配成功,返回一个Match
对象,否则返回None
。常见的判断方法就是
import re test = '用户输入的字符串' if re.match(r'正则表达式',test):
print 'ok'
else:
print 'failed'
切分字符串
用正则表达式切分字符串比用固定的字符更灵活,请看正常的切分代码:
>>> 'a b c'.split(' ')
['a', 'b', '', '', 'c']
嗯,无法识别连续的空格,用正则表达式试试:
>>> re.split(r'\s+', 'a b c')
['a', 'b', 'c']
无论多少个空格都可以正常分割。加入,
试试:
>>> re.split(r'[\s\,]+', 'a,b, c d')
['a', 'b', 'c', 'd']
再加入;
试试:
>>> re.split(r'[\s\,\;]+', 'a,b;; c d')
['a', 'b', 'c', 'd']
分组(group)
编译
当我们在Python中使用正则表达式时,re模块内部会干两件事情:
编译正则表达式,如果正则表达式的字符串本身不合法,会报错;
用编译后的正则表达式去匹配字符串。
>>> import re
# 编译:
>>> re_telephone = re.compile(r'^(\d{3})-(\d{3,8})$')
# 使用:
>>> re_telephone.match('010-12345').groups()
('010', '12345')
>>> re_telephone.match('010-8086').groups()
('010', '8086')
re_email = re.compile(r'[a-z\.]{3,20})@([a-z]+\.[a-z]{3})')
常用内建模块
batteried included
collection是Python内建的一个集合摸块,提供了许多有用的集合类。
namedtuple
tuple 可以表示不变集合,例如,一个点的二维坐标可以表示为:
>>> p = (1, 2)
nametuple 是一个函数,它用来创建一个自定义的tuple对象
并且规定了tuple
元素的个数,并可以用属性而不是索引来引用tuple
的某个元素。
这样一来,我们用namedtuple
可以很方便地定义一种数据类型,它具备tuple的不变性,又可以根据属性来引用,使用十分方便。
>>> from collections import namedtuple
>>> Point = namedtuple('Point', ['x', 'y'])
>>> p = Point(1, 2)
>>> p.x
1
>>> p.y
2
deque
list 访问快,但是插入和删除慢,因为List是线性存储。
deque是双向链表,适合用于队列和栈
>>> from collections import deque
>>> q = deque(['a', 'b', 'c'])
>>> q.append('x')
>>> q.appendleft('y')
>>> q
deque(['y', 'a', 'b', 'c', 'x'])
deque 可以实现List的:append(), pop(), appendleft(), popleft()
defultdict
使用dict
时,如果引用的Key不存在,就会抛出KeyError
。如果希望key不存在时,返回一个默认值,就可以用defaultdict
:
>>> from collections import defultdit
>>> dd = defaultdict(lambda: 'N/A')
>>> dd['key1'] = 'abc'
>>> dd['key1']
'abc'
>>> dd['key2']
'N/A'
OrderedDict
>>> od = OrderedDict([('a', 1), ('b', 2), ('c', 3)])
>>> od # OrderedDict的Key是有序的
OrderedDict([('a', 1), ('b', 2), ('c', 3)])
Counter
Counter
是一个简单的计数器,例如,统计字符出现的个数:
>>> from collections import Counter
>>> c = Counter()
>>> for ch in 'programming':
... c[ch] = c[ch] + 1
...
>>> c
Counter({'g': 2, 'm': 2, 'r': 2, 'a': 1, 'i': 1, 'o': 1, 'n': 1, 'p': 1})
Counter
实际上也是dict
的一个子类,上面的结果可以看出,字符'g'
、'm'
、'r'
各出现了两次,其他字符各出现了一次。
Base64
base64是一种用64 个字符来表示任意二进制数据的方法。
Base64编码会把3字节的二进制数据编码为4字节的文本数据,长度增加33%,好处是编码后的文本数据可以在邮件正文、网页等直接显示。
对二进制数据进行处理,每3个字节一组,一共是3x8=24
bit,划为4组,每组正好6个bit
如果要编码的二进制数据不是3的倍数,最后会剩下1个或2个字节怎么办?Base64用\x00
字节在末尾补足后,再在编码的末尾加上1个或2个=
号,表示补了多少字节,解码的时候,会自动去掉。
由于=
字符也可能出现在Base64编码中,但=
用在URL、Cookie里面会造成歧义,所以,很多Base64编码后会把=
去掉
自动去掉=的:
def b64decode_self(str):
return base64.b64decode(str+'='*(4-len(str)%4))
struct
在Python中,比方说要把一个32位无符号整数变成字节,也就是4个长度的str
1bit:表示1比特
1byte:表示1字节
1byte=8bit
>>> import struct
>>> struct.pack('>I', 10240099)
'\x00\x9c@c' pack的第一个参数是处理指令,'>I'的意思是: >表示字节顺序是big-endian,也就是网络序,I表示4字节无符号整数。 后面的参数个数要和处理指令一致。 unpack把str变成相应的数据类型: >>> struct.unpack('>IH', '\xf0\xf0\xf0\xf0\x80\x80')
(4042322160, 32896)
Windows的位图文件(.bmp)是一种非常简单的文件格式,我们来用struct
分析一下。
首先找一个bmp文件,没有的话用“画图”画一个。
读入前30个字节来分析:
>>> s = '\x42\x4d\x38\x8c\x0a\x00\x00\x00\x00\x00\x36\x00\x00\x00\x28\x00\x00\x00\x80\x02\x00\x00\x68\x01\x00\x00\x01\x00\x18\x00'
BMP格式采用小端方式存储数据,文件头的结构按顺序如下:
两个字节:'BM'
表示Windows位图,'BA'
表示OS/2位图; 一个4字节整数:表示位图大小; 一个4字节整数:保留位,始终为0; 一个4字节整数:实际图像的偏移量; 一个4字节整数:Header的字节数; 一个4字节整数:图像宽度; 一个4字节整数:图像高度; 一个2字节整数:始终为1; 一个2字节整数:颜色数。
所以,组合起来用unpack
读取:
>>> struct.unpack('<ccIIIIIIHH', s)
('B', 'M', 691256, 0, 54, 40, 640, 360, 1, 24)
结果显示,'B'
、'M'
说明是Windows位图,位图大小为640x360,颜色数为24。
请编写一个bmpinfo.py
,可以检查任意文件是否是位图文件,如果是,打印出图片大小和颜色数。
import struct
def test(img_dir):
with open(img_dir, 'rb')as f:
tu = struct.unpack('<ccIIII\
IIHH', f.read(30))
if tu[0] == 'B' and tu[1] == 'M':
print 'windows bitmap,size:%s, width:%s,\
height:%s' %(tu[2],tu[6], tu[7])
elif tu[0] == 'B' and tu[1] == 'A':
print 'OS\2:, width:%s, height:%s' %(tu[6], tu[7])
else:
print 'sorry'
f.close() test('/Users/Y1Y/Desktop/1.bmp')
socket
# 导入socket库:
import socket
# 创建一个socket:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 建立连接:
s.connect(('www.sina.com.cn', 80))
创建Socket
时,AF_INET
指定使用IPv4协议,如果要用更先进的IPv6,就指定为AF_INET6
。SOCK_STREAM
指定使用面向流的TCP协议,这样,一个Socket
对象就创建成功,但是还没有建立连接。
客户端要主动发起TCP连接,必须知道服务器的IP地址和端口号。新浪网站的IP地址可以用域名www.sina.com.cn
自动转换到IP地址,但是怎么知道新浪服务器的端口号呢?
答案是作为服务器,提供什么样的服务,端口号就必须固定下来。由于我们想要访问网页,因此新浪提供网页服务的服务器必须把端口号固定在80
端口,因为80
端口是Web服务的标准端口。其他服务都有对应的标准端口号,例如SMTP服务是25端口,FTP服务是21端口,等等。端口号小于1024的是Internet标准服务的端口,端口号大于1024的,可以任意使用。
因此,我们连接新浪服务器的代码如下:
s.connect(('www.sina.com.cn', 80))
注意参数是一个tuple,包含地址和端口号。
建立TCP连接后,我们就可以向新浪服务器发送请求,要求返回首页的内容:
# 发送数据:
s.send('GET / HTTP/1.1\r\nHost: www.sina.com.cn\r\nConnection: close\r\n\r\n')
Python复习 基础知识的更多相关文章
- Python数据挖掘——基础知识
Python数据挖掘——基础知识 数据挖掘又称从数据中 挖掘知识.知识提取.数据/模式分析 即为:从数据中发现知识的过程 1.数据清理 (消除噪声,删除不一致数据) 2.数据集成 (多种数据源 组合在 ...
- Python 面向对象基础知识
面向对象基础知识 1.什么是面向对象编程? - 以前使用函数 - 类 + 对象 2.什么是类什么是对象,又有什么关系? class 类: def 函数1(): pass def 函数2(): pass ...
- python 爬虫基础知识一
网络爬虫(又被称为网页蜘蛛,网络机器人,在FOAF社区中间,更经常的称为网页追逐者),是一种按照一定的规则,自动的抓取万维网信息的程序或者脚本. 网络爬虫必备知识点 1. Python基础知识2. P ...
- Python:基础知识
python是一种解释型.面向对象的.带有动态语义的高级程序语言. 一.下载安装 官网下载地址:https://www.python.org/downloads 下载后执行安装文件,按照默认安装顺序安 ...
- Python学习-基础知识-2
目录 Python基础知识2 一.二进制 二.文字编码-基础 为什么要有文字编码? 有哪些编码格式? 如何解决不同国家不兼容的编码格式? unicode编码格式的缺点 如何既能全球通用还可以规避uni ...
- Java白皮书学习笔记+Head First Java--用于自我复习 基础知识篇
本笔记是摘与Hava白皮书上面的内容,用来给自己做提醒的,因此大概并不适合Java的学习者作为笔记参考使用. 以我的水平现在还看不懂这个... 一.基础知识篇 1.常量 final关键字指示常量,只能 ...
- 第2章 Python编程基础知识 第2.1节 简单的Python数据类型、变量赋值及输入输出
第三节 简单的Python数据类型.变量赋值及输入输出 Python是一门解释性语言,它的执行依赖于Python提供的执行环境,前面一章介绍了Python环境安装.WINDOWS系列Python编辑和 ...
- Python入门 ---基础知识
Python入门不知道这些你还是承早放弃吧!真的 Python 简介 Python 是一个高层次的结合了解释性.编译性.互动性和面向对象的脚本语言. Python 的设计具有很强的可读性,相比其他语言 ...
- 10分钟学会Python函数基础知识
看完本文大概需要8分钟,看完后,仔细看下代码,认真回一下,函数基本知识就OK了.最好还是把代码敲一下. 一.函数基础 简单地说,一个函数就是一组Python语句的组合,它们可以在程序中运行一次或多次运 ...
随机推荐
- docker安全最佳实践概述
/************************************************* * Author : Samson * Date : 08/07/2015 * Test plat ...
- Zoj2421 广搜
<span style="color:#330099;">/* M - 广搜 加强 Time Limit:2000MS Memory Limit:65536KB 64b ...
- wamp中apache2.4.9配置httpd.conf允许外部访问
安装最新的wamp后发现通过外部网络无法访问本机的apache.在网上查询了相关问题,所有的答案基本都是说在httpd.conf文件中加入语句Allow from all.但是这些对应的是ap ...
- css实现弹出窗体始终垂直水平居中
<!DOCTYPE html><html> <head> <meta charset=" utf-8"> <meta name ...
- 传统的Java虚拟机和Android的Dalvik虚拟机及其ART模式
Java虚拟机的解释执行引擎称为“基于栈的执行引擎”,其中所指的“栈”就是操作数栈.因此我们也称Java虚拟机是基于栈的,这点不同于Android虚拟机,Android虚拟机是基于寄存器的. 基于栈的 ...
- UltimateRecyclerView的用法具体解释
近期在用非常多第三方库的时候,发现有一些附带的demo写的不是非常全面或者样例的代码太多,凝视太少,要想使用还要去看下源代码什么的(.. .用第三方开源库不就是想节省时间嘛).所以决定每周两到三篇.写 ...
- poj 1730Perfect Pth Powers(分解质因数)
id=1730">Perfect Pth Powers Time Li ...
- Android-基本控件和详解四种布局方式
转自:https://www.cnblogs.com/ludashi/p/4883915.html 一.常用基本控件 1.TextView 看到Android中的TextView, 我不禁的想到了iO ...
- iOS开发-14款状态栏(StatusBar)开源软件
本文转载至 http://mobile.51cto.com/hot-418125.htm 之前逛街看到移动做推广,有一个定位应用挺好的,合理的利用了状态栏,做了一些消息提醒和隐藏动画,自己回家就做了一 ...
- if __name__
我们经常在python 程序中看到 if __name__ == '__main__' :这代表什么意思? python中 模块是对象,并且所有的模块都有一个内置属性 __name__.一个模 ...