Python基础总结

Python语言特性

Python是静态还是动态类型?是强类型还是弱类型?

动态强类型语言

​ 动态还是静态指的是编译期还是运行期确定类型
​ 强类型指的是不会发生隐式类型转换

Python作为后端语言优缺点

​ 为什么要用Python ?
​ 胶水语,应用广泛 (web 爬虫 测试 人工智能)
​ 语言灵活,生产力高
​ 性能问题、代码维护问题、python2/3兼容问题

什么是鸭子类型

“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。”

鸭子类型更关注接口而非类型

​ 关注点在对象的行为,而不是类型(duck typing)
​ 比如file,StringIO,socket 对象都支持read/write方法(file likeobject)
​ 再比如定义了_iter__魔术方法的对象可以用for 迭代

什么是monkey patch?(猴子补丁)

什么是monkey patch?哪些地方用到了?自己如何实现?

​ 所谓的monkey patch就是运行时替换
​ 比如gevent库需要修改内置的socket

from gevent import monkey; monkey.patch_socket()
什么是自省?
Introspection

​ 运行时判断一个对象的类型的能力
​ Python一切皆对象,用type, id, isinstance获取对象类型信息
​ Inspect模块提供了更多获取对象信息的函数

什么是列表和字典推导
List Comprehension
[i for i in range(10) if i % 2 == 0] #一种快速生成list/dict/set的方式。用来替代map/filter等
(i fori in range(10) if i % 2 == O) #返回生成器,可以节省内存
知道Python之禅吗
The Zen of Python

​ Tim Peters编写的关于Python编程的准则

​ 编程拿不准的时候可以参考

import this #编程拿不准的时候可以参考
The Zen of Python, by Tim Peters
《蟒蛇的禅宗》,作者蒂姆·彼得斯
Beautiful is better than ugly.
美总比丑好。
Explicit is better than implicit.
显性比隐性好。
Simple is better than complex.
简单总比复杂好。
Complex is better than complicated.
复杂总比复杂好。
Flat is better than nested.
扁平比嵌套好。
Sparse is better than dense.
稀疏比密集好。
Readability counts.
可读性。
Special cases aren't special enough to break the rules.
特殊情况并不特别到足以打破规则。
Although practicality beats purity.
尽管实用性胜过纯洁性。
Errors should never pass silently.
错误不应该悄无声息地过去。
Unless explicitly silenced.
除非显式地沉默。
In the face of ambiguity, refuse the temptation to guess.
面对模棱两可,拒绝猜测的诱惑。
There should be one-- and preferably only one --obvious way to do it.
应该有一种——最好只有一种——显而易见的方法来做到这一点。
Although that way may not be obvious at first unless you're Dutch.
虽然这种方式一开始可能不太明显,除非你是荷兰人。
Now is better than never.
现在总比没有好。
Although never is often better than *right* now.
虽然从来没有比现在更好。
If the implementation is hard to explain, it's a bad idea.
如果实现很难解释,那就不是一个好主意。
If the implementation is easy to explain, it may be a good idea.
如果实现很容易解释,那么它可能是一个好主意。
Namespaces are one honking great idea -- let's do more of those!
命名空间是一个伟大的想法——让我们做更多的命名空间!
Python2/3差异

​ print成为了函数
​ 字符编码问题。Python3不再有Unicode 对象,默认str 就是unicode
​ 除法变化。Python3除号返回浮点数(python2整数除法会整数截断)
​ 优化的 super()方便直接调用父类函数。
​ Chained exceptions Python3重新抛出异常不会丢失栈信息(raise from 保存异常栈信息)
​ range,zip, map, dict.values, etc. are alliterators返回迭代器

#类型注解(type hint)。帮助IDE 实现类型检查
def hello(name: str)->str:
	return 'hello' + name
#高级解包操作
a, b, *rest = range(10) 
#Keyword only arguments   限定关键字参数
def add(a,b,*,c):
	return a+b+c

print(add(1,2,c=3))
Python3新增

​ yield from 链接子生成器
​ asyncio内置库,async/await 原生协程支持异步编程
​ 新的内置库enum, mock, asyncio, ipaddress, concurrent.futures 等

Python3改进

​ 生成的pyc文件统一放到_pycache___
​ 一些内置库的修改。urllib, selector 等
​ 性能优化

Python2/3工具

​ six模块
​ 2to3等工具转换代码

#from __future__ import print_function
__future__
Python函数
以下Python代码分别输出什么?
#可变类型作为参数
def flist(l):
    l.append(0)
    print(id(l))
    print(l)
ll = []
flist(ll)
flist(ll)

#[0]
#[0,0]
#不可变类型作为参数
def fstr(s):
    s += 'a'
    print(id(l))
    print(s)
 ss = "hehe"
