第一篇博客是关于Python 3.0的一些总结,主要参考了廖雪峰和Python基础教程的一些内容,工欲兴其事,必先利其器,目前,主流的深度学习框架都提供了Python接口,所以,对Python的熟悉和掌握可以算是机器学习基础中的基础了。
python基础
字符串和编码
字符编码
- python3中,字符串以Unicode编码
- 由于Python的字符串类型是
str
,在内存中以Unicode表示,一个字符对应若干个字节。如果要在网络上传输,或者保存到磁盘上,就需要把str
变为以字节为单位的bytes
。 - 要注意区分
'ABC'
和b'ABC'
,前者是str
,后者虽然内容显示得和前者一样,但bytes
的每个字符都只占用一个字节。 - 文本总是Unicode,由str类型表示,二进制数据则由bytes类型表示。Python3不会以任意隐式的方式混用str和bytes,正是这使得两者的区分特别清晰。你不能拼接字符串和字节包,也无法在字节包里搜索字符串(反之亦然),也不能将字符串传入参数为字节包的函数(反之亦然)。
数据结构
tuple所谓的“不变”是说,tuple的每个元素,指向永远不变。即指向
'a'
,就不能改成指向'b'
,指向一个list,就不能改成指向其他对象,但指向的这个list本身是可变的set和dict类似,也是一组key的集合,但不存储value。由于key不能重复,所以,在set中,没有重复的key。
>>> a = 'abc' >>> b = a.replace('a', 'A') >>> b 'Abc' >>> a 'abc'
当我们调用
a.replace('a', 'A')
时,实际上调用方法replace
是作用在字符串对象'abc'
上的,而这个方法虽然名字叫replace
,但却没有改变字符串'abc'
的内容。*replace
方法创建了一个新字符串'Abc'
并返回 ,如果我们用变量b
指向该新字符串,就容易理解了,变量a
仍指向原有的字符串'abc'
,但变量b
却指向新字符串'Abc'
了
函数
普通函数
- 在语法上,返回一个tuple可以省略括号,而多个变量可以同时接收一个tuple,按位置赋给对应的值,所以,Python的函数返回多值其实就是返回一个tuple
- 数据类型检查可以用内置函数
isinstance()
实现 - 定义默认参数要牢记一点:默认参数必须指向不变对象!
- 关键字参数**para,可以扩展函数的功能。比如,在
person
函数里,我们保证能接收到name
和age
这两个参数,但是,如果调用者愿意提供更多的参数,我们也能收到。试想你正在做一个用户注册的功能,除了用户名和年龄是必填项外,其他都是可选项,利用关键字参数来定义这个函数就能满足注册的需求。
迭代
- Python的
for
循环抽象程度要高于C的for
循环,因为Python的for
循环不仅可以用在list或tuple上,还可以作用在其他可迭代对象上。 for
循环其实可以同时使用两个甚至多个变量
生成器
在Python中,这种一边循环一边计算的机制,称为生成器:generator。
生成器的创建:
- 列表生成式的
[]
改成()
,就创建了一个generator - 这就是定义generator的另一种方法。如果一个函数定义中包含
yield
关键字,那么这个函数就不再是一个普通函数,而是一个generator
- 列表生成式的
生成器的引用:
- generator保存的是算法,每次调用
next(g)
或g.next()
,就计算出g
的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration
的错误。 - for 循环
- generator保存的是算法,每次调用
generator和函数的执行流程不一样。函数是顺序执行,遇到
return
语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()
的时候执行,遇到yield
语句返回,再次执行时从上次返回的yield
语句处继续执行。但是用
for
循环调用generator时,发现拿不到generator的return
语句的返回值。如果想要拿到返回值,必须捕获StopIteration
错误,返回值包含在StopIteration
的value
中except StopIteration as e: print('Generator return value:', e.value)
普通函数调用直接返回结果;generator函数的“调用”实际返回一个generator对象
迭代器
- 凡是可作用于
for
循环的对象都是Iterable
类型;isinstance(obj,iterable)
- 凡是可作用于
next()
函数的对象都是Iterator
类型,它们表示一个惰性计算的序列; - 集合数据类型如
list
、dict
、str
等是Iterable
但不是Iterator
,不过可以通过iter()
函数获得一个Iterator
对象。
高阶函数
map/reduce
map()
函数接收两个参数,一个是函数,一个是Iterable
,map
将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator
返回。reduce
把一个函数作用在一个序列[x1, x2, x3, ...]
上,这个函数必须接收两个参数,reduce
把结果继续和序列的下一个元素做累积计算
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
filter
filter()
也接收一个函数和一个序列。和map()
不同的是,filter()
把传入的函数依次作用于每个元素,然后根据返回值是True
还是False
决定保留还是丢弃该元素。
sorted
sorted(list,key,reverse)
函数也是一个高阶函数,它还可以接收一个key
函数来实现自定义的排序,key指定的函数将作用于list的每一个元素上,并根据key函数返回的结果进行排序。
要进行反向排序,不必改动key函数,可以传入第三个参数reverse=True
返回函数
def lazy_sum(*args):
def sum():
ax = 0
for n in args:
ax = ax + n
return ax
return sum
我们在函数lazy_sum
中又定义了函数sum
,并且,内部函数sum
可以引用外部函数lazy_sum
的参数和局部变量,当lazy_sum
返回函数sum
时,相关参数和变量都保存在返回的函数中,这种称为“闭包(Closure)”的程序结构拥有极大的威力。
匿名函数
>>> f = lambda x: x * x
>>> f
<function <lambda> at 0x101c6ef28>
>>> f(5)
25
匿名函数有个限制,就是只能有一个表达式,不用写return
,返回值就是该表达式的结果
装饰器
在函数调用后增强函数的功能但又不希望修改原先函数的定义,这种在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。
def log(func): def wrapper(*args, **kw): print('call %s():' % func.__name__) return func(*args, **kw) return wrapper
log
,因为它是一个decorator,所以接受一个函数作为参数,并返回一个函数。我们要借助Python的@语法,把decorator置于函数的定义处def log(text): def decorator(func): def wrapper(*args, **kw): print('%s %s():' % (text, func.__name__)) return func(*args, **kw) return wrapper return decorator
如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数,写出来会更复杂。
面向对象编程OOP
- OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。
-
- 面向过程的程序设计把计算机程序视为一系列的命令集合,即一组函数的顺序执行。
- 而面向对象的程序设计把计算机程序视为一组对象的集合,而每个对象都可以接收其他对象发过来的消息,并处理这些消息,计算机程序的执行就是一系列消息在各个对象之间传递。
类和实例
- 类是创建实例的模板,而实例则是一个一个具体的对象,各个实例拥有的数据都互相独立,互不影响;
- 方法就是与实例绑定的函数,和普通函数不同,方法可以直接访问实例的数据;
- 通过在实例上调用方法,我们就直接操作了对象内部的数据,但无需知道方法内部的实现细节。
- 和静态语言不同,Python允许对实例变量绑定任何数据,也就是说,对于两个实例变量,虽然它们都是同一个类的不同实例,但拥有的变量名称都可能不同
访问限制
- 如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线
__
,在Python中,实例的变量名如果以__
开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问;不能直接访问__name
是因为Python解释器对外把__name
变量改成了_Student__name
,所以,仍然可以通过_Student__name
来访问__name
变量 - 以一个下划线开头的实例变量名,比如
_name
,这样的实例变量外部是可以访问的
实例属性和类属性
- 实例属性属于各个实例所有,互不干扰;
- 类属性属于类所有,所有实例共享一个属性;
- 不要对实例属性和类属性使用相同的名字,否则将产生难以发现的错误。
高级编程
使用slots
class Student(object):
__slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称
类属性方法
Python内置的@property
装饰器就是负责把一个方法变成属性调用的
@property
的实现比较复杂,我们先考察如何使用。把一个getter方法变成属性,只需要加上@property
就可以了,此时,@property
本身又创建了另一个装饰器@score.setter
,负责把一个setter方法变成属性赋值
定制类
iter
- 如果一个类想被用于
for ... in
循环,类似list或tuple那样,就必须实现一个__iter__()
方法,该方法返回一个迭代对象,然后,Python的for循环就会不断调用该迭代对象的__next__()
方法拿到循环的下一个值,直到遇到StopIteration
错误时退出循环。 - 实例本身就是迭代器,故返回自己
return self
class 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 # 返回下一个值
getattr
当我们调用类的方法或属性时,如果不存在,就会报错。
写一个
__getattr__()
方法,动态返回一个属性,返回函数也是完全可以的class Student(object): def __getattr__(self, attr): if attr=='age': return lambda: 25
只有在没有找到属性的情况下,才调用
__getattr__
,已有的属性,比如name
,不会在__getattr__
中查找。
call
一个对象实例可以有自己的属性和方法,当我们调用实例方法时,我们用
instance.method()
来调用;我们也可以在实例本身上调用属性
class Student(object): def __init__(self, name): self.name = name def __call__(self): print('My name is %s.' % self.name)
>>> s = Student('Michael') >>> s() # self参数不要传入 My name is Michael.
__call__()
还可以定义参数。对实例进行直接调用就好比对一个函数进行调用一样
使用元类
动态语言和静态语言最大的不同,就是函数和类的定义,不是编译时定义的,而是运行时动态创建的。
异常和调试
错误
try
- 当我们认为某些代码可能会出错时,就可以用
try
来运行这段代码,如果执行出错,则后续代码不会继续执行,而是直接跳转至错误处理代码,即except
语句块,执行完except
后,如果有finally
语句块,则执行finally
语句块
调用栈
$ python3 err.py
Traceback (most recent call last): # 告诉我们这是错误的跟踪信息。
File "err.py", line 11, in <module>#调用main()出错了,在代码文件err.py的第11行代码,但原因是第9行
main()
File "err.py", line 9, in main #调用bar('0')出错了,在代码文件err.py的第9行代码,但原因是第6行:
bar('0')
File "err.py", line 6, in bar #原因是return foo(s) * 2这个语句出错了,但这还不是最终原因
return foo(s) * 2
File "err.py", line 3, in foo #原因是return 10 / int(s)这个语句出错了,这是错误产生的源头
return 10 / int(s)
ZeroDivisionError: division by zero #错误类型ZeroDivisionError
抛出错误
- 如果要抛出错误,首先根据需要,可以定义一个错误的class,选择好继承关系,然后,用
raise
语句抛出一个错误的实例
调试
I/O编程
序列化
原因
- 在程序运行的过程中,所有的变量都是在内存中
- 可以随时修改变量,但是一旦程序结束,变量所占用的内存就被操作系统全部回收。
我们把变量从内存中变成可存储或传输的过程称之为序列化,把变量内容从序列化的对象重新读到内存里称之为反序列化。
Python提供了
pickle
模块来实现序列化。- pickle.dumps()
方法把任意对象序列化成一个
bytes,然后,就可以把这个
bytes写入文件。或者用另一个方法
pickle.dump()`直接把对象序列化后写入一个file-like Object:
>>> import pickle >>> d = dict(name='Bob', age=20, score=88) >>> pickle.dumps(d) b'\x80\x03}q\x00(X\x03\x00\x00\x00ageq\x01K\x14X\x05\x00\x00\x00scoreq\x02KXX\x04\x00\x00\x00nameq\x03X\x03\x00\x00\x00Bobq\x04u.' >>> f = open('dump.txt', 'wb') >>> pickle.dump(d, f) >>> f.close()
- 当我们要把对象从磁盘读到内存时,可以先把内容读到一个
bytes
,然后用pickle.loads()
方法反序列化出对象,也可以直接用pickle.load()
方法从一个file-like Object
中直接反序列化出对象。
- pickle.dumps()
python高级编程
进程和线程
概念
- 对于操作系统来说,一个任务就是一个进程(Process);在一个进程内部,要同时干多件事,就需要同时运行多个“子任务”,我们把进程内的这些“子任务”称为线程(Thread)。
- 多任务的实现有3种方式:
- 多进程模式;
- 多线程模式;
- 多进程+多线程模式。
多进程
系统调用
Unix/Linux操作系统提供了一个
fork()
系统调用,它非常特殊。普通的函数调用,调用一次,返回一次,但是fork()
调用一次,返回两次,因为操作系统自动把当前进程(称为父进程)复制了一份(称为子进程),然后,分别在父进程和子进程内返回。- 子进程永远返回
0
,而父进程返回子进程的ID。 - 一个父进程可以fork出很多子进程,所以,父进程要记下每个子进程的ID,而子进程只需要调用
getppid()
就可以拿到父进程的ID。
- 子进程永远返回
Python的
os
模块封装了常见的系统调用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
multiprocessing
模块提供了一个Process
类来代表一个进程对象,创建子进程时,只需要传入一个执行函数和函数的参数,创建一个Process
实例,用start()
方法启动,这样创建进程比fork()
还要简单。join()
方法可以等待子进程结束后再继续往下运行,通常用于进程间的同步。
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.')
pool
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()
对Pool
对象调用join()
方法会等待所有子进程执行完毕,调用join()
之前必须先调用close()
,调用close()
之后就不能继续添加新的Process
了
进程间通信
Python的multiprocessing
模块包装了底层的机制,提供了Queue
、Pipes
等多种方式来交换数据。
- multiprocessing.Queue()
以Queue为例,在父进程中创建两个子进程,一个往Queue
里写数据,一个从Queue
里读数据:
multiprcessing.Queue.put()
为 入队操作
multiprcessing.Queue.get()
为 出队操作
multiprocessing.Pipe()
- Pipe()函数返回一对由管道连接的连接对象,默认情况下是双工(双向)。
- Pipe()返回的两个连接对象代表管道的两端。 每个连接对象都有send()和recv()方法(等等)。 请注意,如果两个进程(或线程)尝试同时读取或写入管道的同一端,管道中的数据可能会损坏。 当然,同时使用管道不同端的过程也不会有风险。
- 返回表示管道末端的一对
Connection(conn1,conn2)
对象。 - 如果
duplex
为True(默认),则管道是双向的。 - 如果
duplex
是False,那么管道是单向的:conn1只能用于接收消息,conn2只能用于发送消息。
多线程
threading
启动一个线程就是把一个函数传入并创建Thread
实例,然后调用start()
开始执行
t = threading.Thread(target=loop, name='LoopThread')
t.start()
t.join()
lock
多线程和多进程最大的不同在于,多进程中,同一个变量,各自有一份拷贝存在于每个进程中,互不影响,而多线程中,所有变量都由所有线程共享,所以,任何一个变量都可以被任何一个线程修改,因此,线程之间共享数据最大的危险在于多个线程同时改一个变量,把内容给改乱了。
- 创建一个锁就是通过
threading.Lock()
来实现 - 由于锁只有一个,无论多少线程,同一时刻最多只有一个线程持有该锁
- 当多个线程同时执行
lock.acquire()
时,只有一个线程能成功地获取锁,然后继续执行代码,其他线程就继续等待直到获得锁为止。
常用內建模块
virtualenv
virtualenv就是用来为一个应用创建一套“隔离”的Python运行环境。
我们用
pip
安装virtualenv创建一套独立的python运行环境
创建目录
创建一个独立的Python运行环境
virtualenv (--no-site-packages(不复制所有第三方包)) dirname
进入该环境中
source dirname/bin/acivate
,此时,pip安装的包全部在dirname这个环境下deactivate
退出当前环境