文章目录
- 1. 删除字典键,合并两个字典
- 2. 谈下python的GIL
- 3. 列表去重
- 4. fun(*args,**kwargs)中的*args,**kwargs什么意思?
- 4. format的基本用法
- 5. python2和python3的range(100)的区别
- 6. 字符串转字典
- 7. 什么是闭包?
- 8. 如何理解装饰器(戴帽子)?
- 9. functools模块中的偏函数(Partial function)
- 10. python内建数据类型有哪些?
- 11. 面向对象中__new__和__init__区别
- 12. __new__方法疑问?
- 13. with方法打开处理文件过程
- 14. map()函数的使用
- 15. 随机数生成
- 16. 避免转义,使用r
- 17. 正则匹配
- 18. python中断言方法 assert(expressions)
- 19. python中可变数据类型和不可变数据类型,并简述原理
- 20. 字符串去重并从小到大排序输出
- 21. lambda函数使用
- 22. sort和sorted方法排序
- 23. 统计出现次数
- 24. 字符串去掉整数、小数和字母操作
- 25. 格式化输出时间戳和星期
- 26. 简述Django的orm
- 27. 将[[1,2],[3,4],[5,6]]一行代码展开该列表,得出[1,2,3,4,5,6]
- 28. 提高python运行效率的方法
- 29. 判断类型以及一个类中是否包含某个方法
- 30. Python中单例模式详细解析
- 31. 分别从前端、后端、数据库阐述web项目的性能优化
- 32. 简述多线程、多进程
- 33. 列出几种魔法方法并简要介绍用途
- 34. 列表推导式和生成器,字典推导式
- 35. pandas常用读取文件类型方法
- 36. 正则匹配邮箱(其他文章中有常用大全)
- 37. python字符串转字典
- 38. 简述python引用计数机制和垃圾回收机制
- 39. 简述乐观锁和悲观锁
- 40. python的复制,深拷贝和浅拷贝的区别
- 41. Python进阶 __slots__魔法
- 42. heapq模块(堆排序)
- 43. itertools模块
- 44. collections模块常用的工具类
1. 删除字典键,合并两个字典
# 删除字典
del dic["name"]
# 合并字典,方式1
for k, v in b.items():
a[k] = v
# 合并字典,方式2
a.update(b)
# 合并字典,方式3
dict(a, **b)
# 合并字典,方式4
dict(a.items() + b.items())
2. 谈下python的GIL
-
并行:多个CPU同时执行多个任务,就好像有两个程序,这两个程序是真的在两个不同的CPU内同时被执行。
-
并发:CPU交替处理多个任务,还是有两个程序,但是只有一个CPU,会交替处理这两个程序,而不是同时执行,只不过因为CPU执行的速度过快,而会使得人们感到是在“同时”执行,执行的先后取决于各个程序对于时间片资源的争夺。
-
并行和并发同属于多任务,目的是要提高CPU的使用效率。这里需要注意的是,一个CPU永远不可能实现并行,即一个CPU不能同时运行多个程序,但是可以在随机分配的时间片内交替执行(并发),就好像一个人不能同时看两本书,但是却能够先看第一本书半分钟,再看第二本书半分钟,这样来回切换
-
Guido(吉多)创建python时就只考虑到单核cpu,解决多线程之间数据完整性和状态同步的最简单方法自然就是加锁,于是有了GIL这把超级大锁。因为cpython解析只允许拥有GIL全局解析器锁才能运行程序,这样就保证了保证同一个时刻只允许一个线程可以使用cpu。
-
Python的多线程在多核CPU上,只对于IO密集型计算产生正面效果;而当有至少有一个CPU密集型线程存在,那么多线程效率会由于GIL而大幅下降。
-
什么是GIL?
即全局解释器锁(global interpreter lock),每个线程在执行时候都需要先获取GIL,保证同一时刻只有一个线程可以执行代码,即同一时刻只有一个线程使用CPU,也就是说多线程并不是真正意义上的同时执行。
- 如何解决GIL锁的问题?
1.更换cpython为jpython(不建议)
2.使用多进程完成多线程的任务
3.在使用多线程可以使用c语言去实现
- 什么时候会释放Gil锁?
1.遇到像 i/o操作这种 会有时间空闲情况造成cpu闲置的情况会释放Gil
2.会有一个专门 ticks 进行计数 一旦ticks数值达到100 这个时候释放Gil锁,线程之间开始竞争Gil锁 (说明:
ticks这个数值可以进行设置来延长或者缩减获得Gil锁的线程使用cpu的时间)
- 互斥锁 和 Gil锁 的关系?
Gil锁: 保证同一时刻只有一个线程能使用到cpu
互斥锁 : 多线程时,保证修改共享数据时有序的修改,不会产生数据修改混乱
首先假设只有一个进程,这个进程中有两个线程 Thread1,Thread2, 要修改共享的数据date, 并且有互斥锁
执行以下步骤
-
多线程运行,假设Thread1获得GIL可以使用cpu,这时Thread1获得 互斥锁lock,Thread1可以改date数据(但并没有开始修改数据)
-
Thread1线程在修改date数据前发生了i/o操作或者ticks计数满100 (注意就是没有运行到修改data数据),这个时候Thread1让出了Gil,Gil锁可以被竞争
-
Thread1和Thread2开始竞争Gil (注意:如果Thread1是因为i/o阻塞 让出的Gil Thread2必定拿到Gil,如果Thread1是因为ticks计数满100让出Gil 这个时候 Thread1 和 Thread2 公平竞争)
-
假设 Thread2正好获得了GIL,运行代码去修改共享数据date,由于Thread1有互斥锁lock,所以Thread2无法更改共享数据date,这时Thread2让出Gil锁 , GIL锁再次发生竞争
-
假设Thread1又抢到GIL,由于其有互斥锁Lock所以其可以继续修改共享数据data,当Thread1修改完数据释放互斥锁lock,Thread2在获得GIL与lock后才可对data进行修改
- 附加说明
(1)对于thread来说,申明一个global变量,用thread.Lock的context包裹住三行就搞定了。而multiprocessing由于进程之间无法看到对方的数据,只能通过在主线程申明一个Queue,put再get或者用share memory的方法。这个额外的实现成本高了不少。
(2)像JPython和IronPython这样的解析器由于实现语言的特性,他们不需要GIL的帮助。然而由于用了Java/C#用于解析器实现,他们也失去了利用社区众多C语言模块有用特性的机会。所以这些解析器也因此一直都比较小众。
(3)改进GIL
1.将切换颗粒度从基于opcode计数改成基于时间片计数
2.避免最近一次释放GIL锁的线程再次被立即调度
3.新增线程优先级功能(高优先级线程可以迫使其他线程释放所持有的GIL锁)
3. 列表去重
lst2 = list(set(lst1))
4. fun(args,**kwargs)中的args,**kwargs什么意思?
可以不恰当地理解为,*变为元组, **变为字典。
# *args的用法:当传入的参数个数未知,且不需要知道参数名称时。
def kw_tuple(*args):
return args
kw_tuple(1, 2, 3)
# 用在format函数中
print('{2}, {1}, {0}'.format(*'abc'))
# **args的用法:当传入的参数个数未知,但需要知道参数的名称时。
def kw_dict(**kwargs):
return kwargs
kw_dict(a=1, b=2, c=3)
4. format的基本用法
print('{} {}'.format('hello','world')) # 不带字段
print('{1} {1} {0}'.format('hello','world')) # 带数字字段
print('{a} {b} {a}'.format(b='hello',a='world')) # 带关键字
coord = (3, 5)
'X: {0[0]}; Y: {0[1]}'.format(coord)
# 通过下标或key匹配参数
a = {'a': 'test_a', 'b': 'test_b'}
'X: {0[a]}; Y: {0[b]}'.format(a)
# 格式化时间
import datetime
d = datetime.datetime(2010, 7, 4, 12, 15, 58)
'{:%Y-%m-%d %H:%M:%S}'.format(d)
# '2010-07-04 12:15:58'
5. python2和python3的range(100)的区别
- 在python2中range函数返回的是一个列表对象
- 在python3中返回是一个可迭代对象(等同python2中xrange()函数)
- 在Python3不会生成列表,而是只有在使用时才会动态生成的生成器,占用很小的内存,节省内存。
6. 字符串转字典
- 使用 ast.literal_eval 进行转换既不存在使用 json 进行转换的问题,也不存在使用 eval 进行转换的安全性问题,因此推荐使用 ast.literal_eval。
import ast
user = '{"name" : "john", "gender" : "male", "age": 28}'
user_dict = ast.literal_eval(user)
7. 什么是闭包?
- 闭包函数的必要条件:
-
闭包函数必须返回一个函数对象
-
闭包函数返回的那个函数必须引用外部变量(一般不能是全局变量),而返回的那个函数内部不一定要return
注意:闭包函数引用的外部变量不一定就是其父函数的参数,也可以是父函数作用域内的任意变量
- 闭包例子
# ENV>>> Python 3.6
# NO.1
def line_conf(a, b):
def line(x):
return a * x + b
return line
# NO.2
def line_conf():
a = 1
b = 2
def line(x):
print(a * x + b)
return line
# NO.3
def _line_(a, b):
def line_c(c):
def line(x):
return a * (x ** 2) + b * x + c
return line
return line_c
# 不需要重复定义直线概念,只要每次生成闭包函数的实例对象
# 定义两条直线,接NO.1
line_A = line_conf(2, 1) # y=2x+1
line_B = line_conf(3, 2) # y=3x+2
# 打印x对应y的值
print(line_A(1)) # 3
print(line_B(1)) # 5
- 显式地查看“闭包”
__closure__属性返回的是一个元组对象,包含了闭包引用的外部变量,它是只读的,不能由人为修改。
def line_conf():
a = 1
b = 2
def line(x):
print(a * x + b)
return line
L = line_conf()
print(line_conf().__closure__)
# (<cell at 0x05BE3530: int object at 0x1DA2D1D0>,<cell at 0x05C4DDD0: int object at 0x1DA2D1E0>)
for i in line_conf().__closure__:
# 打印引用的外部变量值
print(i.cell_contents)
# 1 ;2
# 1. 若主函数内的闭包不引用外部变量,就不存在闭包,主函数的_closure__属性永远为None.
# 2. 若主函数没有return子函数,就不存在闭包,主函数不存在_closure__属性.
- 为何叫闭包?
闭包作为对象被返回时,它的引用变量就已经确定(已经保存在它的__closure__属性中),不会再被修改,它的所有变量就已经固定,形成了一个封闭的对象,这个对象包含了其引用的所有外部、内部变量和表达式。当然,闭包的参数例外。
- 闭包可以保存运行环境
在Python中,循环体内定义的函数是无法保存循环执行过程中的不停变化的外部变量的,即普通函数无法保存运行环境!但是闭包可以做到。
- 扩展
python中的装饰器,即使用@语法实现的单例模式就是利用闭包实现的,只不过用了@作为语法糖,使写法更简洁,闭包函数将函数的唯一实例保存在它内部的__closure__属性中,在再次创建函数实例时,闭包检查该函数实例已存在自己的属性中,不会再让他创建新的实例,而是将现有的实例返给它。
8. 如何理解装饰器(戴帽子)?
- 装饰器含义
装饰器,就是增强函数或类的功能的一个函数。让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。有了装饰器,可以极大地提高代码的复用性和可读性。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。
- 无参装饰器
def use_logging(func):
def wrapper(*args, **kwargs):
logging.warn("%s is running" % func.__name__)
return func(*args, **kwargs)
return wrapper
def bar():
print('i am bar')
bar = use_logging(bar) # 给bar重新赋值
bar()
# 在这个例子中,函数进入和退出时,被称为一个横切面(Aspect),
# 这种编程方式被称为面向切面的编程(Aspect-Oriented Programming)
- 语法糖
@use_logging
def bar():
print("i am bar")
bar()
# 相当于省去bar = use_logging(bar)这一句
- 带参装饰器 (三层函数定义)
def login(text):
def decorator(func):
def wrapper(*args,**kargs):
print('%s----%s'%(text, func.__name__))
return func(*args,**kargs)
return wrapper
return decorator
# 等价于 ==> (login(text))(f) ==> 返回 wrapper
@login('this is a parameter of decorator')
def f():
print('2019-06-13')
# 等价于 ==> (login(text))(f)() ==> 调用 wrapper()并返回f()
f()
上面的login是允许带参数的装饰器 (三层函数)。它实际上是对原有装饰器的一个函数封装,并返回一个装饰器。可以理解为一个含有参数的闭包。当调用@login(‘this …’),Python能够发现这一层的封装,并把参数传递到装饰器的环境中.
- 不带参的类装饰器
# 基于类装饰器的实现,必须实现 __call__ 和 __init__两个内置函数。
# __init__ :接收被装饰函数; __call__ :实现装饰逻辑
class logger(object):
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print("[INFO]: the function {func}() is running..."\
.format(func=self.func.__name__))
return self.func(*args, **kwargs)
@logger
def say(something):
print("say {}!".format(something))
say("hello")
- 带参的类装饰器
# __init__ : 不再接收被装饰函数,而是接收传入参数.
# __call__ :接收被装饰函数,实现装饰逻辑.
class logger(object):
def __init__(self, level='INFO'):
self.level = level
def __call__(self, func): # 接受函数
from functools import wraps
@wraps(func)
def wrapper(*args, **kwargs):
print("[{level}]: the function {func}() is running..."\
.format(level=self.level, func=func.__name__))
func(*args, **kwargs)
return wrapper #返回函数
@logger(level='WARNING')
def say(something):
print("say {}!".format(something))
say("hello")
类装饰器(必须带init和call方法)具有灵活度大、高内聚、封装性等优点。使用类装饰器还可以依靠类内部__call__方法,当使用 @ 形式将装饰器附加到函数上时,就会调用此方法。
- wraps 装饰器
def wrapper(func):
def inner_function():
pass
return inner_function
@wrapper
def wrapped():
pass
print(wrapper(wrapped).__name__)
print(wrapped.__name__)
# 输出都为"inner_function",因为重新赋值了 wrapped = wrapper(wrapped)
1. 缺点
- 装饰器使原函数的元信息不见了,比如函数的docstring、__name__、参数列表等。
2. 解决
- 使用 functools .wraps 装饰器,它的作用就是将被修饰的函数(wrapped)的一些属性值赋值给修饰器函数(wrapper),或者说把原函数的元信息拷贝到装饰器函数中。
from functools import wraps
def wrapper(func):
@wraps(func) << --- 添加这句
def inner_function():
pass
return inner_function
@wrapper
def wrapped():
pass
print(wrapped.__name__)
# 输出 "wrapped"
3. wraps 其实是一个偏函数对象(partial),源码如下
def wraps(wrapped,
assigned = WRAPPER_ASSIGNMENTS,
updated = WRAPPER_UPDATES):
return partial(update_wrapper, wrapped=wrapped,
assigned=assigned, updated=updated)
wraps其实就是调用了一个函数update_wrapper,知道原理后,我们改写上面的代码,在不使用 wraps的情况下,也可以让 wrapped.name 打印出 wrapped,代码如下:
from functools import update_wrapper
WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__',
'__annotations__')
def wrapper(func):
def inner_function():
pass
update_wrapper(inner_function, func, assigned=WRAPPER_ASSIGNMENTS)
return inner_function
@wrapper
def wrapped():
pass
print(wrapped.__name__)
- 使用偏函数与类实现装饰器
绝大多数装饰器都是基于函数和闭包实现的,但这并非制造装饰器的唯一方式。Python 对某个对象是否能通过装饰器(@decorator)形式使用只有一个要求:decorator 必须是一个“可被调用(callable)的对象。除函数之外,类也可以是 callable 对象,只要实现了__call__ 函数,还有比较少人使用的偏函数也是 callable 对象。
如下所示,DelayFunc 是一个实现了 __call__ 的类,delay 返回一个偏函数,在这里 delay就可以做为一个装饰器。
import time
import functools
class DelayFunc:
def __init__(self, duration, func):
self.duration = duration
self.func = func
def __call__(self, *args, **kwargs):
print(f'Wait for {self.duration} seconds...')
time.sleep(self.duration)
return self.func(*args, **kwargs)
def eager_call(self, *args, **kwargs):
print('Call without delay')
return self.func(*args, **kwargs)
def delay(duration):
"""
装饰器:推迟某个函数的执行。
同时提供 .eager_call 方法立即执行
"""
# 此处为了避免定义额外函数,
# 直接使用 functools.partial 帮助构造 # DelayFunc 实例
return functools.partial(DelayFunc, duration)
@delay(duration=2)
def add(a, b):
return a+b
add(3,5) # 直接调用实例,进入 __call__
输出" Wait for 2 seconds...
8 "
4. 常见内置装饰器
- 常见内置装饰器三种 @property 、@staticmethod、@classmethod
1. @property
把类内方法当成属性来使用,必须要有返回值,相当于getter;
假如没有定义 @func.setter 修饰方法的话,就是只读属性
class Car:
def __init__(self, name, price):
self._name = name
self._price = price
@property
def car_name(self):
return self._name
#car_name可以读写的属性
@car_name.setter
def car_name(self, value):
self._name = value
# car_price是只读属性
@property
def car_price(self):
return str(self._price) + '万'
benz = Car('benz', 30)
print(benz.car_name) # benz
benz.car_name = "baojun"
print(benz.car_name) # baojun
print(benz.car_price) # 30万
2. @staticmethod
静态方法,不需要表示自身对象的self和自身类的cls参数,就跟使用普通无相关函数一样
3. @classmethod
类方法,不需要self参数,但第一个参数需要是表示自身类的cls参数
class Demo(object):
text = "三种方法的比较"
def instance_method(self):
print("调用实例方法")
@classmethod
def class_method(cls):
print("调用类方法")
print("在类方法中 访问类属性 text: {}".format(cls.text))
print("在类方法中 调用实例方法 instance_method: {}".format(cls().instance_method()))
@staticmethod
def static_method():
print("调用静态方法")
print("在静态方法中 访问类属性 text: {}".format(Demo.text))
print("在静态方法中 调用实例方法 instance_method: {}".format(Demo().instance_method()))
if __name__ == "__main__":
# 实例化对象
d = Demo()
# 对象可以访问 实例方法、类方法、静态方法
# 通过对象访问text属性
print(d.text)
# 通过对象调用实例方法
d.instance_method()
# 通过对象调用类方法
d.class_method()
# 通过对象调用静态方法
d.static_method()
# 类可以访问类方法、静态方法
# 通过类访问text属性
print(Demo.text)
# 通过类调用类方法
Demo.class_method()
# 通过类调用静态方法
Demo.static_method()
4. @staticmethod 和 @classmethod 的区别和使用场景
- 区别
在定义静态类方法和类方法时,@staticmethod 装饰的静态方法里面,想要访问类属性或调用实例方法,必须需要把类名写上;
而@classmethod装饰的类方法里面,会传一个cls参数,代表本类,这样就能够避免手写类名的硬编码。
在调用静态方法和类方法时,实际上写法都差不多,一般都是通过 类名.静态方法() 或 类名.类方法()。
也可以用实例化对象去调用静态方法和类方法,但为了和实例方法区分,最好还是用类去调用静态方法和类方法。
- 使用场景
所以,在定义类的时候,假如不需要用到与类相关的属性或方法时,就用静态方法@staticmethod;
假如需要用到与类相关的属性或方法,然后又想表明这个方法是整个类通用的,而不是对象特异的,就可以使用类方法@classmethod。
- 装饰器的执行顺序(实际从上到下即可)
def decorator_a(func):
print('Get in decorator_a')
def inner_a(*args, **kwargs):
print('Get in inner_a')
return func(*args, **kwargs)
return inner_a
def decorator_b(func):
print('Get in decorator_b')
def inner_b(*args, **kwargs):
print('Get in inner_b')
return func(*args, **kwargs)
return inner_b
@decorator_b # 等效 f = decorator_b(f)
@decorator_a # 等效 f = decorator_a(f)
def f(x):
print('Get in f')
return x * 2
f(1)
# 输出 a,b(未调用已执行)| b,a(调用了f函数后才执行的)
# decorator_b(decorator_a(f(x))) -->
# init_f --> dar_inner_a (new1_f) --> dar_inner_b (new2_f)
# --> f(1) (inner_b(1)) --> inner_a(1) --> f(1)
# ^---开始调用函数
# decorator_a(f) --> inner_a --> decorator_b(inner_a) -->
# inner_b --> f(1)调用 --> inner_b(1) b结束 --> inner_a(1) a结束
9. functools模块中的偏函数(Partial function)
举例
# int()函数把字符串转换为整数,当仅传入字符串时,默认按十进制转换,
# 但int()函数还提供额外的base参数,默认值为10
# 如果传入base参数,就可以做N进制的转换
int('12345')
int('12345', base=8)
int('12345', 16)
# 输出"12345" "5349" "74565"
# 假设要转换大量的二进制字符串,每次都传入int(x,base=2)非常麻烦,
# 于是,我们想到,可以定义一个int2()的函数,默认把base=2传进去
def int2(x, base=2):
return int(x, base)
# functools.partial就是帮助创建一个偏函数的,不需要我们自己定义int2(),
# 可以直接使用下面的代码创建一个新的函数int2
import functools
int2 = functools.partial(int, base=2)
int2('1000000')
64
# 简单总结functools.partial的作用就是,把一个函数的某些参数给固定
# 住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单
- 创建偏函数时,实际可以接收函数对象、*args和**kw这3个参数
int2 = functools.partial(int, base=2)
int2('10010')
# 相当于:kw={'base':2} 即 int('10010', **kw)
max2 = functools.partial(max, 10)
max2(5, 6, 7)
# 相当于把10作为*args的一部分自动加到左边:
# args = (10, 5, 6, 7), max(*args), 结果为10
10. python内建数据类型有哪些?
整型–int,布尔型–bool,字符串–str,列表–list,元组–tuple,字典–dict
11. 面向对象中__new__和__init__区别
-
__init__用于初始化一个新实例,控制这个初始化的过程,比如添加一些属性,做一些额外的操作,发生在类实例被创建完以后。它是实例级别的方法
-
__new__通常用于控制生成一个新实例的过程。它是类级别的方法
-
new是一个静态方法,而init是一个实例方法
-
new方法会返回一个创建的实例(cls 的实例),而init返回是 None
-
只有在new返回一个cls的实例时后面的init才能被调用
-
当创建一个新实例时调用new(构造方法),初始化一个实例时用init(初始化方法)
class Person(object):
# cls == Person args == (name,age) kwargs == {}
def __new__(cls, *args, **kwargs):
print("in __new__")
instance = object.__new__(cls)
print(cls, args, kwargs)
return instance
# 一定要返回类实例 return object.__new__(cls) 等价于 super().__new__(cls)
# self 为新实例,其余的参数与被传递给 __new__() 的相同
def __init__(self, name, age):
print("in __init__")
self._name = name
self._age = age
p = Person("Wang", 33)
# 输出 in __new__
# <class '__main__.Person'> ('Wang', 33) {}
# in __init__
# 需要注意:有cls参数,python解释器会自动识别为当前类名,__new__创建的是当前
# 类的实例,会自动调用__init__函数,通过return语句里面调用的__new__函数
# 的第一个参数是cls来保证是当前类实例,如果是其他类的类名,;那么实际创建返回的就是其他
# 类的实例,就不会调用当前类的__init__函数,也不会调用其他类的__init__函数。
12. __new__方法疑问?
- 有cls参数,为什么不是类方法?
- 这是个静态方法装饰器的特例吗?
- 除了object里面,还有别的地方这么用吗?
1.是静态方法,因为object.__new__(cls)在这里只是一个绑定源对象object的普通方法,也就是说cls仅仅是一个位置参数,跟object类无关。常规意义下__new__()的确是类方法。
# object.__new__(cls)源码
@staticmethod # known case of __new__
def __new__(cls, *more): # known special case of object.__new__
""" Create and return a new object. See help(type) for accurate signature. """
pass
2.不是,所有的静态方法都只是绑定类对象或者实例对象的普通方法
3.没有,实际上不要把cls这个普通的position argument和类方法当中的代表类对象的cls position argument混淆了
13. with方法打开处理文件过程
1. with作用
打开文件在进行读写的时候可能会出现一些异常状况,如果按照常规的f.open写法,我们需要try,except,else,finally,做异常判断,并且文件最终不管遇到什么情况,最后都要执行f.close()关闭文件,with方法帮我们实现这些过程
2. with使用了上下文管理器,可以自动获取上下文相关内容
-
上下文管理协议(Context Management Protocol):包含方法 __enter__() 和 __exit__(),支持该协议的对象必须要实现这两个方法
-
上下文管理器(Context Manager):支持上下文管理协议的对象,这种对象实现了__enter__() 和 __exit__() 方法。上下文管理器定义执行 with 语句时要建立的运行时上下文,负责执行 with 语句块上下文中的进入与退出操作。通常使用 with 语句调用上下文管理器,也可以通过直接调用其方法来使用
-
运行时上下文(runtime context):由上下文管理器创建,通过上下文管理器的 __enter__() 和__exit__() 方法实现,__enter__() 方法在语句体执行之前进入运行时上下文,__exit__() 在语句体执行完后从运行时上下文退出
with expression [as targer]:
code body
3. 创建上下文管理器的方式
# 方法1:自定义
class OpenMe:
def __init__(self, file):
self.file = file
self.ff = open(self.file, 'r')
def __enter__(self):
print('enter')
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.ff.close()
print('exit')
if exc_tb:
print('have error')
def read_me(self):
print('read')
return self.ff.read()
with OpenMe('xx.py') as f:
print(f.read_me())
# __enter__函数的返回结果赋值给as变量
# exc_type: 错误的类型
# exc_val: 错误类型对应的值
# exc_tb: 代码中错误发生的位置
# 一般对变量 exc_tb 进行检测,如果不为 None,表示发生了异常
# 方法2:使用 contexlib 模块中的 @contextmanager 装饰器
from contextlib import contextmanager
@contextmanager
def some_generator(<arguments>):
<setup>
try:
yield <value>
finally:
<cleanup>
with some_generator(<arguments>) as <variable>:
<body>
# 等价于
<setup>
try:
<variable> = <value>
<body>
finally:
<cleanup>
# yield 之前的代码会在上下文管理器中作为 __enter__()方法执行
# yield 就是赋值给 as变量
# 所有在 yield 之后的代码会作为 __exit__() 方法执行
4.注意与举例
1、@contextmanager (源码很清晰) 应该仅仅用来写自包含的上下文管理函数,逻辑控制部分要自己实现。
2、如果你有一些对象(一个文件,网络连接,多线程中锁或资源的获取与释放),需要支持 with 语句,那么你就需要单独实现 __enter__() 方法和 __exit__() 方法。
from pymysql import connect
class OpenDB():
def __init__(self, username, password, database):
self.conn = connect(host="localhost", port=3306, user=username, password=password, database=database, charset='utf-8')
self.cs = self.conn.cursor()
def __enter__(self):
return self.cs # 返回游标
def __exit__(self, exc_type, exc_val, exc_tb):
self.conn.commit() # 提交sql操作
self.cs.close() # 关闭游标
self.conn.close() # 关闭数据库连接
with OpenDB('root', '123456', 'test_database') as d:
sql = "select * from test_database"
d.execute(sql)
content = d.fetchall()
for temp in content:
print(temp)
14. map()函数的使用
- map(func,iterable1,…,iterablen)
第一个参数接受一个函数名,后面的参数接受一个或多个可迭代的序列,返回一个将 function 应用于 iterable中每一项并输出其结果的迭代器
m = map(lambda x, y: (x ** y, x + y), [2, 4, 6], [3, 2, 1])
print(list(m)) # [(8, 5), (16, 6), (6, 7)]
list(map(int, ['1234']) # [1, 2, 3, 4]
15. 随机数生成
r1 = round(random.random(), 2)
r2 = random.randint(0, 10)
r3 = np.random.randn(5)
#随机小数 含区间内随机整数 5个随机小数数组
16. 避免转义,使用r
# 单行 多行 文件中
print (r'\t \\ \n \\t')
print (r'''静夜思
床前明月光\t\t,
''')
file1=open(r"text.txt","r")
17. 正则匹配
符号 | 含义 | 示例 |
---|---|---|
. | 可以匹配任意字符,但不包含换行符’\n’ | Pyt.on ->Pytmon |
\ | 转义符,一般用于保留字符串中的特殊元字符 | 10\.3 ->10.3 |
| | 逻辑或 | 人a|A->人a或者人A |
[] | 分组,用于匹配的一组字符 | m[aA]n ->man 或者 mAn |
\d与\D | \d匹配任意数字,\D代表所有的非\d | 等价于 [0-9]和[^0-9] |
\s与\S | \s匹配任意空白字符,\S代表所有非\s | 等价于[\t\n\r]和 [^\t\n\r] |
\w与\W | \w匹配字母数字和下划线,\W代表所有非\w | 等价于[a-zA-Z0-9_]和[^a-zA-Z0-9_] |
* | 匹配前一个字符0到无穷次,等价于{0,} | OK* ->O或者OK 或 OKK |
+ | 匹配前一个字符1到无穷次,等价于{1,} | OK+ ->OK或者OK 或 OKK |
? | 匹配前一个字符0到1次,等价于{0,1} | OK? ->O或者OK |
{m} | 匹配前一个字符m次 | OK{3} ->OKKK |
{m,n} | 匹配前一个字符m到n次 | OK{1,2} ->OK或者OKK |
{, n} | 匹配前一个字符最多n次 | OK{,2} ->O或OK或OKK |
{n, } | 匹配前一个字符至少n次 | OK{2,} ->OKK或OKKK… |
^ | 匹配字符串开头(若[^]中表示非) | “^[a-zA-Z_]+” |
$ | 匹配字符串末尾 | “[a-zA-Z0-9_]*$” |
( ) | 标记一个子表达式的开始和结束位置 | “(a-z)[a-z0-9_]*$” |
常用函数 | 含义 |
---|---|
re.match(pattern,string,flags=0) | 从起始位置开始匹配,返回匹配的对象 |
re.search(pattern,string,flags=0) | 遍历字符串,并返回第一个成功的匹配 |
re.sub(pattren,repl,string,count=0,flags=0) | 替换字符串中的匹配项 |
re.findall(pattern,string,flags,pos,endpos) | 返回一个列表,包含所有匹配的子串 |
re.split(pattern,string,maxsplit=0,flags=0) | 将字符串按照指定的正则表达式分隔开 |
re.search(pattern,string,flags=0) | 遍历字符串,并返回第一个成功的匹配 |
re.compile(pattern[,flags]) | 是将正则表达式编译成一个对象,加快速度,并重复使用 |
- 参数说明
# pattern:指定需要匹配的正则表达式
# string:要匹配的字符串
# flags:指定匹配模式,常用的值可以是re.I、re.M、re.S和re.X
# repl : 替换的字符串,可以是一个函数
# count:模式匹配后替换的最大次数,默认 0 表示替换所有的匹配
# pos:可选参数,指定字符串的起始位置,默认为 0
# endpos:可选参数,指定字符串的结束位置,默认为字符串的长度
# maxsplit:用于指定最大分割次数,默认为全部分割
# re.I的模式是让正则表达式对大小写不敏感;
# re.M的模式是让正则表达式可以多行匹配;
# re.S的模式指明正则符号.可以匹配任意字符,包括换行符\n;
# re.X模式允许正则表达式写更详细,如多行表示、忽略空白字符、加入注释等
- 查找、替换、分割
import re
line = "Cats are smarter than dogs"
# 几个括号几个组号
matchObj = re.match(r'(.*) are (.*?) (.*)', line, re.M | re.I)
searchObj = re.search(r'(.*) are (.*?) .*', line, re.M | re.I)
if matchObj:
print("matchObj.groups() : ", matchObj.groups()) # 包含所有小组字符串的元组
print("matchObj.group() : ", matchObj.group()) # 匹配的整个表达式的字符串
print("matchObj.group(1) : ", matchObj.group(1)) # 组号
print("matchObj.group(2) : ", matchObj.group(2))
# 字符串中数字 * 2
def double_num(matchedobj):
print(matchedobj)
print(matchedobj.span())
value = int(matchedobj.group('value'))
# print(value)
return str(value * 2)
s = 'A23G4HFD567'
print(re.sub('(?P<value>\d+)', double_num, s))
# ?P<value>,子表达式符合部分取组名
\num 引用分组num匹配到的字符串
(?P<name>) 分组起别名
(?P=name) 引用别名为name分组匹配到的字符串
s = '1102231990xxxxxxxx'
res = re.search('(?P<province>\d{3})(?P<city>\d{3})(?P<born_year>\d{4})', s)
print(res.groupdict())
# {'province': '110', 'city': '223', 'born_year': '1990'}
print(re.split('\W+', 'runoob, runoob, runoob.'))
print(re.split('(\W+)', ' runoob, runoob, runoob.'))
# ()里分割字符串也是一个元素,注意区别
- 贪婪与非贪婪
# 贪婪匹配:在满足匹配时,匹配尽可能长的字符串,默认情况下,采用贪婪匹配
# 非贪婪匹配:在满足匹配时,匹配尽可能短的字符串,使用?来表示非贪婪匹配
pattern1 = "a.*c"
pattern2 = "a.*?c"
print(re.match(pattern1, "abcabc")) # "abcabc"
print(re.match(pattern2, "abcabc")) # "abc"
18. python中断言方法 assert(expressions)
a = 5
assert (a < 3)
print('ok')
assert (a > 8)
print('程序报错, AssertionError')
19. python中可变数据类型和不可变数据类型,并简述原理
- 不可变数据类型:数值型、字符串型string和元组tuple
a = 'abc'
b = 'abc'
print(id(a), id(b))
# 2120368547312 2120368547312
不允许变量的值发生变化,如果改变了变量的值,相当于是新建了一个对象,而对于相同的值的对象,在内存中则只有一个对象(一个地址),用id()方法可以打印对象的id,相当于起别名(指向同一个内存地址)
- 可变数据类型:列表list和字典dict
a = [1, 2, 3]
b = [1, 2, 3]
print(id(a), id(b))
# 2187778937416 2187778937928
允许变量的值发生变化,即如果对变量进行append、+=等这种操作后,只是改变了变量的值,而不会新建一个对象,变量引用的对象的地址也不会变化,不过对于相同的值的不同对象,在内存中则会存在不同的对象,即每个对象都有自己的地址,相当于内存中对于同值的对象保存了多份,这里不存在引用计数,是实实在在的对象
20. 字符串去重并从小到大排序输出
s = "ajldjlajfdljfddd"
new_s = list(set(s))
# new_s = new_s.sort(reverse=False)
new_s.sort(reverse=False)
print(new_s) # ['a', 'd', 'f', 'j', 'l']
res = "".join(new_s)
print(res) # adfjl
-
set去重,去重转成list,利用sort方法排序,reeverse=False是从小到大排
-
list是不 变数据类型,s.sort时候没有返回值,所以注释的代码写法不正确
21. lambda函数使用
l = lambda x, y: x * y
print(l(4, 3))
# 等价于
print((lambda x, y: x * y)(4, 3))
22. sort和sorted方法排序
# sorted
dic = {"name": "zs", "age": 18, "city": "深圳", "tel": "1362626627"}
lis = sorted(dic.items(), key=lambda x: x[0], reverse=False)
print(dict(lis))
# 全局sorted()方法来对可迭代的序列排序生成新的序列
# key参数来指定一个函数,此函数将在每个元素比较前被调用,key=str.lower可忽略大小写
# sort
list0 = [9, 8, 6, 5, 2]
list0.sort(reverse=False)
print(list0)
# 在原list上修改,无返回值
23. 统计出现次数
from collections import Counter
# 字符串和列表同操作
c = "kjalfj;ldsjafl;hdsllfdhg;lahfbl;hl;ahlf;h"
ct = Counter(c)
ct2 = c.count('l')
print(ct)
print(ct2)
# Counter({'l': 9, ';': 6, 'h': 6, 'f': 5, s': 2, 'k': 1,'g': 1, 'b': 1})
# 9
24. 字符串去掉整数、小数和字母操作
- 精准匹配小数:(\d+.\d+)+?
# 每个词中间是空格,用正则过滤掉英文和数字,最终输出"张三 深圳"
import re
s = "not 404 found 张三 99 5.56 深圳"
alist = re.findall('\d+\.?\d*|[a-zA-Z]+', s)
print(alist)
# ['not', '404', 'found', '99', '5.56']
blist = re.split(' ', s)
print(blist)
# ['not', '404', 'found', '张三', '99', '深圳']
print(set(blist) - set(alist))
# 集合可以加减运算,{'深圳', '张三'}
print(" ".join(set(blist) - set(alist)))
# 张三 深圳
25. 格式化输出时间戳和星期
import datetime
print(str(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')) \
+ ' 星期' + str(datetime.datetime.now().isoweekday()))
# 2020-06-15 08:32:09 星期1
26. 简述Django的orm
实现了数据模型与数据库的解耦,通过简单的配置就可以轻松更换数据库,而不需要修改代码只需要面向对象编程,orm操作本质上会根据对接的数据库引擎,翻译成对应的sql语句,所有使用Django开发的项目无需关心程序底层使用的是MySQL、Oracle、sqlite…,如果数据库迁移,只需要更换Django的数据库引擎即可
27. 将[[1,2],[3,4],[5,6]]一行代码展开该列表,得出[1,2,3,4,5,6]
a = [[1, 2], [3, 4], [5, 6]]
print([j for i in a for j in i]) # 列表生成式
print(np.array(a).flatten().tolist()) # np数组
28. 提高python运行效率的方法
-
让关键代码依赖于外部包:你可以为紧急的任务使用C、C++或机器语言编写的外部包,这样可以提高应用程序的性能
-
使用生成器,因为可以节约大量内存
-
多个if elif条件判断,可以把最有可能先发生的条件放到前面写,这样可以减少程序判断的次数,提高效率
-
使用较新的Python版本
-
在排序时使用键(key)
-
使用多进程、多线程、协程
29. 判断类型以及一个类中是否包含某个方法
print(isinstance(5, str)) # False
print(hasattr(LeiMing, "__init__")) # True
print(type(5)) # <class 'int'>
30. Python中单例模式详细解析
-
单例模式(Singleton Pattern)是一种常用的软件设计模式,该模式的主要目的是确保某一个类只有一个实例存在。即每一次执行cls.()返回的对象,内存地址是相同的,可以通过id(实例对象)来查看实例对象对应的内存空间地址
-
应用场景: 音乐播放器、回收站对象、打印机对象、网站的计数器、多线程的线程池、Web应用的配置对象的读取:配置文件是共享资源
-
四种实现方式
1. 装饰器方式
def singleton(cls, *args, **kw):
instances = {}
def _singleton():
if cls not in instances:
instances[cls] = cls(*args, **kw)
return instances[cls]
return _singleton
@singleton
class MyClass4(object):
a = 1
def __init__(self, x=0):
self.x = x
one = MyClass4()
two = MyClass4()
print(id(one), id(two))
# 1949916642376 1949916642376
# 当你实例化Myclass的时候,得到的其实是singleton对象,
# 是一个方法,而不是类,所以无法使用类的本身的属性(x)
2. 使用元类metaclass
class Singleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]
class MyClass(metaclass=Singleton):
def __init__(self, x):
self.x = x
m1 = MyClass("x1")
m2 = MyClass("x2")
print(id(m1), id(m2))
# 2519462231112 2519462231112
# 是一个真正的类,自动覆盖继承,合理使用了metaclass,可以使用(x)
3. 使用基类,基于__new__方法实现(推荐使用,方便)
class Singleton(object):
_instance = None
def __new__(cls, *args, **kwargs):
if not isinstance(cls._instance, cls):
cls._instance = object.__new__(cls)
return cls._instance
class MyClass(Singleton):
def __init__(self, x):
self.x = x
m1 = MyClass("x1")
m2 = MyClass("x2")
print(id(m1), id(m2))
4. 直接使用模块
其实Python的模块就是天然的单例模式,因为模块在第一次导入时,会生成 .pyc 文件,当第二次导入时,就会直接加载.pyc文件,而不会再次执行模块代码。因此,我们只需把相关的函数和数据定义在一个模块中,就可以获得一个单例对象了
class MySingleton(object):
def foo(self):
pass
mySingleton = MySingleton()
# 将上面的代码保存在文件 mysingleton.py 中,然后直接导入使用:
# from mysingleton import mySingleton
# mySingleton.foo()
31. 分别从前端、后端、数据库阐述web项目的性能优化
- 前端优化
- 减少http请求、例如制作精灵图
- html和CSS放在页面上部,javascript放在页面下面,因为js加载比HTML和Css加载慢,所以要优先加载html和css,以防页面显示不全,性能差,也影响用户体验差
- 后端优化
- 缓存存储读写次数高,变化少的数据,比如网站首页的信息、商品的信息等。应用程序读取数据时,一般是先从缓存中读取,如果读取不到或数据已失效,再访问磁盘数据库,并将数据再次写入缓存
- 异步方式,如果有耗时操作,可以采用异步,比如celery
- 代码优化,避免循环和判断次数太多,如果多个if else判断,优先判断最有可能先发生的情况
- 数据库优化
- 如有条件,数据可以存放于redis,读取速度快
- 建立索引、外键等
32. 简述多线程、多进程
- 进程
- 操作系统进行资源分配和调度的基本单位,多个进程之间相互独立
- 稳定性好,如果一个进程崩溃,不影响其他进程,但是进程消耗资源大,开启的进程数量有限制
- 线程
- CPU进行资源分配和调度的基本单位,线程是进程的一部分,是比进程更小的能独立运行的基本单位,一个进程下的多个线程可以共享该进程的所有资源
- 如果IO操作密集,则可以多线程运行效率高,缺点是如果一个线程崩溃,都会造成进程的崩溃
- 应用
- IO密集的用多线程,在用户输入,sleep 时候,可以切换到其他线程执行,减少等待的时间
- CPU密集的用多进程,因为假如IO操作少,用多线程的话,因为线程共享一个全局解释器锁,当前运行的线程会霸占GIL,其他线程没有GIL,就不能充分利用多核CPU的优势
33. 列出几种魔法方法并简要介绍用途
-
__init__:对象初始化方法
-
__new__:创建对象时候执行的方法,单列模式会用到
-
__str__:当使用print输出对象的时候,只要自己定义了__str__(self)方法,那么就会打印从在这个方法中return的数据
-
__del__:删除对象执行的方法
-
__name__:当前模块名称,当前脚本运行时,__name__取值为字符串__main__
34. 列表推导式和生成器,字典推导式
import random
l1 = [i for i in range(10)]
l2 = (i for i in range(10))
dic = {k: random.randint(3, 8) for k in range(3, 8)}
print(l1)
print(dic)
for i in range(10):
print(l2.__next__())
35. pandas常用读取文件类型方法
# 读取CSV
pd.read_csv('file.csv', name=['列名','列名2'])
pd.read_csv('c:/mydata/jit.csv',encoding='gb18030')
# 读取SQL
import pymysql
conn=pymysql.connect(host='', user='', passwd='', db='')
sql='select * from employee'
pd.read_sql(sql, conn)
# 读取限定分隔符文本文件
pd.read_table(filename, header=0)
# 读取Excel
# Excel 导入,指定 sheet 和表头
pd.read_excel('file.xlsx', sheet_name=' 表1', header=0)
# 读取JSON
pd.read_json(json_string)
# 读取URL
# 解析 URL、字符串或者 HTML 文件,抽取其中的 tables 表格
pd.read_html(url)
# 读取剪切板
# 从你的粘贴板获取内容,并传给 read_table()
pd.read_clipboard()
# 读取字典
# 从字典对象导入数据,Key 是列名,Value是数据
pd.DataFrame(dict)
# 读取字符串
from io import StringIO
pd.read_csv(StringIO(web_data.text))
36. 正则匹配邮箱(其他文章中有常用大全)
- ^\w+([-+.]\w+)@\w+([-.]\w+).\w+([-.]\w+)*$
37. python字符串转字典
# eval--不安全
str_info = '{"name": "test", "age": 18}'
dict_info = eval(str_info)
# json模块--不能使用单引号
dict_info = json.loads(str_info)
# ast模块--推荐
import ast
dict_info = ast.literal_eval(str_info)
38. 简述python引用计数机制和垃圾回收机制
-
python采用的是引用计数机制为主,标记-清除和分代收集两种机制为辅的策略
-
Python GC主要使用引用计数(reference counting)来跟踪和回收垃圾。在引用计数的基础上,通过“标记-清除”(mark and sweep)解决容器对象可能产生的循环引用问题,通过“分代回收”(generation collection)以空间换时间的方法提高垃圾回收效率
-
三种情况触发GC
1、调用 gc.collect()
2、GC达到阀值时
3、程序退出时
1. 引用计数——reference count
-
PyObject是每个对象必有的内容,其中ob_refcnt就是做为引用计数。当一个对象有新的引用时,它的ob_refcnt就会增加,当引用它的对象被删除,它的ob_refcnt就会减少.引用计数为0时,该对象生命就结束了
-
可以通过使用内置的模块sys.getrefcount(a)可以查看a对象的引用计数,但是比正常计数大1,因为调用函数的时候传入a,这会让a的引用计数+1
引用计数+1的情况 | 引用计数-1的情况 |
---|---|
对象被创建,例如a=23 | 对象的别名被显式销毁,例如del a |
对象被引用,例如b=a | 对象的别名被赋予新的对象,例如a=24 |
对象被作为参数,传入到一个函数中,例如func(a) | 一个对象离开它的作用域,例如f函数执行完毕时,func函数中的局部变量(全局变量不会) |
对象作为一个元素,存储在容器中,例如list1=[a,a] | 对象所在的容器被销毁,或从容器中删除对象 |
优点 | 缺点 |
---|---|
简单 | 维护引用计数消耗资源 |
实时性 | 循环引用——最致命的缺点 |
# 增加引用示例
import sys
class A:
pass
def func(x):
print(f'对象a:{sys.getrefcount(x)-1}',end=' ')
return x
#a=123.56
a=A() #创建对象a
print(f'对象a:{sys.getrefcount(a)-1}')
b=a #再一次引用对象a
print(f'对象a:{sys.getrefcount(a)-1},对象b:{sys.getrefcount(b)-1}')
c=func(a) #对象a作为函数参数
print(f'对象c:{sys.getrefcount(c)-1}')
d=list() #对象a作为列表元素
d.append(a)
print(f'对象a:{sys.getrefcount(a)-1},对象d:{sys.getrefcount(d)-1}')
# 输出为
# 对象a:1
# 对象a:2,对象b:2
# 对象a:4 对象c:3
# 对象a:4,对象d:1
# 减少引用示例
import sys
class B:
pass
del d[0] #删除列表d中的元素a
print(f'对象a:{sys.getrefcount(a)-1},对象d:{sys.getrefcount(d)-1}')
a=B() # a被重新复制,引用计数为1
print(f'对象a:{sys.getrefcount(a)-1}')
del a #删除了a
# 输出为
# 对象a:3,对象d:1
# 对象a:1
# 一个小误区
a=100
sys.getrefcount(a)-1
返回结果为:50
这是为什么呢?
这是因为python系统维护着一个常见的“整数常量池”即-5-255,在这个区间的数字会有其他的处理方式,这说明100这个数字,目前在系统中有 50 个引用。
包括字符串也有一些特殊的处理,所以在使用应用技术的时候,最好是使用自己自定义的数据类型,这样方便分析,这也是上面为什么要自定义一个类型A的原因
- 引用计数的致命缺陷——循环引用导致的内存泄漏
指由于疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,失去了对该段内存的控制,因而造成了内存的浪费。导致程序运行速度减慢甚至系统崩溃等严重后果。
有 del() 函数的对象间的循环引用是导致内存泄漏的主凶。不使用一个对象时使用:del object 来删除一个对象的引用计数就可以有效防止内存泄漏问题。通过 Python 扩展模块 gc 来查看不能回收的对象的详细信息。可以通过 sys.getrefcount(obj) 来获取对象的引用计数,并根据返回值是否为 0 来判断是否内存泄漏。
- 针对“循环引用”的解决办法
(1)标记清除技术——mark and sweep
(2)分代回收技术——generation collection
(3)手动使用gc模块——Garbage collection
2. 标记-清除机制
-
『 标记清除(Mark—Sweep)』算法是一种基于追踪回收(tracing GC)技术实现的垃圾回收算法。它分为两个阶段:第一阶段是标记阶段,GC会把所有的『活动对象』打上标记,第二阶段是把那些没有标记的对象『非活动对象』进行回收
-
对象之间通过引用(指针)连在一起,构成一个有向图,对象构成这个有向图的节点,而引用关系构成这个有向图的边。从根对象(root object)出发,沿着有向边遍历对象,可达的(reachable)对象标记为活动对象,不可达的对象就是要被清除的非活动对象。根对象就是全局变量、调用栈、寄存器
- 在上图中,我们把小黑圈视为全局变量,也就是把它作为root object,从小黑圈出发,对象1可直达,那么它将被标记,对象2、3可间接到达也会被标记,而4和5不可达,那么1、2、3就是活动对象,4和5是非活动对象会被GC回收
标记清除算法作为Python的辅助垃圾收集技术主要处理的是一些容器对象,比如list、dict、tuple,instance等,因为对于字符串、数值对象是不可能造成循环引用问题。Python使用一个双向链表将这些容器对象组织起来。不过,这种简单粗暴的标记清除算法也有明显的缺点:清除非活动的对象前它必须顺序扫描整个堆内存,哪怕只剩下小部分活动对象也要扫描所有对象
3. 分代技术
-
分代回收的整体思想是:将系统中的所有内存块根据其存活时间划分为不同的集合,每个集合就成为一个“代”,垃圾收集频率随着“代”的存活时间的增大而减小,存活时间通常利用经过几次垃圾回收来度量。
-
分代回收是一种以空间换时间的操作方式,Python将内存根据对象的存活时间划分为不同的集合,每个集合称为一个代,Python将内存分为了3“代”,分别为年轻代(第0代)、中年代(第1代)、老年代(第2代),他们对应的是3个链表,它们的垃圾收集频率与对象的存活时间的增大而减小。新创建的对象都会分配在年轻代,年轻代链表的总数达到上限时,Python垃圾收集机制就会被触发,把那些可以被回收的对象回收掉,而那些不会回收的对象就会被移到中年代去,依此类推,老年代中的对象是存活时间最久的对象,甚至是存活于整个系统的生命周期内。同时,分代回收是建立在标记清除技术基础之上。分代回收同样作为Python的辅助垃圾收集技术处理那些容器对象
-
Python默认定义了三代对象集合,索引数越大,对象存活时间越长
-
逻辑如下
分配内存
-> 发现超过阈值了
-> 触发垃圾回收
-> 将所有可收集对象链表放到一起
-> 遍历, 计算有效引用计数
-> 分成 有效引用计数=0 和 有效引用计数 > 0 两个集合
-> 大于0的, 放入到更老一代
-> =0的, 执行回收
-> 回收遍历容器内的各个元素, 减掉对应元素引用计数(破掉循环引用)
-> 执行-1的逻辑, 若发现对象引用计数=0, 触发内存回收
-> python底层内存管理机制回收内存
- 新创建的对象做为0代
- 每执行一个【标记-删除】,存活的对象代数就+1
- 代数越高的对象(存活越持久的对象),进行【标记-删除】的时间间隔就越长。这个间隔,江湖人称阀值。
- 默认情况下,当0代超过700,或1,2代超过10,垃圾回收机制将触发。
- 0代触发将清理所有三代,1代触发会清理1,2代,2代触发后只会清理自己
4. 垃圾回收与性能调优
- 手动垃圾回收
- 调高垃圾回收阈值
- 避免循环引用(手动解循环引用和使用弱引用)
39. 简述乐观锁和悲观锁
-
悲观锁, 就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。
-
乐观锁,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制,乐观锁适用于多读的应用类型,这样可以提高吞吐量
40. python的复制,深拷贝和浅拷贝的区别
在python中,对象赋值实际上是对象的引用。当创建一个对象,然后把它赋给另一个变量的时候,python并没有拷贝这个对象,而只是拷贝了这个对象的引用
- 直接赋值,默认浅拷贝传递对象的引用而已,原始列表改变,被赋值的b也会做相同的改变
- copy浅拷贝,没有拷贝子对象,所以原始数据改变,子对象会改变
- 深拷贝,包含对象里面的自对象的拷贝,所以原始对象的改变不会造成深拷贝里任何子元素的改变
b=alist
c=copy.copy(alist)
d=copy.deepcopy(alist)
41. Python进阶 __slots__魔法
- 在Python中,每个类都有实例属性。默认情况下Python用一个字典来保存一个对象的实例属性
- 使用__slots__方法来告诉Python不要使用字典,而且只给一个固定集合的属性分配空间,可以节省大量内存
- 在确定了类的属性固定的情况下,可以使用__slots__来优化内存
# 不使用 __slots__
class MyClass(object):
def __init__(self, name, identifier):
self.name = name
self.identifier = identifier
self.set_up()
# ...
# 使用 __slots__
class MyClass(object):
__slots__ = ['name', 'identifier']
def __init__(self, name, identifier):
self.name = name
self.identifier = identifier
self.set_up()
# ...
42. heapq模块(堆排序)
"""
从列表中找出最大的或最小的N个元素
堆结构(大根堆/小根堆)
"""
import heapq
list1 = [34, 25, 12, 99, 87, 63, 58, 78, 88, 92]
list2 = [
{'name': 'IBM', 'shares': 100, 'price': 91.1},
{'name': 'AAPL', 'shares': 50, 'price': 543.22},
{'name': 'FB', 'shares': 200, 'price': 21.09},
{'name': 'HPQ', 'shares': 35, 'price': 31.75},
{'name': 'YHOO', 'shares': 45, 'price': 16.35},
{'name': 'ACME', 'shares': 75, 'price': 115.65}
]
print(heapq.nlargest(3, list1))
print(heapq.nsmallest(3, list1))
print(heapq.nlargest(2, list2, key=lambda x: x['price']))
print(heapq.nlargest(2, list2, key=lambda x: x['shares']))
# [99, 92, 88]
# [12, 25, 34]
# [{'name': 'AAPL', 'shares': 50, 'price': 543.22}, {'name': 'ACME', 'shares': 75, 'price': 115.65}]
# [{'name': 'FB', 'shares': 200, 'price': 21.09}, {'name': 'IBM', 'shares': 100, 'price': 91.1}]
43. itertools模块
"""
迭代工具模块
"""
import itertools
# 产生ABCD的全排列
itertools.permutations('ABCD')
# 产生ABCDE的五选三组合
itertools.combinations('ABCDE', 3)
# 产生ABCD和123的笛卡尔积
itertools.product('ABCD', '123')
# 产生ABC的无限循环序列
itertools.cycle(('A', 'B', 'C'))
44. collections模块常用的工具类
-
namedtuple:命令元组,它是一个类工厂,接受类型的名称和属性列表来创建一个类。
-
deque:双端队列,是列表的替代实现。Python中的列表底层是基于数组来实现的,而deque底层是双向链表,因此当你需要在头尾添加和删除元素是,deque会表现出更好的性能,渐近时间复杂度为 O ( 1 ) O(1) O(1)
-
Counter:dict的子类,键是元素,值是元素的计数,它的most_common()方法可以帮助我们获取出现频率最高的元素
-
OrderedDict:dict的子类,它记录了键值对插入的顺序,看起来既有字典的行为,也有链表的行为
-
defaultdict:类似于字典类型,但是可以通过默认的工厂函数来获得键对应的默认值,相比字典中的setdefault()方法,这种做法更加高效
"""
找出序列中出现次数最多的元素
"""
from collections import Counter
words = [
'look', 'into', 'my', 'eyes', 'look', 'into', 'my', 'eyes',
'the', 'eyes', 'the', 'eyes', 'the', 'eyes', 'not', 'around',
'the', 'eyes', "don't", 'look', 'around', 'the', 'eyes',
'look', 'into', 'my', 'eyes', "you're", 'under'
]
counter = Counter(words)
print(counter.most_common(3))
# [('eyes', 8), ('the', 5), ('look', 4)]