fstr(ss)
fstr(ss)

#hehea
#hehea
容易混淆的问题

传递的是值还是引用呢?
都不是。唯一支持的参数传递是共享传参
Call by Object (Call by Object Reference or Call by Sharing)
Call by sharing(共享传参)。函数形参获得实参中各个引用的副本

Python可变/不可变对象

​ 不可变对象 bool/int/float/tuple/str/frozenset
​ 可变对象 list/set/dict

#示例
def clear_list(l):
    l = []
ll = [1,2,3]
clear_list(ll)
print(ll)
#[1, 2, 3]

#记住默认参数只计算一次
def flist(l=[1]):
    l.append(1)
    print(l)
flist()
flist()
#[1, 1]
#[1, 1, 1]

函数传递中*args,**kwargs含义是什么

​ 用来处理可变参数
​ *args被打包成tuple
​ **kwargs被打包成dict

Python异常机制

Python使用异常处理错误(有些语言©使用错误码)
所有异常继承BaseException
SystemExit/KeyboardInterrupt/GeneratorExit
Exception

使用异常的常见场景

什么时候需要捕获处理异常呢?看Python内置异常的类型
网络请求(超时、连接错误等)
资源访问(权限问题、资源不存在)
代码逻辑(越界访问、KeyError 等)

异常的处理
try:
	#func									#可能会抛出异常的代码
except(Exception1, Exception2) as e: 		#可以捕获多个异常并处理
	#异常处理代码								
else:										#异常没有发生时的代码逻辑
	#pass
finally:									#无论异常是否发生都会执行的代码,
	#pass									#一般处理资源的关闭和释放
如何自定义异常

​ 继承Exception 实现自定义异常(想想为什么不是BaseException)
​ 给异常加上一些附加信息
​ 处理一些业务相关的特定异常( raise MyException)

Python性能分析与优化,GIL
什么是Cpython GIL

​ GIL,Global Interpreter Lock
​ Cpython解释器的内存管理并不是线程安全的
​ 保护多线程情况下对Python 对象的访问
​ Cpython使用简单的锁机制避免多个线程同时执行字节码

GIL的影响

限制了程序的多核执行
同一个时间只能有一个线程执行字节码
CPU密集程序难以利用多核优势
IO期间会释放GIL,对IO密集程序影响不大

如何规避GIL影响

区分CPU和IO密集程序
CPU密集可以使用多进程+进程池
IO密集使用多线程/协程
cython扩展

#注意代码输出  非原子操作不是线程安全的
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)
#输出可能小于10000

为什么有了GIL还要关注线程安全Python中什么操作才是原子的(一步到位执行完)?

​ 一个操作如果是一个字节码指令可以完成就是原子的
​ 原子的是可以保证线程安全的
​ 使用dis操作来分析字节码

import dis
def update_list(l):
	l[0] = 1 	#原子操作不用担心线程安全问题
dis.dis(update_list)
'''
  3           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)
'''
 2           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/多线程

Python生成器与协程
生成器
#Generator示例
def simple_gen():
    yield 'hello'
    yield 'world'
gen = simple_gen()
print(type(gen))	#<class 'generator'>
print( next(gen))	#'hello'
print(next(gen))	#'world'

基于生成器的协程
# 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值会抛异常Stoplteration
#c.shend(None) next(c)

协程注意点

协程需要使用send(None)或者next(coroutine)来『预激』(prime)才能启动
在yield处协程会暂停执行
单独的yield value 会产出值给调用方
可以通过coroutine.send(value)来给协程发送值,发送的值会赋值给yield 表达式左边的变量value = yield
协程执行完成后(没有遇到下一个yield语句)会抛出Stoplteration异常

# 协程装饰器
# 避免每次都要用send预激它
from functools import wraps


def coroutine(func):  # 这样就不用每次都用send(None)启动了

    # 装饰器: 向前执行到第一个yield表达式, 预激'func'
    @wraps(func)
    def primer(*args, **kwargs):  # 1
        gen = func(*args, **kwargs)  # 2
        next(gen)  # 3
        return gen  # 4

    return primer

Python3原生协程
# Python3.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()

Python单元测试
什么是单元测试

Unit Testing
针对程序模块进行正确性检验
一个函数,一个类进行验证
自底向上保证程序正确性

为什么要写单元测试

三无代码不可取(无文档、无注释、无单测)
保证代码逻辑的正确性(甚至有些采用测试驱动开发(TDD))
单测影响设计,易测的代码往往是高内聚低耦合的
​ 回归测试,防止改一处整个服务不可用

单元测试相关库

​ nose/pytest较为常用
​ mock模块用来模拟替换网络请求等
​ coverage 统计测试覆盖率

