py基础考察点
py语言特性
py是动态强类型语言
- 动态还是静态指的是编译期还是运行期确定类型
强类型指的是不会发生隐式类型装换
为什么使用py?
* 胶水语言,轮子多,应用广泛 * 语言灵活,生产力高 * 性能问题,代码维护问题,py2/py3不兼容
什么是鸭子类型?
- 当看到一只鸟走起来像鸭子,游泳起来像鸭子,叫起来也像鸭子,那么这只鸟就可以被称为鸭子
关注点 在对象的行为,而不是类型(duck typing)
eg: file,StringIO,socket对象都支持read/write方法
(file like object) 再比如定义__iter__魔法方法的对象可以用for迭代 鸭子类型更关注接口而非类型
class Duck: def quack(self): print("gua gua") class Person: def quack(self): print("我是人类, 但我也会guo guo guo") def in_the_forest(duck): duck.quack() def game(): donald = Duck() john = Person() in_the_forest(donald) in_the_forest(john) print(type(donald)) print(type(john)) print(isinstance(donald, Duck)) print(isinstance(john, Person)) game()
什么是monkey patch? 哪些地方用到了?自己如何实现?
- 所谓的monkey patch 就是运行时替换
//eg:gevent库需要修改内置的socket from gevent import monkey; monkey.patch_socket() import socket print(socket.socket) print("After monkey patch") from gevent import monkey monkey.patch_socket() print(socket.socket) import select print(select.select) monkey.patch_select() print("After monkey patch") print(select.select) import time print(time.time()) def _time(): return 1234 time.time = _time print(time.time())
什么是自省?
introspection
运行时判断一个对象的类型的能力,py一切皆对象 用type,id,isinstance获取对象类型信息
inspect模块提供了更多获取对象信息的函数
ll = [1, 3, 3] d = dict(a=1) #{a:1} print(type(ll)) print(type(d)) print(isinstance(ll, list)) print(isinstance(d, dict)) def add(a, b): if isinstance(a, int): return a + b elif isinstance(a, str): return a.upper() + b print(add(1, 2)) print(add('head', 'tail')) print(id(ll)) print(id(d)) print(ll is d) print(ll is ll)
什么是列表和字典推导
List Comprehension
'''[i for i in range(10) if i % 2 == 0] 一种快速生成list/dict/set的方式。用来替代map/filter等 (i for i in range(10) if i % 2 == 0) 返回生成器''' eg: a = ['a', 'b', 'c'] b = [1, 2, 3] d = {} for i in range(len(a)): d[a[i]] = b[i] print(d) d = {k: v for k, v in zip(a, b)} print(d) # !< output {'a': 1, 'b': 2, 'c': 3} {'a': 1, 'b': 2, 'c': 3} eg: l = [i for i in range(10)] print(l) print(type(l)) l = (i for i in range(10)) print(type(l)) for i in l: print(i)
使用生成器可以大大节省内存
py之禅
import this
py2/3差异常考题
- 使用pyenv安装python版本
py3改进
? print成为函数
? 编码问题,py3不再有unicode对象,默认str就是unicode
? 除法变化,py3除号返回浮点数
? 5/2 = 2.5
? 5//2 = 2
类型注解(type hint) 帮助IDE实现类型检查
def hello(name: str)->str: return "hello" + name print(hello("laowang"))
优化的super()方便直接调用父类函数
高级解包操作, a,b, *ret = range(10)
a, b, *c = range(10) print(a) print(b) print(c) a, b, *_ = range(10) print(a) print(b) #后面没有数字被舍弃
py3改进
Keyword only arguments 限定关键字参数
eg:
#限定关键字参数需要指定参数名传参
def add(a, b, *, c): return a + b +c print(add(1, 2, c = 3))
Chained exceptions Py3重新抛出异常不会丢失栈信息
#raise from 保留异常栈信息
一切返回迭代器
range, zip, map, dict.values,etc are all iterators
py3新增
* yield from 链接子生成器
* asyncio内置库, async/await 原生协程支持异步编程 新的内置库enum, mock, asyncio, ipaddress, concurrent.futures等
py3改进
- 生成的pyc文件统一到__pycache__ 一些内置库的修改, urlib, selector等
- 性能优化等
一些兼容2/3的工具
? six模块
py2 to py3等工具转换代码
__future__
py函数常考题
常考点
参数传递
(不)可变对象
可变参数
以下代码分别输出什么?
* 可变类型作为参数
def flist(l): l.append(0) print(l) l = [] flist(l) flist(l) # !< out: [0] [0, 0]
- 不可变类型作为参数
def fstr(s): s += 'a' print(s) s = "hehe" fstr(s) fstr(s) # !< out: "hehea" "hehea"
py如何传递参数
一个容易混淆的问题
传递值还是引用呢?都不是。唯一支持的参数传递是共享传参
Call by Object(Call by Object Reference or Call by Sharing)
Call by sharing(共享传参) 函数形参获得实参中各个引用的副本
py一切皆对象
py可变/不可变对象
搞懂可变和不可变内置对象有利于理解函数参数的副作用
哪些是可变和不可变对象?
不可变对象 bool/int/float/tuple/str/frozenset 可变对象 list/set/dict
def clear_list(l): l = [] ll = [1, 2, 3] clear_list(ll) print(ll)
py可变参数作为默认参数 记住默认参数只计算一次
def flist(l = [1]): l.append(l) print(l) fl() fl()
py *args **kwargs
函数传递中*args, **kwags 含义是?
用来处理可变参数
*args被打包成tuple
? **kwargs被打包成dict
def print_multiple_args(*args): print(type(args), args) for idx, val in enumerate(args): print(idx, val) print_multiple_args('a', 'b', 'c') print_multiple_args(*['a', 'b', 'c']) # !< out: <class 'tuple'> ('a', 'b', 'c') 0 a 1 b 2 c
def print_kwargs(**kwargs): print(type(kwargs)) for k, v in kwargs.items(): print('{}: {}'.format(k, v)) print_kwargs(a=1, b=2) print_kwargs(**dict(a=1, b=2)) # !< out: <class 'dict'> a: 1 b: 2
def print_all(c, *args, **kwargs): print(c) if args: print(args) if kwargs: print(kwargs) print_all(1, "hello", a="muke") 1 ('hello',) {'a': 'muke'}
py异常机制常考题
什么是py的异常?
py使用异常处理错误(有些语言使用错误码)
BaseException
? SystemExit / KeyboardInterrupt / GeneratorExit
? Exception
使用异常的常见场景
? 什么时候需要捕获处理异常?看py内置异常的类型
? 网络请求(超时, 连接错误等)
? 资源访问(权限问题, 资源不存在)
? 代码逻辑(越界访问, KeyError等)
如何处理py异常
搞懂几个关键字
try: #func #可能会抛出异常的代码 except (Exception1, Exception 2) as e: #可以捕获多个异常并处理 #异常处理的代码 else: #pass #异常没有发生的时候代码逻辑 finally: #pass #无论异常有没有发生都会执行的代码, 一般处理资源的关闭和释放
如何自定义异常
如何自定义自己的异常?为什么需要定义自己的异常?
? 继承Exception实现自定义异常(为什么不是BaseException)
? 给异常加上一些附加信息
? 处理一些业务相关的特定异常(raise MyException)
class MyException(Exception): pass try: raise MyException('my exception') except MyException as e: print(e)
py性能分析与优化,GIL常考题
什么是Cpython GIL
GIL, Global Interpreter Lock
Cpython解释器的内存管理并不是线程安全的
保护多线程情况下对python对象的访问
Cpython使用简单的锁机制避免多个线程同时执行字节码
GIL的影响
限制程序的多核执行
同一个时间只能有一个线程执行字节码
CPU密集程序难以利用多核优势
IO期间会释放GIL, 对IO密集程序影响不大
如何规避GIL影响
区分CPU和IO密集程序
CPU密集可以使用多进程+进程池
IO密集使用多线程/协程
cpython扩展
GIL的实现
# 请问这段代码输出? import threading n = [0] def foo(): n[0] = n[0] + 1 n[0] = n[0] + 1 threads = [] for i in range(5000): t = threading.Thread(target=foo) threads.append(t) for t in threads: t.start() print(n)
# !< 加锁操作 import threading lock = threading.lock() n = [0] def foo(): with lock: n[0] = n[0] + 1 n[0] = n[0] + 1 threads = [] for i in range(5000): t = threading.Thread(target=foo) threads.append(t) for t in threads: t.start() print(n)
为什么有了GIL还要关注线程安全?
py中什么操作才是原子的?
? 一步到位执行完
? 一个操作如果是一个字节码指令可以完成就是原子的
? 原子的是可以保证线程安全的
? 使用dis操作来分析字节码
为什么有GIL还要关注线程安全?
#原子操作 import dis def update_list(l): l[0] = 1 #原子操作, 不用担心线程安全问题 #dis.dis(update_list) """ 280 0 LOAD_CONST 1 (1) 2 LOAD_FAST 0 (l) 4 LOAD_CONST 2 (0) 6 STORE_SUBSCR #单字节码操作,线程安全 8 LOAD_CONST 0 (None) 10 RETURN_VALUE """ #非原子操作 不是线程安全 def incr_list(l): l[0] += 1 #危险!!不是原子操作 dis.dis(incr_list) """ 295 0 LOAD_FAST 0 (l) 2 LOAD_CONST 1 (0) 4 DUP_TOP_TWO 6 BINARY_SUBSCR 8 LOAD_CONST 2 (1) 10 INPLACE_ADD #需要多个字节码操作, 有可能在线程执行 #过程中切到其它线程 12 ROT_THREE 14 STORE_SUBSCR 16 LOAD_CONST 0 (None) 18 RETURN_VALUE """
如何剖析程序性能
使用各种profile工具(内置或第三方)
- 二八定律,大部分时间耗时在少量代码上
- 内置的profile/cprofile等工具
- 使用pyflame(uber开源)的火焰图工具
服务端性能优化措施
web应用一般语 言不会成为瓶颈
- 数据结构与算法优化
- 数据库层:索引优化 慢查询消除 批量操作减少IO,NoSQL
- 网络IO:批量操作, pipeline操作 减少IO
- 缓存:使用内存数据库 redis/memcached
- 异步:asyncio celery
- 并发:gevent/多线程
py生成器与协程
Generator
- 生成器就是生成值得函数
- 当一个函数有了yield关键字就成了生成器
- 生成器可以挂起执行并且保持当前执行的状态
什么是生成器
def simple_gen(): yield 'hello' yield 'world' gen = simple_gen() print(type(gen)) #'generator' object print(next(gen)) #'hello' print(next(gen)) #'world'
基于生成器的协程
? py3之前没有原生协程,只有基于生成器的协程
pep342(Coroutines via Enhanced Generators)增强生成器功能
生成器可以通过yield暂停执行和产出数据
? 同时支持send()向生成器发送数据和throw()向生成器抛异常
# !< Generator Based Coroutine def coro(): hello = yield 'hello' #yield关键字在右边作为表达式, 可以被send值 yield hello c = coro() #输出'hello' 这里调用next产出第一个值'hello', 之后函数暂停 print(next(c)) #再次调用send发送值 此时hello变量赋值为'world' 之后yield产出hello变量的值'world' print(c.send('world')) #之后协程结束, 后续再send值会抛异常StopIteration
协程注意点
协程需要使用send(None)或者next(coroutine)来预激prime才能启动
在yield处协程会暂停执行
单独的yield value 会产出值给调用方
可以通过coroutine.send(value)来给协程发送值,发送的值会赋值给yield表达式左边的变量
value = yield
协程执行完成后(没有遇到下一个yield语句)会抛出StopIteration异常
协程装饰器
避免每次都要用send预激它
from functools import wraps def coroutine(func): #不用每次都用send(None)启动 """装饰器:向前执行到第一个'yield'表达式,预激`func`""" @wraps(func) def primer(*args, **kwargs): gen = func(*args, **kwargs) next(gen) return gen return primer
py3原生协程
py3.5引入async/await支持原生协程(native coroutine)
import asyncio import datetime import random async def display_date(num, loop): end_time = loop.time() + 50.0 while True: print('Loop: {} Time: {}'.format(num, datetime.datetime.now())) if(loop.time() + 1.0) >= end_time: break await asyncio.sleep(random.randint(0, 5)) loop = asyncio.get_event_loop() asyncio.ensure_future(display_date(1, loop)) asyncio.ensure_future(display_date(2, loop)) loop.run_forever()
单元测试
什么是单元测试
Unit Testing
针对程序模块进行正确性检验
一个函数, 一个类进行验证
自底向上保证程序正确性
为什么要写单元测试
三无代码不可取(无文档, 无注册, 无单测)
- 保证代码逻辑的正确性(甚至有些采用测试驱动开发(TDD))
- 单测影响设计, 易测的代码往往是高内聚低耦合的
- 回归测试, 防止改一处整个服务不可用
单元测试相关的库
- nose/pytest 较为常用
- mock模块用来模拟替换网络请求等
- coverage统计测试覆盖率
def binary_search(array, target): if not array: return -1 beg, end = 0, len(array) while beg < end: mid = beg + (end - beg) // 2 #py3 if array[mid] == target: return mid elif array[mid] > target: end = mid else: beg = mid + 1 return -1 def test(): """ 如何设计测试用例:等价类划分 - 正常值功能测试 - 边界值(eg 最大最小, 最左最右值) - 异常值(eg None, 空值, 非法值) :return: """ #正常值, 包含有和无两种结果 assert binary_search([0, 1, 2, 3, 4, 5], 1) == 1 assert binary_search([0, 1, 2, 3, 4, 5], 6) == -1 assert binary_search([0, 1, 2, 3, 4, 5], -1) == -1 #边界值 assert binary_search([0, 1, 2, 3, 4, 5], 0) == 0 assert binary_search([0, 1, 2, 3, 4, 5], 5) == 5 assert binary_search([0], 0) == 0 # #异常值 assert binary_search([], 1) == -1 #pip install pytest #pytest xx.py
py深拷贝和浅拷贝 深拷贝与浅拷贝的区别 什么是深拷贝?什么是浅拷贝? py中如何实现深拷贝?