标签:ons inter 包装 -- 协议 第一个 line 获取 rri
最近学习了慕课的python进阶强化训练,将学习的内容记录到这里,同时也增加了很多相关知识。
主要分为以下九个模块:
基本的数据包括:list,tuple(元组),set(集合)和dict(字典)、heapq、queue
from random import randint
data = [randint(-1,10) for _ in range(10)]
# 1. 列表解析
list_data = [x for x in data if x >= 0]
# 2. 字典方式
dict_ = dict(zip(data,[i for i in range(10)]))
print(dict_)
dict_data = {k:v for k,v in dict_.items() if v >= 0}
# 3. filter 方式
filter_data = filter(lambda x: x>=0, data)
# 4. for循环方式
res = []
for x in data:
if x>0:
res.append(x)
# 出现的问题是:
student1 = ('Jim',16,'male','jim123456@qq.com')
student2 = ('Jone',16,'male','jone123456@qq.com')
student3 = ('Siliy',16,'female','S123456@qq.com')
# name
print(student1[0])
# age
print(student1[1])
# sex
print(student1[2])
# 程序中出现这些数字导致程序的可读性很差
# 1. 枚举元素
NAME,AGE,SEX,MAIL = range(4)
# 2. 使用collection.namedtuple
from collections import namedtuple
Student = namedtuple('Student',['name','age','sex','email'])
s = Student('jim',15,'male','fdasfas@qq.com') # 可以直接使用元组放入元素
实际问题:统计词频
解决方案:collection.Counter获取词的出现次数,most_common获取出现次数最多的几个元素
实际问题:找到字典的公共键
解决方案:获取的字典的key是set类型,直接对set进行交集操作即可
实际问题:让字典保持元素输入的顺序
解决方案:使用collection.OrderedDict结构代替原始的dcit结构
实际问题:保证一个容量为n的队列存储的历史记录
解决方案:使用collection.deque,它是一个双端队列,一旦元素个数超过限制就删除头元素
迭代器是访问集合内元素的一种方式,迭代器对象从集合的第一个元素开始访问,直到所有对象都被访问才结束。迭代器只能向前不能后退。
迭代器的基本方法是__next__(self),字符串、列表和元组对象都可以创建迭代器。
生成器是一种简易实现迭代器的方式,同时提供延迟操作,在需要的时候才产生结果,而不是立即产生结果。
创建生成器的有两种方式:
可迭代对象是可以使用for循环遍历的对象。
list = [1,2,3,4]
it = iter(list) # 创建迭代器对象
print(next(it)) # 输出迭代器的下一个元素
# == > 1
print(it.__next__())
# == > 2
print(it.__next__())
# == > 3
print(it.__next__())
# == > 4
# 使用生成器函数
def gensquares(N):
for i in range(N):
yield i ** 2
for item in gensquares(5):
print(item)
# 使用生成器表达式
squares = (x**2 for x in range(5)) #<generator object at 0x00B2EC88>
可以直接使用生成器来简化计算
res = sum(x ** 2 for x in xrange(4)) # 省略直接构造list
#生成器只能遍历一遍
def get_province_population(filename):
with open(filename) as f:
for lien in f:
yield int(line)
gen = get_province_population('data.txt')
all_polulation = sum(gen)
for population in gen:
print(population/all_population)
# 这段代码不会有任何输出,因为sum已经遍历过生成器了,所以再次遍历不会输出任何结果
QA:
为什么要有迭代器对象?
可以任意访问实现遵守迭代器协议的对象,这样访问的时候可以不管访问对象的性质,只要实现了iter和next函数就可以被访问。这种方式充分将内容和方式解耦合。
为什么要有生成器?
使用迭代器和生成器的方式可以省时间和空间,他们是每次返回下一个数据是在被调用到的时候才返回。
使用生成器可以代码量更少,代码更清晰
生成器、迭代器和可迭代对象的区别?
生成器使用yield来创建可迭代对象的一种方式
迭代器是使用Iterator来创建可迭代对象的一种方式
生成器和迭代器的区别是:产生可迭代对象的方式不同,迭代器的方式较为复杂
可迭代对象是可以被for循环遍历的对象,是迭代器和生成器的产物。
A[可迭代对象] -->B[迭代器访问]
A[可迭代对象]-->C[其他访问方式]
B[可迭代对象]-->D[生成器]
D-->B
# 该函数实现一个产生从start开始,每隔step产生一个数字,一直到end结束的数组
class FloatRange:
def __init__(self,start,end,step):
self.start = start
self.end = end
self.step = step
def __iter__(self):
t = self.start
while t <= self.end:
yield t
t += self.step
def __reversed__(self):
t = self.end
while t >= self.start:
yield t
t -= self.step
float_range = FloatRange(0,10,0.5)
# == > <__main__.FloatRange object at 0x7fbe70796470>
print(float_range)
for x in float_range:
print(x)
import requests
from collections import Iterable, Iterator
# 迭代器是针对城市的,所有要有一个城市列表
class WeatherIterator(Iterator):
def __init__(self, cities):
self.cities = cities # 城市列表
self.index = 0 # 访问的位置记录
def get_wather(self, city):
r = requests.get(u'http://wthrcdn.etouch.cn/weather_mini?city=' + city)
data = r.json()['data']['forecast'][0]
return '%s: %s, %s' % (city, data['low'], data['high'])
def __next__(self):
print('迭代器 next')
if self.index == len(self.cities):
raise StopIteration
city = self.cities[self.index]
self.index += 1
return self.get_wather(city)
class WeatherIterable(Iterable):
def __init__(self, cities):
self.cities = cities
def __iter__(self):
print('可迭代对象 iter')
# 可迭代对象遍历的时候调用的迭代器的next函数。
# 返回的是一个迭代器对象
return WeatherIterator(self.cities)
for x in WeatherIterable(['北京', '天津', '上海']):
print(x)
# ==>
# 可迭代对象 iter
# 迭代器 next
# 北京: 低温 -10℃, 高温 -1℃
# 迭代器 next
# 天津: 低温 -5℃, 高温 1℃
# 迭代器 next
# 上海: 低温 3℃, 高温 9℃
# 迭代器 next
x = iter(WeatherIterable(['北京', '天津', '上海']))
print(list(x))
# ==>
# 可迭代对象 iter
# 迭代器 next
# 迭代器 next
# 迭代器 next
# 迭代器 next
# ['北京: 低温 -10℃, 高温 -1℃', '天津: 低温 -5℃, 高温 1℃', '上海: 低温 3℃, 高温 9℃']
class WeatherGenerator():
def __init__(self, cities):
self.cities = cities
def get_wather(self, city):
r = requests.get(u'http://wthrcdn.etouch.cn/weather_mini?city=' + city)
data = r.json()['data']['forecast'][0]
return '%s: %s, %s' % (city, data['low'], data['high'])
def __iter__(self):
for x in self.cities:
yield self.get_wather(x)
重点掌握几个字符串的函数:
split(分割)
startwith(以某个字母开始)
endwith(以某个字符结束)
+连接操作
‘‘.join连接操作
str.ljust(左对齐)
str.rjust(右对齐)
str.center(居中)
str.format(<长度 >长度 =长度 实现zyou)
str.strip(删除两端的空白字符)
str.lstrip(删除左边的空白字符)
str.rstrip(删除右边的空白字符)
切片+拼接(删除单个字符)
str.translate(删除多种不同的字符-使用dict来定义要删除的字符)
重点掌握re的几个函数
re.replace
re.sub
设置文件的缓冲
open(buffering=XXX),XXX>1-- 全缓冲,XXX=1--行缓冲,XXX=0---无缓冲,XXX是缓冲区的大小
文件的路径函数
os.path
文件的状态函数
os.stat,os.fstat
使用临时文件
from timefile import TemporaryFile, NamedTemporaryFile
f = TemporaryFile()
t = NamedTemporaryFile #创建临时文件
文件的编码和解码问题
python2 和 python3 编码问题
ASCII码:一个字节(8位),包括拉丁文和数字
GB2112:两个字节,表示汉字
Unicode:国际统一标准
utf-8:针对Unicode的可变长的字符编码,使用1-4个字节表示符号
python2的字符串类型有两种:str(字节数据)和unicode(unicode数据),这种命名方式更直白,也和C语言是一样的
python3的字符串类型有两种:str(unicode数据)和bytes(字节数据),就是将原来的str数据改成统一标准,容纳更多数据,这种方式更符合使用习惯。
创建类的方式有三种:
- class定义(最经常使用的一种)
- 使用type函数,因为class定义实在运行时动态创建的,而创建class的方法就是使用type函数,type函数可以查看一个对象的类型也可以创建一个新的class类型
- 使用metaclass元类,可以把元类看做的类的模板,类的就是要生成的实例。
元类是类的模板,类是实例的模板
def fn(self,name='world'): #先定义函数
print('hello')
# 第一个参数是class的名字,第二个参数是父类的集合,第三个参数是方法名和函数绑定
Hello = type('hello',(object,),dcit(hello=fn))
创建抽象类的方式有两种:
QA: 这三种方式有什么区别?
使用NotImplementedError的方式时,如果类没有实现抽象方法的话并且调用子类的抽象方法的话,会出错。但是如果没有调用的话,不会出错。
使用abc.abstractmethod方式时,如果没有实现子类方法,并且调用的话不会出现错误。
使用meta元类的方式的话,如果子类没有实现抽象方法的话,实例化的过程就会出现错误。
实际问题:想要自定义新类型的元组,对于传入的可迭代对象过滤小于等于0的元素。
解决方案:
1. 使用metaclass方式实际定义一种新的类的类型
2. 继承tuple类,更改new实例化类的过程
class Person(object):
"""Silly Person"""
def __new__(cls, name, age):
print '__new__ called.'
return super(Person, cls).__new__(cls, name, age)
def __init__(self, name, age):
print '__init__ called.'
self.name = name
self.age = age
def __str__(self):
return '<Person: %s(%s)>' % (self.name, self.age)
if __name__ == '__main__':
piglei = Person('piglei', 24)
print piglei
# == >
# piglei@macbook-pro:blog$ python new_and_init.py
# __new__ called.
# __init__ called.
# <Person: piglei(24)>
整个代码的执行逻辑是:
class IntTuple(tuple):
def __new__(cls, iterable):
g = (x for x in iterable if isinstance(x, int) and x > 0)
return super(IntTuple, cls).__new__(cls, g)
def __init__(self, iterable):
# print self
tuple.__init__(iterable)
# super(IntTuple, self).__init__(iterable) # 不知道这种方式为什么不可以调用父类的init函数
t = IntTuple([1, -1, 'abc', 6, ['x', 'y'], 3])
import sys
class Player:
def __init__(self,id,name,age,status=0,level=1):
self.id = id
self.name = name
self.age = age
self.status =status
self.level = level
class Player2():
__slots__ = ('id','name','age','status')
def __init__(self,id, name, age, status=0, level=1):
self.id = id
self.name = name
self.age = age
self.status = status
# self.level = level
p1 = Player(1,'jim',15) # ==> 56
p2 = Player2(2,'john',11) # ==> 80
print(p2.__slots__)
# print(p2.__dict__) # 没有dict属性
print('p1占用空间的字节大小为:',sys.getsizeof(p1))
# p1占用空间的字节大小为: 56
print('p2占用空间的字节大小为:',sys.getsizeof(p2))
# p2占用空间的字节大小为: 72
print('p1和p2的差集:',set(dir(p1))-set(dir(p2)))
p1和p2的差集: {'__dict__', '__weakref__', 'level'}
这段代码需要注意一个事情是:如果只是显示定义了__slots__,没有给实例动态增加属性的话,p2的占用空间是较大的,尽管p2仍然没有dict属性。但是动态增加属性后,__slots__方法就有优势了。
描述符就是setter和getter方法
class Student(object):
@property
def score(self): #相当于是getter方法
print('getter')
return self._score
@score.setter
def score(self, value):
print('setter')
if not isinstance(value, int):
raise ValueError('score must be an integer!')
if value < 0 or value > 100:
raise ValueError('score must between 0 ~ 100!')
self._score = value
s = Student()
s.score = 100
print(s.score)
# ==>
# setter
# getter
# 100
实际问题:实现类的比较操作
解决方案:重载类的__lt__,__le__函数即可
实际问题:使用描述符对实例属性做类型检查
解决方案:实现__set__,__get__方法,在set方法中做类型检查。实现方式可以property方式
基础知识
闭包是打破函数变量定义空间的一种方式,闭包里面包裹自有变量,自由变量的可见范围和闭包返回函数的范围是一样的。每个对象都包含一个__closure__属性,当函数是闭包的时候,它返回的是一个由cell对象组成的元组对象。cell 对象的cell_contents 属性就是闭包中的自由变量。
为什么要用闭包:避免使用全局变量,可以保存自有变量的值
# 闭包的实例
def adder(x):
def wrapper(y):
return x + y
return wrapper
adder5 = adder(5)
adder6 = adder5(10)
print(adder6)
# ==> 15,因为函数最开始封装了x=5
# 输出 11
adder7 = adder5(6)
print(adder7)
# ==> 11,因为函数最开始封装了x=5
装饰器本质是使用闭包函数,封装了原先的函数,使其原函数在不改变代码的前提下增加额外的功能,装饰器函数返回的是原函数,只不过在原函数之前增加了一些功能。经过装饰器后的函数和没有经过装饰器的函数的用法是一样的。这种编程方式被称为面向切面编程。主要使用场景是:向不同的函数添加大量和函数逻辑没有关联的代码。比如:插入日志、性能检测、事务处理、权限校验。
附一个讲解装饰器很好的网址:https://zhuanlan.zhihu.com/p/27449649
import logging
# ======= 改进方式1.直接在原函数上添加 =================
def foo():
print('foo function')
logging.info('foo is running') # 增加打印log功能
def bar():
print('bar function')
logging.info('bar is running') # 增加打印log功能
# ======= 改进方式2. 重新定义新的函数,包裹要添加的项 =================
def use_logging(func):
print(type(func))
logging.info('%s is running' %func.__name__)
func()
use_logging(foo)
# 这种方式破坏了原有的代码的逻辑结构,使得原先的调用是foo()变成use_logging(foo)
# ======== 改进方式3. 装饰器返回包裹的函数 ==============
def use_logging(func):
# 使用函数闭包,返回包装后的原函数,这样在调用原函数的时候会先调用闭包内的内容
def wrapper(*args,**kwargs):
logging.info('%s is running' %func.__name__)
return func(*args,**kwargs)
return wrapper
foo = use_logging(foo)
foo()
# 这种方式使得函数在进入和退出的时候像是一个横切面,也被称为面向切面编程
# ======== 改进方式4. 装饰器符号方式 ==============
def use_logging(func):
# 使用函数闭包,返回包装后的原函数,这样在调用原函数的时候会先调用闭包内的内容
def wrapper(*args,**kwargs):
logging.info('%s is running' %func.__name__)
return func(*args,**kwargs)
return wrapper
@use_logging
def foo():
print('foo function')
logging.info('foo is running') # 增加打印log功能
# 这种方式使用装饰器符号,省去代码foo=use_logging(foo)
foo()
QA:为什么一定要定义的一层的函数来接受参数?为什么不是直接在原有的函数函数结构上多增加参数?用可变参数的方式?
查看多个文档发现,函数装饰器的写法都是统一的,每个函数装饰器的参数都是唯一的函数,可能是为了维护统一,所以选择多增加一层函数封装。
# ======== 改进方式4. 装饰器符号方式 ==============
# 多增加一层函数接收参数
def use_logging(level):
def decorator(func):
# 使用函数闭包,返回包装后的原函数,这样在调用原函数的时候会先调用闭包内的内容
def wrapper(*args,**kwargs):
logging.info('%s is running' %func.__name__)
return func(*args,**kwargs)
return wrapper
return decorator
@use_logging
def foo():
print('foo function')
logging.info('foo is running') # 增加打印log功能
# 这种方式使用装饰器符号,省去代码foo=use_logging(foo)
foo()
QA:装饰器中使用到了args和kwargs参数,为什么要使用这两个参数
args可以接收元组的参数,kwargs可以接收字典参数,这两种组合在一起可以接受任意的参数,这样就可以不破坏原先函数的参数。
# =========== 方式1. 将原函数的所有需要的属性都付给被包裹的函数 ============================
def log(level="low"):
def deco(func):
def wrapper(*args,**kwargs):
print("log was in...")
if level == "low":
print("detailes was needed")
return func(*args,**kwargs)
wrapper.__name__ = func.__name__
wrapper.__dict__ = func.__dict__
return wrapper
return deco
@log()
def myFunc():
'''I am myFunc...'''
print("myFunc was called")
print(myFunc.__name__)
myFunc()
print(myFunc.__name__)
# 缺点是:需要大量复制的操作
# =========== 方式2. 使用functools的wrapper,update_wrapper ============================
from functools import wraps,update_wrapper
def log(level="low"):
def deco(func):
@wraps(func)
def wrapper(*args,**kwargs):
print("log was in...")
if level == "low":
print("detailes was needed")
return func(*args,**kwargs)
update_wrapper(wrapper, func, ('__name__','__doc__'), ('__dict__',))
return wrapper
return deco
@log()
def myFunc():
print("myFunc was called")
print(myFunc.__name__)
myFunc()
print(myFunc.__name__)
from time import ctime
def deco1(func):
def decorator1(*args, **kwargs):
print('decorator1 print')
print('[%s] %s() is called' % (ctime(), func.__name__))
return func(*args, **kwargs)
return decorator1
def deco2(func):
def decorator2(*args, **kwargs):
print('decorator2 print')
print('[%s] %s() is called' % (ctime(), func.__name__))
return func(*args, **kwargs)
return decorator2
@deco2
@deco1
def foo():
print('Hello, Python')
foo()
# == >
# decorator2 print
# [Wed Dec 12 15:32:19 2018] decorator1() is called
# decorator1 print
# [Wed Dec 12 15:32:19 2018] foo() is called
# Hello, Python
装饰器不仅可以是函数,还可以是类,相比函数装饰器,类装饰器具有灵活度大、高内聚、封装性等优点。使用类装饰器主要依靠类的__call__方法,当使用 @ 形式将装饰器附加到函数上时,就会调用此方法。
class Foo(object):
def __init__(self, func):
self._func = func
def __call__(self):
print ('class decorator runing')
self._func()
print ('class decorator ending')
@Foo
def bar():
print ('bar')
bar()
多任务是由多进程完成的,也可以由一个进程的多线程完成。线程是操作系统的最基本的执行单元。pyton标准库提供了两个模块,tread和threading,thred是低级模块,threding是高级模块,对thred进行了封装,绝大数情况下使用的是threding模块。
以下开始从单线程--多线程 代码改造
from time import ctime, sleep
# ============= 方式1. 基本的单线程执行任务 ==================
def music(names):
for i in range(len(names)):
print('at time: %s, i am listening music %s' % (ctime(), names[i]))
sleep(1)
return
def movie(names):
for i in range(len(names)):
print('at time: %s, i am watching movie %s' % (ctime(), names[i]))
sleep(5)
if __name__ == '__main__':
music(('光年之外', '青花瓷'))
movie(('暗战', '熔炉'))
print('at time %s, all is over' % ctime())
# ============= 方式2. 多线程执行任务 ==================
import threading
from itertools import chain
def music(name):
print('at time: %s, music %s start' % (ctime(), name))
sleep(1)
print('at time: %s, music %s end' % (ctime(), name))
return
def movie(name):
print('at time: %s, movie %s start' % (ctime(), name))
sleep(5)
print('at time: %s, movie %s end' % (ctime(), name))
if __name__ == '__main__':
threads = []
for inx, x in enumerate(chain(('光年之外', '青花瓷') + ('暗战', '熔炉'))):
if inx < 2:
threads.append(threading.Thread(target=music, args=(x,))) #传递参数需要传递元组的形式,否则会把一个元素拆成多个元素
else:
threads.append(threading.Thread(target=movie, args=(x,)))
for t in threads:
t.setDaemon(True) # 设置为守护线程,如果不设置为守护线程会被无限挂起
t.start()
# 这个程序会在主线程执行完结束后直接结束子线程
print('at time %s, all is over' % ctime())
# ==== 方式3. 多线程改进,使主线程等待子线程结束之后再结束 =========
import threading
from itertools import chain
def music(name):
print('at time: %s, music %s start' % (ctime(), name))
sleep(1)
print('at time: %s, music %s end' % (ctime(), name))
return
def movie(name):
print('at time: %s, movie %s start' % (ctime(), name))
sleep(5)
print('at time: %s, movie %s end' % (ctime(), name))
if __name__ == '__main__':
threads = []
for inx, x in enumerate(chain(('光年之外', '青花瓷') + ('暗战', '熔炉'))):
if inx < 2:
threads.append(threading.Thread(target=music, args=(x,))) #传递参数需要传递元组的形式,否则会把一个元素拆成多个元素
else:
threads.append(threading.Thread(target=movie, args=(x,)))
for t in threads:
t.setDaemon(True) # 设置为守护线程,如果不设置为守护线程会被无限挂起
t.start()
t.join() # 使子线程完成之前,这个父线程将会被一直阻塞
# 这个程序会在主线程执行完结束后直接结束子线程
print('at time %s, all is over' % ctime())
附一个讲解进程和线程很好的网址:https://python3-cookbook.readthedocs.io/zh_CN/latest/c12/p03_communicating_between_threads.html
# 下载电影之后才能看电影,而且下载好电影之后需要通知看电影的进程
from time import ctime, sleep
# ============= 方式1. 基本的单线程执行任务 ==================
from queue import Queue
import threading
def download(data_q):
while True:
data = ctime()
print('put in ', data)
data_q.put(data)
def consume(data_q):
while True:
data = data_q.get()
print('consume ', data)
if __name__ == '__main__':
q = Queue()
threads = []
for _ in range(2):
threads.append(threading.Thread(target=download, args=(q,)))
threads.append(threading.Thread(target=consume, args=(q,)))
for t in threads:
t.setDaemon(True)
t.start()
备注:尽管python支持多线程编程,但是解释器的C语言实现部分在完全并行执行的时候只有一个线程。因为解释器被一个全局解释器GIL保护着,它确保任何时候只有一个python线程执行,所有对于所有的线程的来说表面上看是有多个线程同时进行,但是实际上底层部分只有一个线程在运行。所以GIL影响的就是计算密集任务,在pyyhon情况下,如果是针对计算密集的任务,使用多线程并不能加快处理速度,相反可能会导致不同任务之间的CPU切换占据大量的时间。CIL对于IO任务可以加快速度,因为可以在等待IO的过程,CPU操作别的任务。
在Python多线程下,每个线程的执行方式:
1.获取GIL
2.执行代码直到sleep或者是python虚拟机将其挂起。
3.释放GIL
可见,某个线程想要执行,必须先拿到GIL,我们可以把GIL看作是“通行证”,并且在一个python进程中,GIL只有一个。拿不到通行证的线程,就不允许进入CPU执行。
计算密集任务:需要CPU进行大量计算的任务,CPU在过程中一直在使用。
IO密集任务:磁盘IO和网络IO是主要的任务,CPU大部分时间实在等待IO操作结束。
有两种策略可以解决GIL的缺点:
参考网址:https://python3-cookbook.readthedocs.io/zh_CN/latest/c12/p09_dealing_with_gil_stop_worring_about_it.html
多核下,想做并行提升效率,比较通用的方法是使用多进程,能够有效提高执行效率。
原因是:每个进程有各自独立的GIL,互不干扰,这样就可以真正意义上的并行执行,所以在python中,多进程的执行效率优于多线程(仅仅针对多核CPU而言)。
python的最简单的赋值语句:a=1
分析这句话,1作为一个对象,a是1对象的引用,整个赋值语句就是利用赋值语句将引用a指向对象1.
python是动态语言类型,将对象和引用分离。
a = 1
b = 1
print(id(a)) # ==> 33710424
print(id(b)) # ==> 33710424
# 因为python为了优化速度,使用小整数对象池[-1,256], 这些对象都是提前建立好,不会被垃圾回收,所有位于这个区间的整数使用的都是同一个对象。
print(a is b) # ==> True
a = "very good morning"
b = "very good morning"
print(a is b) # ==> False
a = []
b = []
print(a is b) # ==> False
可变对象:int,float,string,tuple,bool
不可变对象:list,dict
结论一:可变对象list是可以改变某个元素的,不可变对象tuple是不可以改变某个元素的
# 可变对象
a = [1, 2, 3]
a[1] = 4
a # == > [1, 4, 3]
# 不可变对象
b = (1, 2, 3)
b[1] = 4
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
结论一:一个变量下,可变对象改变内容后地址是不变的
a = [1, 2, 3]
id(a) # == > 2139167175368
a[1] = 4
id(a) # == > 2139167175368
结论二:一个变量下,可变对象改变内容后地址是不变的
a = [1, 2, 3]
id(a) # == > 2139167175368
a[1] = 4
id(a) # == > 2139167175368
结论三:两个变量下,可变对象改变,另一个变量的内容改变,因为他们指向同一个地址
a = [1, 2, 3]
b = a
a[2] = 4
id(a) # == > 139679751551744
id(b) # == > 139679751551744
b # == > [1, 2, 4]
结论四:两个变量下,不可变对象改变,另一个变量的内容不改变,因为他们会创建新的对象
a=(1,2,3)
b=a
a=(4,5,6)
id(a) # == > 139679784892848 # 不同的地址
id(b) # == > 139679785232816 # 不同的地址
结论五:类的变量和全局变量的地址是共用的,只要一个修改是否会影响另一个取决于对象是可变的还是不可变
class Myclass:
def __init__(self, a):
self.a = a
def printa(self):
print(self.a)
print(id(3))
mclass = Myclass(3) # == >10919392
mclass.printa() # == >3
print(id(mclass.a))# == >10919392
print('-----------------')
print(id(1000)) # == >139712873445136
mclass.a = 1000
mclass.printa() # == >1000
print(id(mclass.a)) # == >139712873445136
print('-----------------')
a = [1,2,3]
print(id(a)) # == >139712842879752
mclass.a = a
mclass.printa() # == >[1, 2, 3]
print(id(mclass.a)) # == >139712842879752
print('-----------------')
mclass.a[1] = 4
mclass.printa() # == >[1, 4, 3]
print(id(mclass.a)) # == >139712842879752
print(a) # == >[1, 4, 3]
print('-----------------')
a[1] = 5
mclass.printa() # == >[1, 5, 3]
print(id(mclass.a)) # == >139712842879752
print(a) # == >[1, 5, 3]
每个对象(可变对象和不可变对象)都有计数,用计数的方式保持和跟踪对象。
python里每一个东西都是对象,它们的核心就是一个结构体:PyObject。PyObject是每个对象必有的内容,其中ob_refcnt就是做为引用计数。当一个对象有新的引用时,它的ob_refcnt就会增加,当引用它的对象被删除,它的ob_refcnt就会减少。当引用计数为0时,该对象生命就结束了。
引用计数机制的优点:
实时性:一旦没有引用,内存就直接释放了。不用像其他机制等到特定时机。实时性还带来一个好处:处理回收内存的时间分摊到了平时。
引用计数机制的缺点:
循环引用导致内存泄露
计数操作:
增加计数引用的情况:
func(a)
list1=[a,a]
减少计数引用的情况:
del a
a=24
sys.getrefcount()
可以查看a对象的引用计数,但是比正常计数大1,因为调用函数的时候传入a,这会让a的引用计数+1。
这里顺便说一下:Python的拷贝
# 一个循环引用的存在的问题
list1 = []
list2 = []
list1.append(list2)
list2.append(list1)
ist1与list2相互引用,如果不存在其他对象对它们的引用,list1与list2的引用计数也仍然为1,所占用的内存永远无法被回收,这将是致命的。
垃圾回收主要解决了循环引用的问题。
另外gc模块是开发人员可以设置垃圾回收的工具。包含了执行垃圾回收、设置自动执行垃圾回收的频率、获取当前垃圾回收对象的计数。能引发循环引用问题的,都是那种容器类对象,比如 list、set、object 等。对于这类对象,虚拟机在为其分配内存时,会额外添加用于追踪的PyGC_Head。这些对象被添加到特殊链表里,以便 GC 进行管理。
# 显示执行gc的垃圾回收机制
deff3():
# print gc.collect()
c1=ClassA()
c2=ClassA()
c1.t=c2
c2.t=c1
del c1
del c2
print gc.garbage
print gc.collect() #显式执行垃圾回收
print gc.garbage
time.sleep(10)
if __name__ == '__main__':
gc.set_debug(gc.DEBUG_LEAK) #设置gc模块的日志
f3()
# 输出
gc: uncollectable <ClassA instance at 0230E918>
gc: uncollectable <ClassA instance at 0230E940>
gc: uncollectable <dict 0230B810>
gc: uncollectable <dict 02301ED0>
object born,id:0x230e918
object born,id:0x230e940
4
有三种情况会触发垃圾回收:
gc.collect()
函数gc.collect()
函数具体阈值的设置:
同 .NET、JAVA 一样,Python GC 同样将要回收的对象分成 3 级代龄。GEN0 管理新近加入的年轻对象,GEN1 则是在上次回收后依然存活的对象,剩下 GEN2 存储的都是生命周期极长的家伙。每级代龄都有一个最大容量阈值,每次 GEN0 对象数量超出阈值时,都将引发垃圾回收操作,垃圾回收后的对象会放在gc.garbage列表里面等待回收。
#define NUM_GENERATIONS 3
/* linked lists of container objects */
static struct gc_generation generations[NUM_GENERATIONS] = {
/* PyGC_Head, threshold, count */
{{{GEN_HEAD(0), GEN_HEAD(0), 0}}, 700, 0},
{{{GEN_HEAD(1), GEN_HEAD(1), 0}}, 10, 0},
{{{GEN_HEAD(2), GEN_HEAD(2), 0}}, 10, 0},
};
gc.get_threshold() # 获取各级代龄阈值
#==> (700, 10, 10) # 所有python设置的阈值是一样的
gc.get_count() # 获取各个代龄的对象数量
#==> (460, 0, 0)
# del 对循环引用没用
import gc, weakref
class User(object):pass
def callback(r):
print (r, "dead")
gc.disable()
a = User(); wa = weakref.ref(a, callback)
b = User(); wb = weakref.ref(b, callback)
a.b = b; b.a = a # 形成循环引用关系。
del a; del b # 删除名字引用。
wa(), wb()
# 对象依然在内存中
# ==> (<__main__.User at 0x7fbc1c72e6d8>, <__main__.User at 0x7fbc1c72e860>)
但是GC无法处理有del的循环引用,上一段代码之后,调用gc.collect()
# del 对循环引用没用
import gc, weakref
class User(object):pass
def callback(r):
print (r, "dead")
gc.disable()
a = User(); wa = weakref.ref(a, callback)
b = User(); wb = weakref.ref(b, callback)
a.b = b; b.a = a # 形成循环引用关系。
del a; del b # 删除名字引用。
wa(), wb()
# 对象依然在内存中
# ==> (<__main__.User at 0x7fbc1c72e6d8>, <__main__.User at 0x7fbc1c72e860>)
gc.collect()
# 但是存在疑问: 我这里输出来是可以回收,但是看别人的博客是不可以回收的。
# ==> 自己的代码
gc: collectable <traceback 0x7fbc1c788188>
gc: collectable <tuple 0x7fbc20dee0b8>
gc: collectable <NameError 0x7fbc1c798ca8>
# ==> 别人的博客
gc: collecting generation 2...
gc: objects in each generation: 520 3190 0
gc: uncollectable <User 0x10fd51fd0> # a
gc: uncollectable <User 0x10fd57050> # b
gc: uncollectable <dict 0x7f990ac88280> # a.__dict__
gc: uncollectable <dict 0x7f990ac88940> # b.__dict__
gc: done, 4 unreachable, 4 uncollectable, 0.0014s elapsed.
4
刚查了博客,我的代码为什么类都可以实现自动回收了,因为我的class没有实现__del__
,gc处理不了的是自定义了__del__
的类对象,遇到这种情况只能显示调用gc.garbage里面的对象的__del__
来打破僵局。
所以在项目中避免出现大量无用对象浪费内存的方法有以下几种:
gc模块唯一处理不了的是循环引用的类都有__del__
方法,所以项目中要避免定义__del__
方法,如果一定要使用该方法,同时导致了循环引用,需要代码显式调用gc.garbage里面的对象的__del__
来打破僵局。
typedef union _gc_head {
struct {
union _gc_head *gc_next;
union _gc_head *gc_prev;
Py_ssize_t gc_refs;
} gc;
long double dummy;
} PyGC_Head;
当然,这并不表示此类对象非得 GC 才能回收。如果不存在循环引用,自然是积极性更高的引用计数机制抢先给处理掉。也就是说,只要不存在循环引用,理论上可以禁用 GC。当执行某些密集运算时,临时关掉 GC 有助于提升性能。
常用的垃圾回收(GC)算法有这几种引用计数(Reference Count)、Mark-Sweep、Copying、分代收集。在Python中使用的是前者引用计数,工作原理:为每个内存对象维护一个引用计数。因 此得知每次内存对象的创建与销毁都必须修改引用计数,从而在大量的对象创建时,需要大量的执行修改引用计数操作(footprint),对于程序执行过程 中,额外的性能开销是令人可怕的。
class User(object):
def __del__(self):
print (hex(id(self)), "will be dead")
gc.disable() # 关掉 GC,如果想要关掉gc只能导入gc管理模块,显示关掉gc
a = User()
del a # 对象正常回收,引用计数不会依赖 GC。
#==>
#0x7fbc1c7b6048 will be dead
Python同时采用了分代(generation)回收的策略。这一策略的基本假设是,存活时间越久的对象,越不可能在后面的程序中变成垃圾。我们的程序往往会产生大量的对象,许多对象很快产生和消失,但也有一些对象长期被使用。出于信任和效率,对于这样一些“长寿”对象,我们相信它们的用处,所以减少在垃圾回收中扫描它们的频率。
Python将所有的对象分为0,1,2三代。所有的新建对象都是0代对象。当某一代对象经历过垃圾回收,依然存活,那么它就被归入下一代对象。垃圾回收启动时,一定会扫描所有的0代对象。如果0代经过一定次数垃圾回收,那么就启动对0代和1代的扫描清理。当1代也经历了一定次数的垃圾回收后,那么会启动对0,1,2,即对所有对象进行扫描。
这两个次数即上面get_threshold()返回的(700, 10, 10)返回的两个10。也就是说,每10次0代垃圾回收,会配合1次1代的垃圾回收;而每10次1代的垃圾回收,才会有1次的2代垃圾回收。
同样可以用set_threshold()来调整,比如对2代对象进行更频繁的扫描。
import gc
gc.set_threshold(700, 10, 5)
整数对象缓冲池包括两个部分:小整数对象缓冲池[-1,256]和大整数对象缓冲池。
小整数对象缓冲池
为了优化速度,提前建立,不会被垃圾回收,所有在这个范围的引用使用的都是一个对象。
大整数对象缓冲池
所有不在小整数对象池的中的整数对象都是大整数对象处,每次新建之前先检查小整数对象池和大整数对象池,如果已经存在了直接返回现在对象的内存地址,如果没有就新建。
特别说明一点:类内自己的新建的对象是在不同的地址空间中的。只有当类内自己的对象是由外部赋值得到的,才会外部共享地址空间。
class C1(object):
a = 100
b = 100
c = 1000
d = 1000
class C2(object):
a = 100
b = 1000
c1 = C1()
c2 = C2()
print(id(c1.c)) # == >139686057838352
print(id(c2.b)) # == >139686027637680
a = 1000
print(id(a)) # == >139686027637712
c1.c = a
c2.b = a
print(id(c1.c)) # == >139686027637712
print(id(c2.b)) # == >139686027637712
python使用intern机制管理字符串,intern机制是在创建一个新的字符串对象时,如果已经有了和它的值相同的字符串对象,那么就直接返回那个对象的引用,而不返回新创建的字符串对象。Python在那里寻找呢?事实上,python维护着一个键值对类型的结构interned,键就是字符串的值。但这个intern机制并非对于所有的字符串对象都适用,简单来说对于那些符合python标识符命名原则的字符串,也就是只包括字母数字下划线的字符串,python会对它们使用intern机制。
事实上,即使Python会对一个字符串进行intern操作,它也会先创建出一个PyUnicodeObject对象,之后再检查是否有值和其相同的对象。如果有的话,就将interned中保存的对象返回,之前新创建出来的,因为引用计数变为零,被回收了。被intern机制处理后的对象分为两类:mortal和immortal,前者会被回收,后者则不会被回收,与Python虚拟机共存亡。
在《Python源码剖析》原书中提到使用+来连接字符串是一个极其低效的操作,因为每次连接都会创建一个新的字符串对象,之后再让这个对象等着被销毁,极大浪费时间和空间,所以推荐使用字符串的join方法来连接字符串。
Python中的list是一个动态数组,它储存在一个连续的内存块中,随机存取的时间复杂度是O(1),但插入和删除时会造成内存块的移动,时间复杂度是O(n)。同时,当数组中内存不够时,会重新申请一块内存空间并进行内存拷贝。
为了创建一个列表,Python只提供了一条途径——PyList_New。这个函数接受一个size参数,从而允许我们指定该列表初始的元素个数。不过我们这里只能指定元素个数,不能指定元素是什么。
Python中的list是一个动态数组。所以,在每一次需要申请内存时,PyListObject就会申请一大块内存,这时申请内存的总大小记录在allocated中,而实际被使用了的内存的数量则记录在ob_size中。
推荐参考网址: https://juejin.im/post/595f0de75188250d781cfd12
标签:ons inter 包装 -- 协议 第一个 line 获取 rri
原文地址:https://www.cnblogs.com/x739400043/p/10113269.html