• 第3章 py基础考察点


    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中如何实现深拷贝?

  • 相关阅读:
    做好技术的量的累积,实现业绩的质的飞跃|专访宜信财富技术负责人刘宝剑
    深入理解MySQL索引
    程序的一生:从源程序到进程的辛苦历程
    Serializable详解(1):代码验证Java序列化与反序列化
    关于Java序列化的问题你真的会吗?
    Dubbo源码解析之SPI(一):扩展类的加载过程
    [C#] 命令总线模式
    C#构造函数在继承时必须要求与父类型构造函数入参相同怎么办?
    如何通过JavaScript构建Asp.net服务端控件
    Javascript iframe交互并兼容各种浏览器的解决方案
  • 原文地址:https://www.cnblogs.com/xzpin/p/11616737.html
Copyright © 2020-2023  润新知