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

猜你喜欢

转载自www.cnblogs.com/xuzhaoping/p/11616737.html