#测试示例
#test.py
def binary_search(array, target):
    if not array:
        return -1
    beg, end = 0, len(array)
    while beg < end:
        mid = beg + (end - beg) // 2
        if array[mid] == target:
            return mid
        elif array[mid] > target:
            end = mid
        else:
            beg = mid + 1
    return -1


def test():
    """
    如何设计测试用例: (等价类划分)
    -正常值功能测试
    -边界值    (比如最大最小, 最左最右值)
    -异常值    (比如None, 空值,非法值)
    """
    # 正常值,包含有和无两种结果
    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

 #pytest test.py
Python 深拷贝与浅拷贝
深拷贝与浅拷贝的区别

本质的区别是拷贝出来的对象的地址是否和原对象一样,也就是地址的复制还是值的复制的区别

什么是深拷贝?什么是浅拷贝?

深拷贝和浅拷贝都是对象的拷贝,都会生成一个看起来相同的对象

Python 中如何实现深拷贝?

import copy
a = [1, 2, 3, 4, 5, ['a', 'b']]
# 原始对象
b = a  # 赋值,传对象的引用
c = copy.copy(a)  # 对象拷贝,浅拷贝
d = copy.deepcopy(a)  # 对象拷贝,深拷贝
print("a=", a, "\t原数组\tid(a)=", id(a), "id(a[5])=", id(a[5]))
print("b=", b, "\t赋值\tid(b)=", id(b), "id(b[5])=", id(b[5]))
print("c=", c, "\t浅拷贝\tid(c)=", id(c), "id(c[5])=", id(c[5]))
print("d=", d, "\t深拷贝\tid(d)=", id(d), "id(d[5])=", id(d[5]))
print("*" * 70)

a.append(6)  # 修改对象a
a[5].append('c')  # 修改对象a中的['a','b']数组对象
print("修改对象a\t修改对象a中的['a','b']数组对象后")
print("a=", a, "\t原数组\tid(a)=", id(a), "id(a[5])=", id(a[5]))
print("b=", b, "\t赋值\tid(b)=", id(b), "id(b[5])=", id(b[5]))
print("c=", c, "\t浅拷贝\tid(c)=", id(c), "id(c[5])=", id(c[5]))
print("d=", d, "\t深拷贝\tid(d)=", id(d), "id(d[5])=", id(d[5]))

'''
a= [1, 2, 3, 4, 5, ['a', 'b']] 	原数组	id(a)= 2036820079496 id(a[5])= 2036820078920
b= [1, 2, 3, 4, 5, ['a', 'b']] 	赋值	id(b)= 2036820079496 id(b[5])= 2036820078920
c= [1, 2, 3, 4, 5, ['a', 'b']] 	浅拷贝	id(c)= 2036820079624 id(c[5])= 2036820078920
d= [1, 2, 3, 4, 5, ['a', 'b']] 	深拷贝	id(d)= 2036820079560 id(d[5])= 2036820079688
**********************************************************************
修改对象a	修改对象a中的['a','b']数组对象后
a= [1, 2, 3, 4, 5, ['a', 'b', 'c'], 6] 	原数组	id(a)= 2036820079496 id(a[5])= 2036820078920
b= [1, 2, 3, 4, 5, ['a', 'b', 'c'], 6] 	赋值	id(b)= 2036820079496 id(b[5])= 2036820078920
c= [1, 2, 3, 4, 5, ['a', 'b', 'c']] 	浅拷贝	id(c)= 2036820079624 id(c[5])= 2036820078920
d= [1, 2, 3, 4, 5, ['a', 'b']] 	深拷贝	id(d)= 2036820079560 id(d[5])= 2036820079688
'''

Python 中如何正确初始化一个二维数组?

#初始化多维数组,通过for range方法。以初始化二维数组举例:
arr = [[] for i in range(5)]
>>> [[], [], [], [], []]
arr = [[0, 0] for i in range(5)]
arr[2].append(2)
>>> [[0, 0], [0, 0], [0, 0, 2], [0, 0], [0, 0]]

#一维数组,可以使用*或者for range
arr1 = [None for i in range(5)]
>>> [None, None, None, None, None]
arr2 = [None]*5
>>> [None, None, None, None, None]

#########################
#但是用*初始化二维数组则会在修改数组内容时出现错误
arr = [[0, 0]]*5
arr[2] = 2
>>> [[0, 0], [0, 0], 2, [0, 0], [0, 0]] # 直接复制不会出现错误
arr[2].append(2)
>>> [[0, 0, 2], [0, 0, 2], [0, 0, 2], [0, 0, 2], [0, 0, 2]]
arr[2][1] = 5
>>> [[0, 5], [0, 5], [0, 5], [0, 5], [0, 5]]
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值