一、高级特性——生成器,迭代器
生成器
def fib(max):
n, a, b = 0, 0, 1
while n < max:
yield b
a, b = b, a + b
n = n + 1
return 'done'
def triangles():
l1 = [1]
yield l1
l = [1, 1]
yield l
while True:
l = l1 + [l[i] + l[i+1] for i in range(len(l) - 1)] + l1
yield l
if __name__ == '__main__':
t = triangles()
for i in range(10):
print(next(t))
f = fib(15)
for i in range(15):
print(next(f))
#详见博文:https://blog.csdn.net/weixin_48127633/article/details/127667100
二、函数式编程
1. map, reduce ,filter, sorted
#map reduce函数
def fn(x, y):
return x * 10 + y
print(reduce(fn, [1,3,5,7,9]))
#filter函数
print(list(filter(f, [1,2,3,4])))
2. 闭包
返回闭包时牢记一点:返回函数不要引用任何循环变量,或者后续会发生变化的变量
使用闭包时,对外层变量赋值前,需要先使用nonlocal声明该变量不是当前函数的局部变量。
def fun(*args):
def fun_sum():
sum_ = 0
for i in args:
sum_ += i
return sum_
return fun_sum
def count():
fs = []
for i in range(1, 4):
def f():
return i*i
fs.append(f)
return fs
f = fun(1,2,3,4,5)
print(f())
f1, f2, f3 = count()
print(f1(), f2(), f3())
3. 匿名函数
f = lambda n: n % 2 == 1
4. 装饰器
在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)
def log(func):
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper
@log
def now():
print('2015-3-25')
now()
5. 偏函数
functools.partial的作用就是,把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单
int2 = functools.partial(int, base = 2)
print(int2('1010101'))
6. 函数与作用域
运行python3 hello.py获得的sys.argv就是[‘hello.py’]
运行python3 hello.py Michael获得的sys.argv就是[‘hello.py’, ‘Michael’]
#外部不需要引用的函数全部定义成private,只有外部需要引用的函数才定义为public
def _private_1(name):
return 'Hello, %s' % name
def _private_2(name):
return 'Hi, %s' % name
def greeting(name):
if len(name) > 3:
return _private_1(name)
else:
return _private_2(name)
三、面向对象的高级编程
1. __slots__和 @property
slots__允许我们声明并限定类成员,并拒绝类创建__dict__和__weakref__属性以节约内存空间 ;
子类未声明__slots__时,不继承父类的__slots,即此时子类实例可以随意赋值属性;
子类声明__slots__时,继承父类的__slots__,即此时子类的__slots__为其自身+父类的__slots__;
优点:
(1)更快的赋值属性;
(2)节约内存;
我们可以使用@property装饰器来创建只读属性,@property装饰器会将方法转换为相同名称的只读属性,可以与所定义的属性配合使用,这样可以防止属性被修改
class DataSet(object):
def __init__(self):
self._images = 1
self._labels = 2 #定义属性的名称
@property
def images(self): #方法加入@property后,这个方法相当于一个属性,这个属性可以让用户进行使用,而且用户有没办法随意修改。
return self._images
@property
def labels(self):
return self._labels
l = DataSet()
#用户进行属性调用的时候,直接调用images即可,而不用知道属性名_images,因此用户无法更改属性,从而保护了类的属性。
print(l.images) # 加了@property后,可以用调用属性的形式来调用方法,后面不需要加()。
2. 多重继承
3. 定制类
class Student(object):
def __init__(self, name):
self.name = name
def __str__(self):
return 'Student object (name: %s)' % self.name
print(Student('Michael'))
>>>Student object (name: Michael)
s = Student('Michael')
s
<__main__.Student object at 0x109afb310>
#直接显示变量调用的不是__str__(),而是__repr__(),两者的区别是__str__()返回用户看到的字符串,而__repr__()返回程序开发者看到的字符串,也就是说,repr()是为调试服务的
getattr()
只有在没有找到属性的情况下,才调用__getattr__,已有的属性,不会在__getattr__中查找;
call()
任何类,只需要定义一个__call__()方法,就可以直接对实例进行调用;
通过callable()函数,我们就可以判断一个对象是否是“可调用”对象。
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.
4. 枚举类
from enum import Enum
Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
for name, member in Month.__members__.items():
print(name, '=>', member, ',', member.value)
这样我们就获得了Month类型的枚举类,可以直接使用Month.Jan来引用一个常量,或者枚举它的所有成员
5. 元类
元类就是用来创建类的“东西”。你创建类就是为了创建类的实例对象,不是吗?但是我们已经学习到了Python中的类也是对象。好吧,元类就是用来创建这些类(对象)的,元类就是类的类;(type(), class())
def fn(self, name='world'): # 先定义函数
print('Hello, %s.' % name)
Hello = type('Hello', (object,), dict(hello=fn)) # 创建Hello class
h = Hello()
h.hello()
>>> Hello, world.
print(type(Hello))
>>> <class 'type'>
print(type(h))
>>> <class '__main__.Hello'>
要创建一个class对象,type()函数依次传入3个参数:
class的名称;
继承的父类集合,注意Python支持多重继承,如果只有一个父类,别忘了tuple的单元素写法;
class的方法名称与函数绑定,这里我们把函数fn绑定到方法名hello上。
__metaclass__属性
Python会在类的定义中寻找__metaclass__属性,如果找到了,Python就会用它来创建类Foo,如果没有找到,就会用内建的type来创建这个类
四、异常处理
1. 错误处理
try:
print('try...')
r = 10 / int('a')
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')
raise语句如果不带参数,就会把当前错误原样抛出。此外,在except中raise一个Error,还可以把一种类型的错误转化成另一种类型
try:
10 / 0
except ZeroDivisionError:
raise ValueError('input error!')
2. 调试
(1)断言(assert)
def foo(s):
n = int(s)
assert n != 0, 'n is zero!'
return 10 / n
def main():
foo('0')
assert的意思是,表达式n != 0应该是True,否则,根据程序运行的逻辑,后面的代码肯定会出错。
如果断言失败,assert语句本身就会抛出AssertionError
启动Python解释器时可以用-O参数来关闭assert
(2)使用logging模块
import logging
logging.basicConfig(level=logging.INFO)
logging允许你指定记录信息的级别,有debug,info,warning,error等几个级别,当我们指定level=INFO时,logging.debug就不起作用了
import logging
def foo(s):
return 10 / int(s)
def bar(s):
return foo(s) * 2
def main():
try:
bar('0')
except Exception as e:
logging.exception(e)
main()
print('END')
(3)pdb
第4种方式是启动Python的调试器pdb,让程序以单步方式运行,可以随时查看运行状态;
pdb.set_trace() 只需要import pdb,然后,在可能出错的地方放一个pdb.set_trace(),就可以设置一个断点;
五、IO编程
1. 读写文件
f = open('/Users/michael/gbk.txt', 'r', encoding='gbk', errors='ignore')
2. StringIO和BytesIO
StringIO就是在内存中读写str,getvalue()方法用于获得写入后的str;
要读取StringIO,可以用一个str初始化StringIO,然后,像读文件一样读取
>>> from io import StringIO
>>> f = StringIO()
>>> f.write('hello')
5
>>> f.write(' ')
1
>>> f.write('world!')
6
>>> print(f.getvalue())
hello world!
StringIO操作的只能是str,如果要操作二进制数据,就需要使用BytesIO
#请注意,写入的不是str,而是经过UTF-8编码的bytes
>>> from io import BytesIO
>>> f = BytesIO()
>>> f.write('中文'.encode('utf-8'))
6
>>> print(f.getvalue())
b'\xe4\xb8\xad\xe6\x96\x87'
3. 操作文件和目录
os.path.join(path1,path2) 合并目录
os.path.split(path) 拆分目录
os.path.splitext(path) 获取文件扩展名
4. 序列化
把变量从内存中变成可存储或传输的过程称之为序列化,在Python中叫pickling,序列化之后,就可以把序列化后的内容写入磁盘,或者通过网络传输到别的机器上。
反过来,把变量内容从序列化的对象重新读到内存里称之为反序列化,即unpickling;
>>> import pickle
>>> d = dict(name='Bob', age=20, score=88)
>>> pickle.dumps(d)
>>> f = open('dump.txt', 'wb')
>>> pickle.dump(d, f)
>>> f.close()
dumps是将dict转化成str格式,loads是将str转化成dict格式。
dump和load也是类似的功能,只是与文件操作结合起来了。
tips: #类的对象也可以被序列化
def student2dict(std):
return {
'name': std.name,
'age': std.age,
'score': std.score
}
>>> print(json.dumps(s, default=student2dict))
{"age": 20, "name": "Bob", "score": 88}
def dict2student(d):
return Student(d['name'], d['age'], d['score'])
>>> json_str = '{"age": 20, "score": 88, "name": "Bob"}'
>>> print(json.loads(json_str, object_hook=dict2student))
<__main__.Student object at 0x10cd3c190>
六、进程和线程
1.多进程
multiprocessing
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 进程池
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) #可以同时跑4个进程,默认大小是CPU的核数
for i in range(5): #一共有5个进程
p.apply_async(long_time_task, args=(i,))
print('Waiting for all subprocesses done...')
p.close()
p.join() #调用join()之前必须先调用close(),调用close()之后就不能继续添加新的Process了
print('All subprocesses done.')
进程间的通信
管道,消息队列,共享内存;
2. 多线程
由于任何进程默认就会启动一个线程,我们把该线程称为主线程,主线程又可以启动新的线程,Python的threading模块有个current_thread()函数,它永远返回当前线程的实例
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()
print('thread %s ended.' % threading.current_thread().name)
Lock
多线程中,所有变量都由所有线程共享,所以,任何一个变量都可以被任何一个线程修改,因此,线程之间共享数据最大的危险在于多个线程同时改一个变量,把内容给改乱了
import time, threading
#假定这是你的银行存款:
balance = 0
def change_it(n):
# 先存后取,结果应该为0:
global balance
balance = balance + n
balance = balance - n
#def run_thread(n):
for i in range(2000000): #同时修改一个变量会出问题
change_it(n)
balance = 0
lock = threading.Lock()
def run_thread(n):
for i in range(100000):
# 先要获取锁:
lock.acquire()
try:
# 放心地改吧:
change_it(n)
finally:
# 改完了一定要释放锁:
lock.release()
t1 = threading.Thread(target=run_thread, args=(5,))
t2 = threading.Thread(target=run_thread, args=(8,))
t1.start()
t2.start()
t1.join()
t2.join()
print(balance)
优点:
确保了某段关键代码只能由一个线程从头到尾完整地执行;
缺点:
阻止了多线程并发执行,包含锁的代码只能以单线程模式执行,效率下降;
线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁,导致多个线程全部挂起,既不能执行,也无法结束,只能靠操作系统强制终止;
3. 多线程 VS多进程
**多进程**:较稳定,可以分配到不同的机器上,但是开销大;
**多线程**:只能分配到一台机器的多个CPU上,一个线程出错会导致整个进程崩溃;