目标:
整理python知识,主要包含如下内容:
1、器(生成器、迭代器、装饰器等)
2、类(元类,多态,方法等)
3、进程池与线程池
4、协程
5、实现原理
6、算法
7、基础
8、python重要框架原理
1 什么是生成器?生成方式有哪些?原理是什么?
1)生成器是一边生成,一边计算,适用于不能一下子将输入数据存储在列表中进行处理的场景。
2)圆括号或者yield(将原来的return替换为yield)
generator = (x * x for x in range(5))
for value in generator:
print value
def fibonacci(num):
counter = 0
prev = 0
curr = 1
while counter < num:
yield = curr
tmp = prev
prev = curr
curr += tmp
counter += 1
3)生成器遇到yield就返回,下次运行就从yield语句处继续执行
2 什么是装饰器?无参装饰器如何实现?有参装饰器如何实现?如何保持函数名称不随装饰器改变?类装饰器如何实现?请编写一个上下文装饰器。
1) 装饰器用于代码运行期间动态增加功能
本质:返回函数的高阶函数,接收函数本身作为参数,返回一个包含对该函数前后处理的函数
2)无参装饰器样例如下
def timeHelper(func):
def wrapper(*args, **kwargs):
...
func(*args, **kwargs)
...
return wrapper
@timeHelper
def run():
...
等同于
run = timeHelper(run)
3)有参装饰器样例如下:
如果装饰器本身需要传入参数,需要定义三层函数
最外层函数的参数是装饰器本身参数,中间函数的参数是函数对象,
最里面函数的参数是*args,**kwargs
def timeHelperWithParam(text):
def _wrapper(func):
def wrapper(*args, **kwargs):
...
func(*args, **kwargs)
...
return wrapper
return _wrapper
@timeHelperWithParam('function name')
def run():
...
等同于
run = timeHelperWithParam('function name')(run)
解释:
timeHelperWithParam('function name')是一个函数类
4) 保持函数名称不随装饰器改变
只需要添加: @functools.wraps(func)
具体用法如下:
def timeHelperWithParam(text):
@functools.wraps(func)
def _wrapper(func):
def wrapper(*args, **kwargs):
...
func(*args, **kwargs)
...
return wrapper
return _wrapper
5) 类装饰器
A)类装饰器的__init__函数会被执行且只执行一次。
B)无参类装饰器的func从__init__中传入,被装饰的参数从__call__中传入
C)有参类装饰器的参数从__init__中传入,func从__call__中传入,
被装饰的参数从__call__中定义的wrapper方法接收并处理
实现如下:
class StudWithParams:
def __init__(self, name, age):
self.name = name
self.age = age
def __call__(self, func):
print "enter __call__"
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
return result
return wrapper
class StudWithoutParams:
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print "enter __call__"
result = self.func(*args, **kwargs)
return result
6)上下文装饰器
@contextlib.contextmanager
作用: 以该装饰器装饰的函数在函数执行期间返回单个值(yield)
样例:
from contextlib import contextmanager
class Query(object):
def __init__(self, name):
self.name = name
def query(self):
print('Query info about %s...' % self.name)
@contextmanager
def create_query(name):
print('Begin')
q = Query(name)
yield q
print('End')
with create_query('Bob') as q:
q.query()
参考:
https://www.liaoxuefeng.com/wiki/1016959663602400/1115615597164000
3 什么是迭代器?什么是可迭代对象?请编写一个迭代器。
1)迭代器是包含__next__方法的任何对象
2)可迭代对象是任何定义了__iter__方法的对象,__iter__返回迭代器(实现__next__方法)
3) 迭代器样例代码如下
class Fibonacci(object):
def __init__(self):
self.numbers = []
'''
实现了__iter__方法的都是可迭代对象,返回迭代器(实现__next__方法)
'''
def __iter__(self):
return self
def __next__(self):
if len(self.numbers) < 2:
self.numbers.append(1)
else:
self.numbers.append(sum(self.numbers))
self.numbers.pop(0)
return self.numbers[-1]
def send(self, value):
pass
'''
为兼容python2
'''
next = __next__
def useIterator():
fib = Fibonacci()
iter1 = iter(fib)
print next(iter1)
print next(iter1)
4 什么是上下文管理器?with语句的原理是什么?enter方法可以用来做什么?exit方法的作用?如何使用?
1)含义: 包装任意代码块的对象
作用: 确保资源被正确清理,类似try,except;避免重复
用法: with, enter, exit
2)with语句
作用: 可以进入上下文管理器
原理: __enter__返回的结果被赋予给as关键字之后的变量
用法示例:
with open(fileName, 'r') as fr:
data = fr.read()
3)enter方法
特点: with语句的表达式的作用是返回一个遵循特定协议的对象,该对象必须定义
__enter__方法和__exit__方法
__enter__方法:
参数:只接受self参数
返回值: self
作用:执行一些配置
4)exit方法
作用: 处理包装代码块的异常
参数: self, 异常类型,异常示例,回溯
返回值: 可以为None,如果为False,就会向上传播异常;如果为True,就终止异常
样例:
class DBConnection(object):
def __init__(self, dbName=None, user=None,
password=None, host='localhost'):
self.dbName= dbName
self.user = user
self.password = password
self.host = host
def __enter__(self):
self.conn = psycopg2.connect(
dbname=self.dbName,
host=self.host,
user=self.user,
password=self.password
)
return self.conn.cursor()
def __exit(self, excType, excInstance, backTrace):
self.conn.close()
def useDBConn():
with DBConnection(user='luke', dbname='foo') as db:
db.execute('SELECT 1 + 1')
db.fetchall()
1 什么是元类?元类的作用是什么?类的类型是什么?请编写一个元类。
1)元类是创建类的东西,用来创建这些类,是类的类。
2)元类的作用是:
A)修改类,返回修改后的类
B)对象关系映射,将定义的简单类转变成对数据库的操作
C)验证子类。例如需要用类表示多边形,定义特殊验证类作为多边形的基类,把这个验证类当成自己的元类
当创建类时能够自动改变类,希望可以创建符合当前上下文的类。
3)类的类型是type,type可以动态创建类。可以接收类的描述符作为参数,然后返回一个类
4)
class ListMetaClass(type):
# __new__(当前准备创建的类的对象,类的名字,类继承的父类集合,类的方法集合)
def __new__(cls, name, bases, attrs):
attrs['add'] = lambda self, value: self.append(value)
return type.__new__(cls, name, bases, attrs)
class MyList(list):
# 指示使用元类L来定义类的创建
__metaclass__ = ListMetaClass
2 type()是什么?type的作用什么?如何使用type?
1)type()是内建函数,可以动态创建类,接收类名作为参数,返回类。
本质是创建所有类的元类。
2)type可以动态创建类,接收字典为类定义属性
3)用法: type(类名,父类的元组【针对继承的情况,可以为空】,包含属性或方法的字典)
def fn(self, name=''):
return name
Student = type('Student', (object,), {'getName': fn, 'age': 25})
print Student.age
student = Student()
name = student.getName('chen')
3 什么是抽象类?什么是抽象方法?什么是抽象属性?请编写一个抽象类。
1)抽象类是一个特殊的类,只能被继承,不能实例化。
2)在类前面加上:
@six.add_metaclass(abc.ABCMeta)
class People(object)
里面必须至少包含一个抽象方法,即
@abc.abstractmethod
def getAge(self):
""""""
抽象方法在抽象类中不能实现,必须被继承抽象类的子类实现
3)抽象属性形如:
@abc.abstractproperty
def identity(self):
"""Get identitu of people"""
子类如果要实现该抽象属性,需要实现,并且在子类实现时,在该属性前面加上
@property
def identity(self):
4)代码如下
@six.add_metaclass(abc.ABCMeta)
class People(object):
def __init__(self, name, birthDate):
self.name = name
self.birthDate = birthDate
self.age = None
self._identity = None
@abc.abstractmethod
def getAge(self):
"""Compute age of people"""
@abc.abstractproperty
def identity(self):
"""Get identitu of people"""
def getName(self):
return self.name
class Student(People):
def __init__(self, name, birthDate):
super(Student, self).__init__(name, birthDate)
def getAge(self):
now = datetime.now()
result = now - self.birthDate
self.age = result.days / 365
return self.age
@property
def identity(self):
if not self._identity:
self._identity = 'student'
return self._identity
4 什么是多态?python是否有多态?如果有,如何实现多态?
1)多态是指一个接口,多种实现。继承体系的子类能以自己方式实现某个方法
在C++中可以用类似如下形式实现多态:
ParentClass* pointer = new SubClass1();
......
pointer->commonFunction();
期间可以给point赋予不同的子类指针。
子类指针赋值给父类指针类型,则调用该父类指针时,会根据实际
传入的子类指针而调用对应子类的方法,从而实现多态。
2)python有多态。
3)对于python类的多态实际就是
将类的实例作为参数传给某个方法,在该方法中调用类实例的方法,
如果该类有子类,且每个子类各自都实现了该方法,那么根据传入
的类属于不同的子类,就可以实现该同名方法不同的功能。
5 初始化父类的方式有哪些?super的解析顺序是怎样的?为什么使用super继承父类?
1)
方式1: 在子类中调用父类的__init__方法,
样例:
ParentClass.__init__(self, value)
问题:
1) 多重继承会调用超类的__init__
2) 调用顺序不固定:
继承超类的顺序虽然变化,但如果初始化超类的顺序不变,结果不会随继承超类的变化而变化
3) 钻石继承问题:
假设子类有两个单独超类,每个超类继承自同一个公共基类,导致公共基类多次执行
__init__方法,产生意想不到的行为,每次调用最顶端的父类导致之前计算的值失效,
再次恢复为初始值。
方式2: 用super初始化父类
原因: super函数定义了方法解析顺序,解决超类初始化顺序。
原则: 深度优先,从左到右
语法: super(当前类名称, self).__init__(参数列表)
python3中可以用 __class__来代替当前类名称
2)顺序: 深度优先,从左到右
举例:
class BaseClass(object):
def __init__(self, value):
self.value = value
class TimesFiveCorrect(BaseClass):
def __init__(self, value):
super(TimesFiveCorrect, self).__init__(value)
self.value *= 5
class PlusTwoCorrect(BaseClass):
def __init__(self, value):
super(PlusTwoCorrect, self).__init__(value)
self.value += 2
class FourthWay(TimesFiveCorrect, PlusTwoCorrect):
def __init__(self, value):
super(FourthWay, self).__init__(value)
从左到右的继承超类的顺序是:
FourthWay->TimesFiveCorrect->PlusTwoCorrect->BaseClass
7*5=35<------5+2=7<------5<------传入5
那么传入5后,BaseClass先接收到5,接下来下一个是+2变为7.最后乘以5得到35
3)python需要用标准方法解析顺序解决超类初始化和钻石继承问题
应该用内置的super函数来初始化父类。
6 @classmethod的作用是什么?用法是什么?请实现一个样例。
1)classmethod用于初始化类的实例,等同于实现该类
的另一个__init__方法。
2)形式:
@classmethod
def fromData(cls, data):
......
其中第一个参数是cls,表示类的名称,用于后续创建该类的实例
3)代码如下:
class PathInputData(BaseInputData):
def __init__(self, path):
super(PathInputData, self).__init__()
self.path = path
def read(self):
return open(self.path).read()
@classmethod
def fromData(cls, config):
dataDir = config.get('data_dir')
for name in os.listdir(dataDir):
yield cls(os.path.join(dataDir, name))
1 为什么要使用线程池?线程池有哪些组成?简要叙述线程池处理逻辑?线程池适用哪些场景?如何使用线程池?
1)线程池提出原因:同时创建很多线程是需要消耗资源的,可以创建几个线程,其他任务在等待线程池中线程
完成,就可以继续处理。本质:将任务提交到线程池的任务队列中
2)线程池由:主线程,任务队列,子线程集合组成。
3)主要逻辑是:
主线程将任务放入工作队列中,提交任务到任务队列时,判断子线程集合大小如果小于
设置的线程池大小,则实例化一个子线程并加入子线程集合中,每个子线程会不断从任务队列
获取待处理任务,执行该任务的方法来完成任务,完成任务后设置该任务的执行结果,
主线程读取该任务结果时如果任务没有完成,则会阻塞在该任务的多线程条件变量上,
执行完成则可以获取任务结果。
4)线程池适用I/O密集型任务。
5)线程池使用样例代码如下:
import time
from concurrent import futures
def run(num):
time.sleep(num)
return num
def threadPoolExecutor_submit():
with futures.ThreadPoolExecutor(max_workers=1) as executor:
future = executor.submit(run, 1)
result = future.result()
print result
def threadPoolExecutorMap():
data = [1, 2, 3]
with futures.ThreadPoolExecutor(max_workers=1) as executor:
results = list(executor.map(run, data))
return results
2 线程池有哪些参数?线程池的原理是什么?如何确保线程池中线程大小不超过设定值?
1)线程池重要参数如下:
_max_workers: 线程池的大小,如果不设置,默认与cpu个数相同
_work_queue: 线程池队列,这里使用了python默认的Queue,是先进先出队列
_threads: 一个set
_shutdown_lock: 一个线程锁,即threading.Lock()类型
2)线程池原理如下:
s1:获取线程池锁,初始化一个future对象用于存放任务执行结果,
s2:初始化待处理任务条目包含: future对象,待处理任务执行方法和参数,
s3:并该处理任务加入到先进先出队列,然后进行如下处理:
s3.1:
它通过获取空闲型号量大小来判断线程池中是否有空闲线程可以使用,如果有就直接返回;
s3.2:
否则创建一个新的线程,设置线程实际执行的方法为
_worker(executor_reference, work_queue)
在_worker方法中会从先进先出队列中获取待处理任务,运行待处理任务本身的
run方法,该run方法就是我们一开始在使用线程池
sumbit(fun. *args. **kwargs)中的fun(*args, **kwargs),并且执行任务
完成时,会令空闲信号量+1,则在future对象中调用set_result方法设置执行结果。
s4:set_result方法
会获取threading.Condition()条件变量,设置_result的值为真正的执行结果,
s5:
而一旦调用future.result()方法,就会先获取threading.Condition()多线程条件变量,
然后一直阻塞这里,直到任务真正执行完成通过notify()方法来通知多线程条件变量,
此时可以从线程池中获取到最终结果。
3)每次调用线程池的submit方法提交任务到工作队列时,都会调用_adjust_thread_count
方法,查看子线程集合_threads个数和初始化线程池大小max_workers比较,如果
_threads个数小于max_workers,就会实例化一个子线程并加入到子线程集合_threads中。
3 为什么要使用进程池?进程池的组成由哪些?简要叙述进程池的原理?如何使用进程池?
3.1) python的多线程是伪多线程,无法利用多核心cpu,在计算密集型任务中,
需要通过进程池实现并行处理,提高性能。
3.2)主线程,任务队列,管理线程,Call Queue, 子线程集合,Result Queue。
3.3)主线程将任务放入任务队列中,管理线程从任务队列中获取任务并塞入
Call Queue队列,子线程集合从Call Queue队列中获取任务并处理,将处理后的
结果放入Result Queue中。管理线程从Result Queue中获取任务执行结果,
并放入结果对象Future中,主线程从结果对象Future中获取结果。
3.4)
import multiprocessing
import time
from concurrent import futures
def runMap(num):
time.sleep(num)
return num
def processPoolExecutorMap():
datas = [i for i in range(6, 0, -1)]
with futures.ProcessPoolExecutor(max_workers=multiprocessing.cpu_count()) as executor:
results = list(executor.map(runMap, datas))
return results
4 进程池有哪些参数?进程池的原理是什么?
1)进程池的相关参数:
max_workers: 表示进程池的大小。
_call_queue: multiprocessing.Queue对象,这里设定大小为进程池大小+1。EXTRA_QUEUED_CALLS = 1
_result_queue: multiprocessing.Queue对象
_work_ids:是queue.Queue()对象,其中import Queue as queue
_queue_management_thread: 管理线程,与主线程交互,将任务放入Call Queue中。从Result Queue中获取任务执行结果。
_processes:是set()
_shutdown_lock:是线程锁threading.Lock()对象
_queue_count: 等待处理的任务个数,初始为0
_pending_work_items:等待处理的任务字典,键是待处理任务下标,值是待处理任务
2)进程池原理如下:
s1:初始化一个结果对象Future(包含多线程条件变量,结果等),初始化一个任务(
包含Future对象,待执行方法及参数),设置待处理任务字典<待处理任务id,待处理任务>。
将任务id队列中放入当前任务id。
s2:然后开启一个本地队列管理的daemon线程,在该线程中将待处理任务放入到多进程队列
call_queue中,这个管理线程会从result_queue中获取任务执行结果,根据执行任务结果的id,
找到在管理线程中之前定义的<待处理任务id,待处理任务>的字典,从字典中删除该已经执行
完成的任务的键值对,并设置任务的执行结果Future对象。
s3:最后,就是开启指定进程个数个进程,每个进程会从call_queue任务队列中获取任务,
并执行任务,将任务执行结果放入result_queue队列中。
s4:当尝试获取进程池执行任务的结果时,会阻塞在Future结果对象的多线程条件变量上,
直到任务真正执行完成,该多线程条件变量会notify,此时才真正获取了任务执行结果
5 python中多线程安全如何实现?多线程如何通信?请编写样例代码。
1)通过线程锁threading.Lock实现多线程安全。
样例代码如下:
import time
import threading
G_MUTEX = threading.Lock()
G_NUM = 0
def myrun(sleepTime):
time.sleep(sleepTime)
global G_NUM
if G_MUTEX.acquire():
G_NUM += 1
G_MUTEX.release()
def useMutex():
threadList = []
for i in range(3):
the = threading.Thread(target=myrun, args=(0.1,))
the.start()
threadList.append(the)
# 等待所有线程完成
for the in threadList:
the.join()
assert G_NUM == 3
2) 多线程可以通过条件变量,事件,队列进行通信。
2.1) 多线程通过条件变量进行通信。
多线程条件变量,主要用于线程满足条件之后才可以继续执行,
包含:
wait([timeout]):线程挂起,直到收到notify通知或超时。wait()必须在已获得Lock前提下才能调用。
调用wait()会释放Lock,直到该线程被Notify或者超时线程又重新获得锁。
notify(n=1):通知其他线程,挂起的线程收到通知后会开始运行。notify()必须在已获得Lock前提下才能调用。
notify_all() :在线程挂起的时候,发送通知,让所有 wait() 阻塞的线程都继续运行。
样例代码:
import threading
class MyFuture(object):
def __init__(self):
self.condition = threading.Condition()
self._result = None
self._state = "PENDING"
def get_result(self, timeout=None):
with self.condition:
if self._state == "FINISHED":
return self._result
self.condition.wait(timeout)
if self._state == "FINISHED":
return self._result
else:
raise Exception("timeout on threading.Condition()")
def set_result(self, result):
with self.condition:
self._result = result
self._state = "FINISHED"
self.condition.notify_all()
def run(seconds, future):
start = datetime.now()
print start
time.sleep(seconds)
future.set_result("hello world")
end = datetime.now()
print end
def use_threading_condition():
future = MyFuture()
seconds = 3
the = threading.Thread(target=run, args=(seconds, future,))
the.daemon = True
the.start()
result = future.get_result()
print result
2.2) 多线程通过事件进行通信。
在线程中设置Flag,默认值为False,当线程遇到event.wait()
方法,就阻塞,找到Flag值变为True。用于线程之间通信,使一个线程等待其他线程的通知。
event.wait():阻塞线程,直到Flag值变为True
event.set(): 设置Flag值为True
event.clear(): 修改Flag值为False
样例代码:
def func(event):
start = datetime.now()
print start
event.wait()
end = datetime.now()
print end
def use_threading_event():
print "threading event start"
event = threading.Event()
seconds = 3
the = threading.Thread(target=func, args=(event,))
the.start()
time.sleep(seconds)
event.set()
the.join()
print "threading event end"
2.3) 多线程通过队列进行通信。
由于python的Queue.Queue()是线程安全的,所以无需加线程锁。
import Queue
def produce(queue):
for i in range(6):
queue.put(i)
def consume(queue, name):
while True:
data = queue.get()
print "threading name: {name}, data: {data}".format(
data=data,
name=name
)
time.sleep(1)
def use_queue():
queue = Queue.Queue()
producer = threading.Thread(target=produce, args=(queue,))
consumer = threading.Thread(target=consume, args=(queue, "thread_1",))
consumer2 = threading.Thread(target=consume, args=(queue, "thread_2",))
producer.start()
consumer.start()
consumer2.start()
producer.join()
consumer.join()
consumer2.start()
6 python中多进程安全如何实现?多进程如何通信?请编写样例代码。
1)通过进程锁multiprocessing.Lock实现多进程安全。
multiprocessing.Lock():初始化
Lock.acquire():获取锁
Lock.release():释放锁
with lock: 获取锁和释放锁一起
样例代码:
import multiprocessing
def taskWrite(lock, f):
with lock:
with open(f, "w+") as f:
f.write("hello ")
time.sleep(1)
def taskWriteAppend(lock, f):
lock.acquire()
try:
with open(f, "a+") as f:
time.sleep(1)
f.write("world!")
except Exception as ex:
print ex
finally:
lock.release()
def processLock():
lock = multiprocessing.Lock()
fileName = "./file.txt"
p1 = multiprocessing.Process(target=taskWrite, args=(lock, fileName,))
p2 = multiprocessing.Process(target=taskWriteAppend, args=(lock, fileName,))
p1.start()
p2.start()
p1.join()
p2.join()
2)多进程之间通信可用队列,信号量,事件。
2.1)多进程通过队列通信
multiprocessing.Queue.get([block[, timeout]])
block: True:表示阻塞,直到获取到一个元素
样例代码如下:
import multiprocessing
import time
def readQueue(myQueue):
while True:
value = myQueue.get(True)
if value:
print 'get value: {value}'.format(value=value)
def writeQueue(myQueue, datas=None):
for data in datas:
myQueue.put(data)
print "set value: {value}".format(value=data)
def processQueue():
myQueue = multiprocessing.Queue()
reader = multiprocessing.Process(target=readQueue, args=(myQueue,))
writer = multiprocessing.Process(target=writeQueue, args=(myQueue,), kwargs={'datas': range(1,3)})
reader.start()
writer.start()
writer.join()
# NOTE: it needs to put reader.join() after writer.join(), otherwise there is blocking
reader.join()
2.2)多进程通过信号量通信
multiprocessing.Semaphore(n):
n表示资源总数
作用: 控制对共享资源的访问数量
适用: 池的最大连接数
本质: 相当于N把锁,获取其中一把就可以执行
信号量总数N在构造时传入
s = Semaphore(N)
如果信号量为0:进程堵塞,直到信号量大于0
适用:控制对共享资源的访问数量,例如池的最大连接数,访问服务器,文件
信号量基于计数器,每调用一次acquire(),计数器可减去1;
每调用一次release(),计数器加1,当计数器为0,acquire()被阻塞。
样例代码:
import multiprocessing
import time
def smaphoreTask(semaphore, msg):
semaphore.acquire()
print "{name} acquire".format(name=multiprocessing.current_process().name)
time.sleep(msg)
semaphore.release()
print "{name} release".format(name=multiprocessing.current_process().name)
def processSemaphore():
s = multiprocessing.Semaphore(multiprocessing.cpu_count())
processes = []
for x in range(8):
p = multiprocessing.Process(target=smaphoreTask, args=(s,x,))
processes.append(p)
start = time.time()
for p in processes:
p.start()
for p in processes:
p.join()
end = time.time()
print "8 process cost: %s seconds" % (end - start)
2.3)多进程通过事件进行通信
multiprocessing.Event:
作用:实现进程之间同步通信
默认值为false:
set(): 表示这是为True
wait():等待
样例代码:
def first(event):
time.sleep(3)
print "begin set flag"
event.set()
def second(event):
print "wait first process"
event.wait()
print "end wait first process"
def processEvent():
myEvent = multiprocessing.Event()
firstProc = multiprocessing.Process(target=first, args=(myEvent,))
secondProc = multiprocessing.Process(target=second, args=(myEvent,))
secondProc.start()
firstProc.start()
firstProc.join()
secondProc.join()
7 什么是多线程安全?python是否存在多线程安全问题?为什么?dict,list线程是否安全?如何避免python多线程的不安全问题?
1)对多线程共享的数据的操作的行为是确定的
2)存在。
3)在python中虽然有GIL全局解释器锁,GIL锁确保了python多线程中同
一个时间只有一个线程可以运行代码。
如果多线程程序中存在对共享资源的非原子操作,可能导致对这个共享资源修改到一半,
GIL锁被分配给了另一个线程,另一个线程也对共享资源修改,带来脏数据。
4)的确dict, list内置的list[0]=1, dict[1]='a'
这种操作是线程安全,
但是类似 list[0] += list[0] + 1 这种操作不是线程安全的。
因为
list[0] += list[0] + 1
这个等同于
x = list[0] + 1
list[0] = x
这样的话,这个操作不是原子操作,就有可能第一个线程执行到
x = list[0] + 1
的时候,还没有来得及对list[0]进行修改,
第二个线程得到GIL锁,也对list[0]进行修改,导致第一个
线程的执行结果不是基于第二个线程的结果的基础上进行修改的。
5)判断多线程对共享数据操作代码是否是原子操作,如果不是原子操作,则必须加线程锁。
8 为什么说python的多线程是伪多线程?python多线程可以用于什么场景?
1)GIL是全局解释器锁,GIL锁确保了python多线程中同一个时间只有一个线程可以运行代码。
这带来的问题就是,python的多线程实际是一个伪多线程。
2)适用于I/O密集型任务。但是如果有一个是cpu密集型线程,仍然会使得其他线程
性能下降。
参考:
https://blog.csdn.net/hao119119/article/details/47156977
9 请实现一个线程安全的队列。
不需要实现。python的Queue.Queue()队列是线程安全的。
C++中的std::queue才是非线程安全的队列,需要在put()和get()
时加锁。
C++实现线程安全队列参考:
https://blog.csdn.net/u011726005/article/details/82670730
1 什么是协程?协程由哪几部分组成?适用哪些场景?
1)eventlet:协程(绿色线程),一个线程内的伪并发方式。通过互相渡让cpu实现并发。
原理:是执行序列,有独立栈和局部变量,与其他协程共享全局变量。
而同一时间内,只允许一个协程运行,所以无需考虑锁
好处:协程执行顺序由程序决定,若工作耗时,协程会让出CPU,充分利用CPU性能。非阻塞。
本质:协程休息时会保存当前寄存器,重新工作会恢复
2)eventlet主要依赖greenlet,epoll和重写的socket,os等绿化库。
greenlet: 是实现协程Coroutine的基础库。
每个协程都有自己的私有stack及局部变量;
同一时间内只有一个协程在运行,故无须对某些共享变量加锁;
协程之间的执行顺序,完成由程序来控制;
与eventlet的关系: eventlet对greenlet进行了封装成为GreenThread。
epoll: 基于事件的异步I/O库,提高网络编程性能,用于大规模请求的C/S架构中。
epoll即event poll,epoll只会把哪个流发生了怎样的I/O事件通知我们。
绿化库: 由于需要异步I/O,就需要将相关I/O操作完成非阻塞方式。需要重写socket
库等,将它们改写成支持epoll的模式,放在eventlet.green中。
3) 适用处理和网络相关的python库函数,而且可以通过协程来实现并发,用于wsgi,oslo_service等
2 什么是绿化?什么是猴子补丁?
1)”绿化”是指绿化后的Python环境支持绿色线程的运行模式。
Python原生的标准库不支持eventlet这种绿色线程之间互相让渡CPU控制权的执行模型,
为此eventlet开发者改写了部分Python标准库(自称”补丁“)。
2)猴子补丁,在运行时动态修改已有代码,而无需修改原始代码
使用:替换标准库中的组件,例如socket
用法
import eventlet
eventlet.monkey_patch() # 默认不加任何参数的情况下,是所有可能的module都会被patch。
monkey_patch(all=True,os=None, select=None,
socket=None,thread=None,time=None,psycopg=None
3 greenlet原理是什么?如何使用?
1)通过栈的复制切换来实现不同协程之间的切换
2)代码如下:
from greenlet import greenlet
def test1():
print(12)
gr2.switch()
print(34)
def test2():
print(56)
gr1.switch()
print(78)
gr1 = greenlet(test1)
gr2 = greenlet(test2)
gr1.switch()
样例输出:
12
56
34
解释
创建两个协程,创建的过程传入了要执行的函数和父greenle
始终只有一个协程在运行,函数的执行流由程序自己来控制。
参考:
https://www.xuebuyuan.com/3227958.html
https://greenlet.readthedocs.io/en/latest/
4 select原理是什么?select缺点?
1) select模型是unix 系统中的网络模型。unix系统中select模型的参数原型:
int select(int maxfdpl, fd_set * readset, fd_set *writeset, fd_set *exceptset, const struct timeval * tiomeout)
select()的机制中提供fd_set的数据结构,实际上是1个long类型的数组,
每一个数组元素都能与一打开的文件句柄(不管是Socket句柄,还是其他文件或命名管道或设备句柄)建立联系,
建立联系的工作由程序员完成, 当调用select()时,由内核根据IO状态修改fd_set的内容,
由此来通知执行了select()的进程哪些Socket或文件可读或可写。主要用于Socket通信当中。
2) select缺点
单个进程能监视的文件描述符有最大限制1024
select()维护文件描述符的结构,随数量增大,开销增长。
select总结:
select主要用于socket通信当中,能监视我们需要的文件描述变化
监控readset,writeset,excepset的文件描述符列表,内核根据IO状态修改fd_set的内容,
来通知执行select()的进程哪些socket或文件可读或可写。然后用户进程对可读和可写的socket进行处理。
参考:
http://dwise1.net/pgm/sockets/blocking.html
https://www.cnblogs.com/maociping/p/5129858.html
5 poll的特点是什么?
poll没有最大文件描述符数量限制。
包含大量文件描述符的数组被整体复制用于用户态和内核的
地址空间,开销随数量增加而增大。
6 epoll原理是什么?epoll操作有哪些?python中的epoll以及用法?epoll的处理过程是怎样的?
1)epoll采用事件就绪通知方式。
select/poll只有在调用方法后,内核才对监视文件描述符进行扫描。
epoll通过epoll_ctl()注册一个文件描述符,一旦某文件描述符就绪,
内核会采用类似callback回调激活文件描述符,进程调用epoll_wait()得到通知。
2)epoll_create创建一个epoll对象,一般epollfd = epoll_create()
epoll_ctl (epoll_add/epoll_del的合体),往epoll对象中增加/删除某一个流的某一个事件
比如
epoll_ctl(epollfd, EPOLL_CTL_ADD, socket, EPOLLIN);//注册缓冲区非空事件,即有数据流入
epoll_ctl(epollfd, EPOLL_CTL_DEL, socket, EPOLLOUT);//注册缓冲区非满事件,即流可以被写入
epoll_wait(epollfd,...)等待直到注册的事件发生
(注:当对一个非阻塞流的读写发生缓冲区满或缓冲区空,write/read会返回-1,并设置errno=EAGAIN。而epoll只关心缓冲区非满和缓冲区非空事件)。
一个epoll模式的代码大概的样子是:
while true {
active_stream[] = epoll_wait(epollfd)
for i in active_stream[] {
read or write till
}
}
3)python中的epoll以及用法
客户请求-> Select 触发-> Epoll.poll()->[(fineno, event)]->服务端
EPOLLIN ^ |
EPOLLOUT | |
EPOLLHUP | |
---------------------------------
import select
epoll = select.epoll()
epoll.register(待监控的文件句柄,待监控的事件类型)
事件类型:
select.EPOLLIN 可读事件
select.EPOLLOUT 可写事件
select.EPOLLErR 错误事件
select.EPOLLHUP 客户端断开事件
epoll.unregister(待销毁的文件句柄)
epoll.poll(timeout) 当文件句柄变化,会以列表的形式告诉用户进程,timeout为超时时间,默认-1
4)epoll的处理过程
1)创建一个epoll对象并告诉epoll对象在指定socket上监听指定事件
2)询问epoll对象,从上次查询一开,哪些socket发生事件
3)在这些socket上进行处理并告诉epoll对象,修改socket列表和事件并监控
4)重复步骤2)和3),直到完成
5) 销毁epoll对象
7 请编写epoll用法示例。
服务端:
#!/usr/bin/env python
#-*- coding:utf-8 -*-
import socket
import select
import Queue
#创建socket对象
serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
#设置IP地址复用
serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
#ip地址和端口号
server_address = ("127.0.0.1", 8888)
#绑定IP地址
serversocket.bind(server_address)
#监听,并设置最大连接数
serversocket.listen(10)
print "服务器启动成功,监听IP:" , server_address
#服务端设置非阻塞
serversocket.setblocking(False)
#超时时间
timeout = 10
#创建epoll事件对象,后续要监控的事件添加到其中
epoll = select.epoll()
#注册服务器监听fd到等待读事件集合
epoll.register(serversocket.fileno(), select.EPOLLIN)
#保存连接客户端消息的字典,格式为{}
message_queues = {}
#文件句柄到所对应对象的字典,格式为{句柄:对象}
fd_to_socket = {serversocket.fileno():serversocket,}
while True:
print "等待活动连接......"
#轮询注册的事件集合,返回值为[(文件句柄,对应的事件),(...),....]
events = epoll.poll(timeout)
if not events:
print "epoll超时无活动连接,重新轮询......"
continue
print "有" , len(events), "个新事件,开始处理......"
for fd, event in events:
socket = fd_to_socket[fd]
#如果活动socket为当前服务器socket,表示有新连接
if socket == serversocket:
connection, address = serversocket.accept()
print "新连接:" , address
#新连接socket设置为非阻塞
connection.setblocking(False)
#注册新连接fd到待读事件集合
epoll.register(connection.fileno(), select.EPOLLIN)
#把新连接的文件句柄以及对象保存到字典
fd_to_socket[connection.fileno()] = connection
#以新连接的对象为键值,值存储在队列中,保存每个连接的信息
message_queues[connection] = Queue.Queue()
#关闭事件
elif event & select.EPOLLHUP:
print 'client close'
#在epoll中注销客户端的文件句柄
epoll.unregister(fd)
#关闭客户端的文件句柄
fd_to_socket[fd].close()
#在字典中删除与已关闭客户端相关的信息
del fd_to_socket[fd]
#可读事件
elif event & select.EPOLLIN:
#接收数据
data = socket.recv(1024)
if data:
print "收到数据:" , data , "客户端:" , socket.getpeername()
#将数据放入对应客户端的字典
message_queues[socket].put(data)
#修改读取到消息的连接到等待写事件集合(即对应客户端收到消息后,再将其fd修改并加入写事件集合)
epoll.modify(fd, select.EPOLLOUT)
#可写事件
elif event & select.EPOLLOUT:
try:
#从字典中获取对应客户端的信息
msg = message_queues[socket].get_nowait()
except Queue.Empty:
print socket.getpeername() , " queue empty"
#修改文件句柄为读事件
epoll.modify(fd, select.EPOLLIN)
else :
print "发送数据:" , data , "客户端:" , socket.getpeername()
#发送数据
socket.send(msg)
#在epoll中注销服务端文件句柄
epoll.unregister(serversocket.fileno())
#关闭epoll
epoll.close()
#关闭服务器socket
serversocket.close()
客户端:
#!/usr/bin/env python
#-*- coding:utf-8 -*-
import socket
#创建客户端socket对象
clientsocket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#服务端IP地址和端口号元组
server_address = ('127.0.0.1',8888)
#客户端连接指定的IP地址和端口号
clientsocket.connect(server_address)
while True:
#输入数据
data = raw_input('please input:')
#客户端发送数据
clientsocket.sendall(data)
#客户端接收数据
server_data = clientsocket.recv(1024)
print '客户端收到的数据:'server_data
#关闭客户端socket
clientsocket.close()
参考:
https://www.cnblogs.com/maociping/p/5132583.html
8 python中的IO多路复用的原理是什么?
IO多路复用又称为事件驱动IO。
IO操作:网络操作,文件操作,终端操作等。
IO多路复用的原理:
select/epoll会不断轮询所负责的所有socket,当某个
socket有数据到达,就通知用户进程。用户进程就可以对
就绪的socket进行读写操作。
9 协程如何使用?
1)用法1
import eventlet
eventlet.monkey_patch(all=True)
from eventlet.green import urllib2
def getUrl(url):
stream = urllib2.urlopen(url)
result = stream.read()
return result
def validGreenPool(urls):
pool = eventlet.GreenPool()
results = []
for result in pool.imap(getUrl, urls):
results.append(result)
return results
2)用法2
import time
import eventlet
eventlet.monkey_patch()
def func1():
time.sleep(3)
info = "func1 time: {curTime}".format(
curTime=datetime.now()
)
print info
def func2():
time.sleep(3)
info = "func2 time: {curTime}".format(
curTime=datetime.now()
)
print info
def useEventlet():
pool = eventlet.GreenPool()
pool.spawn(func1)
pool.spawn(func2)
pool.waitall()
10 协程的实现原理是什么?
1)
def spawn(func, *args, **kwargs)
hub = hubs.get_hub()
g = GreenThread(hub.greenlet)
hub.schedule_call_global(0,g.switch,func,args,kwargs)
return g
分析:
hub的第一个作用就是greenthread的调度器。另外一个作用于网络相关,所以hub有多个实现,对应于epoll,select,poll,pyevent等
hub在eventlet中是一个单太实例,也也就是全局就这有这一个实例,其包含一个greenlet实例,该greenlet实例是
self.greenlet = greenlet(self.run),这个实例就是官方文档说的MAINLOOP,主循环,更加具体就是其中的run方法,是一个主循环。
并且该hub还有两个重要的列表变量,self.timers 和 self.next_timers,前者是一个列表,
但是在这个列表上实现了一个最小堆,用来存储将被调度运行的greenthread,后者,用来存储新加入的greenthread。
第二,创建一个GreenThread的实例,greenthread继承于greenlet,简单封装了下,该类的构造函数只需要一个参数,
父greenlet,然后再自己的构造函数中,调用父类greenlet的构造函数,传递两个参数,GreenTread的main函数和一个greenlet的实例。
第二代码就知道,hubs中作为MAINLOOP的greenlet是所有先创建的greenthread的父greenlet。由前面介绍greenlet的例子中,
我们可以知道,当调用该greenthread的switch方法时,将会开始执行该才传递给父类的self.main函数。
第三,然后单态的hub调用schedule_call_global函数,该函数的作用可以看其注释,用来调度函数去执行。
2)
"""
Schedule a callable to be called after 'seconds' seconds have
elapsed. The timer will NOT be canceled if the current greenlet has
exited before the timer fires.
seconds: The number of seconds to wait.
cb: The callable to call after the given time.
*args: Arguments to pass to the callable when called.
**kw: Keyword arguments to pass to the callable when called.
"""
t = timer.Timer(seconds, cb, *args, **kw)
self.add_timer(t)
return t
分析:
timer是指,传递进来的参数会构造成Timer的实例最后添加到self.next_timer列表中。
注意在spawn中传递进来的g.switch函数,如果调用了这个g.switch函数,则触发了它所在
的greenthread的运行。
这三步结束之后,对spawn的调用就返回了,然而现在只是创建了一个GreenThread,
还没有调度它去执行,最后还需要再返回的结果上调用g.wait()方法
3)GreenThread的wait方法
def __init__(self, parent):
greenlet.greenlet.__init__(self, self.main, parent)
self._exit_event = event.Event()
self._resolving_links = False
def wait(self):
""" Returns the result of the main function of this GreenThread. If the
result is a normal return value, :meth:`wait` returns it. If it raised
an exception, :meth:`wait` will raise the same exception (though the
stack trace will unavoidably contain some frames from within the
greenthread module)."""
return self._exit_event.wait()
分析:
wait方法调用了Event实例的wait方法,就是在这个wait函数中,调用了我们前面提到的单态实例hub的switch方法,
然后该switch真正的去调用hub的self.greenlet.switch(),我们已经所过该greenlet是所有调用spwan创建的greenlet的
父greenlet,该self.greenlet在初始时传递了一个self.run方法,就是所谓的MAINLOOP。
最终,程序的运行会由于switch的调用,开始run方法中的while循环了,
这是多线程开发者最熟悉的while循环了。
在该while循环中,就对self.next_timers中的timers做处理:
4)
def prepare_timers(self):
heappush = heapq.heappush
t = self.timers
for item in self.next_timers:
if item[1].called:
self.timers_canceled -= 1
else:
heappush(t, item)
del self.next_timers[:]
分析:
首先处理next_timers中没有被调用的timers,push到最小堆中去,也就是时间最小者排前面,
越先被执行。然后将所有已经调用了的timer删除掉,这是不是会有一个疑问:如果删除了的timers
没有运行结束,那么下次岂不是没有机会再被调度来运行了。再看了greenthread.py中的sleep函数
之后,就会明白。
加入到heap中的timers这会按照顺序开始依次遍历,如果到了他们的执行时间点了,timer对象就会
直接被调用。看下面的代码:
5)
def fire_timers(self, when):
t = self.timers
heappop = heapq.heappop
while t:
next = t[0]
exp = next[0]
timer = next[1]
if when < exp:
break
heappop(t)
try:
if timer.called:
self.timers_canceled -= 1
else:
timer()
except self.SYSTEM_EXCEPTIONS:
raise
except:
self.squelch_timer_exception(timer, sys.exc_info())
clear_sys_exc_info()
分析:
Timer对象重载了__call__方法,所以可以直接调用了,timer被调用之后,我们前面知道,
传递进来的是g.switch,在timer中就是调用了该switch函数,
直接触动了greenthread的执行,此时,我们自定义的函数就可以被执行了。
我们知道,如果我们自定义的函数要运行时间很长,怎么办,其他的greenthread则没有机会去运行了,在openstack
nova官方文档中介绍thread中也提到这个问题,此时我们需要在自己定义的函数中调用greenthread.sleep(0)函数,
来进行切换,使其他的greenthread也能被调度运行。看看greenthread.sleep函数的代码。
6)
def sleep(seconds=0):
"""Yield control to another eligible coroutine until at least *seconds* have
elapsed.
*seconds* may be specified as an integer, or a float if fractional seconds
are desired. Calling :func:`~greenthread.sleep` with *seconds* of 0 is the
canonical way of expressing a cooperative yield. For example, if one is
looping over a large list performing an expensive calculation without
calling any socket methods, it's a good idea to call ``sleep(0)``
occasionally; otherwise nothing else will run.
"""
hub = hubs.get_hub()
current = getcurrent() # 当前正在执行的greenthread,调用这个sleep函数
assert hub.greenlet is not current, 'do not call blocking functions from the mainloop'
timer = hub.schedule_call_global(seconds, current.switch)
try:
hub.switch()
finally:
timer.cancel()
分析:
从该sleep函数可以知道,我们又重新调用了一遍hub.schedule_call_global函数,然后直接调用hub.switch,这样在运行的子greenlet中,
开始触发父greenlet(也就是MAINLOOP的greenlet)的执行,上次该greenlet正运行到 fire_timers 的timer()函数处,
此时父greenlet则接着运行,开始新的调度。至此,调度的过程就大致描述结束了。
greenthread中其他的函数都基本同样,如果我们的函数只是简单的进行CPU运行,而不涉及到IO处理,
上面的知识就可以理解eventlet了,然而,eventlet是一个高性能的网络库,还有很大一部分是很网络相关的。
7)
Timer
对于任何传入到hub的函数,首先就会封装成Timer,代表了该函数将会在多久之后被执行
。实际上,我们知道了,hub调度的是一个个Timer,不管这个Timer中存储的是什么函数,
普通的函数还是greenlet的switch函数,都是一样的被处理。对于普通函数,我们可以让等待一定时间运行,
我们关注的函数hub如何来调度greenthread。这才是重点。
下面是event的例子:
from eventlet import event
from eventlet.support import greenlets as greenlet
from eventlet import hubs
import eventlet
evt = event.Event()
def waiter():
print "about to wait"
result = evt.wait()
print 'waited for,',result
hub = hubs.get_hub()
g = eventlet.spawn(waiter)
eventlet.sleep(0)
evt.send('a')
eventlet.sleep(0)
调用spawn会创建一个greenthread放入到hub中,然后使用sleep(0)从当前的greenlet切换到刚才创建的greenthread,
就开始执行waiter函数,打印第一行。然后函数就在此wait了,我们前面介绍了wait会触发hub的switch方法,
回到MAINLOOP的循环中,由于在每一次循环都将next_timer清空了,所有要执行的timer都添加到self.timer这个小堆中去了。
在MAINLOOP中,由于这个包含timer的wait已经被执行过一次,所以下次循环时不会再执行了,sleep函数就让程序切换到了我们写的代码上来,
接着运行evt.send(’a’),这一行同样触发了hub的调度,接着运行到waiter阻塞的地方,我们发现,这儿send有一个很关键的作用,
用来在不同的greenthread中传递结果。所以后面紧接着打印了waited
for,a。最后一句sleep则从MAINLOOP的空循环中切回到我们的代码尾,然后结束。
通过event,就明白了event可以用来再不同的greenthread中进行值的传递。
官方文档介绍了,event和队列类似,只是event中只有一个元素,
send函数能够用来唤醒正在等待的waiters,是不是和线程中的诸多概念相似了。
8)
总结
我们回过头来看整个hub作为调度模块的结构,hub调度对象是Timer实例,只是有的timer实例封装了greenthread的switch函数,
从而切换到greenthread的执行。不同的greenthread中进行通信,这需要用来event,每个greenthread封装一个event实例,
event完成对本身greenthread的结果传递。而我们普通使用的spawn系列函数则是整个调度系统提供对外的api,使用该api,
则可以将我们的任务作为一个greenthread添加到hub中,让它调度。至此,可以大致看到eventlet的调度框架。
并且后面将提到的greenpool则是一个greenthread的池,使用也差不多了。
参考:
https://www.xuebuyuan.com/3227958.html
1 list的实现原理是什么?
list在cpython中表现为一个结构体,包含ob_size和allocated成员变量。
其中ob_size表示list中实际数组元素多少,allocated表示数组已经分配的内存。
list作为可变数组,其实现可变数组的核心在于,每次添加新的元素时,会检查内存是否可以
容纳新添加的元素,如果不能,则会按照如下方式分配新的内存:
new_allocated = size + (size >> 3) + (size < 9 ? 3 : 6)
然后将新元素添加到指定数组指定位置。
这样做的原因是:
最大可申请大小是:
PY_SSIZE_T_MAX * (9 / 8) + 6
这样申请,确保不会溢出。
可以看到,这个和C++中类似每次翻倍申请内存的方式并不一样(印象中是,不确定,欢迎纠正)。
但是原理是相通的。
需要指出,因为是可变数组,所以都是不断申请新的内存,那么这个操作就是在堆上进行,
而不是在栈上。
2 dict的实现原理是什么?dict获取,修改,查询,删除,迭代的的时间复杂度是多少?
1)python字典本质上是哈希表,与C++中的map(本质是红黑树)还不一样。
通过开放定址法使用二次探测来解决哈希冲突。
字典有Unused,Active,Dummy三种状态。删除的键值对实际是通过
设置key为dummy来做伪删除。
查找key对应的value时,通过 i = (size_t)hash & mask; 来进行 进行定位探测冲突链。
dk_get_index 方法来搜索key对应的值,然后将查询的key和与字典中的key比较成功则返回数据
再比较两个key 之间的hash 是否相同如果相同使用PyObject_RichCompareBool方法比较,成功则返回数据。
如果key经过前面的比较都不相同,则在探测链上继续往下寻找。
2)
copy 复制: O(n)
get(value) 获取:O(1)
set(value) 修改:O(1)
delete(value) 删除:O(1)
search(in) 字典搜索:O(1)
iteration 字典迭代:O(n)
参考:
https://www.jianshu.com/p/db75baf61cdd
3 queue的实现原理是什么?
1)python的Queue.Queue()队列时先进先出,采用线程锁,是线程安全的。
2)先进先出队列底层是collections.deque()双端队列实现。
3)先进先出队列put的原理是:
3.1)若block为False,则忽略timeout参数,队列满则抛异常,否则保存元素;
3.2)若block为True:
3.2.1)若timeout为None,put操作阻塞直到队列中有空闲空间
3.2.2)若timeout为非负数,则阻塞相应时间直到队列中有剩余空间。若一直没有剩余空间,则抛异常。
4)先进先出队列get原理是:
4.1)若block为False,则忽略timeout参数,队列没有元素则抛异常,否则返回元素
4.2)若block为True:
4.2.1)若timeout为None,则阻塞直到队列中有元素
4.2.2)若timeout为非负数,则会阻塞相应时间直到队列中有元素。
4 深拷贝与浅拷贝的区别是什么?
深浅拷贝都是创建新的对象。但深拷贝包含了嵌套拷贝,拷贝后的对象与原对象无关。
浅拷贝中的每个元素和原对象每个元素相同,如果对元素操作,会使得浅拷贝对应元素发生变化。
5 深拷贝的实现原理是什么?
深拷贝:
1)先判断待拷贝对象x的类型,根据不同类型调用对应的拷贝方法进行拷贝,
例如:对数组,则会遍历每个数组元素,再次递归调用deepcopy方法进行拷贝。
得到拷贝后的结果y后,设置字典memo[id(x)] = y
然后确保在memo中持有对对象x的引用。
2)其中memo是一个备忘录,保存已经拷贝的对象,防止循环拷贝。
所以memo是以待拷贝元素的id为key。
6 浅拷贝的实现原理是什么?
浅拷贝根据待拷贝对象的类型,对不可改变对象x直接返回x自己,
对于可变对象x,则用type(x)(x)重新创建一个新的对象。
7 python为什么是解释语言?.py文件运行的流程是怎样的?PyCodeObject对象包含哪些信息?
字节码,PyCodeObject,.pyc是什么关系?
1) python虚拟机将一条条py语句解释运行。
2) 流程如下:
python把py文件编译为字节码,交给字节码虚拟机->
虚拟机从编译的PyCodeObject对象中一条一条执行字节码指令
3)PyCodeObject对象包含: 字节码指令+程序静态信息
不包含: 程序运行动态信息,即执行环境PyFlameObject
4)字节码在python虚拟机中对应的是PyCodeObject对象;
.pyc文件是字节码在磁盘上的表现形式。
8 python中是如何模拟进程与线程的?
1)模拟进程用: PyInterpreterState
模拟线程用: PyThreadState。每个线程对应帧栈。
python虚拟机在多线程上切换。
当Python虚拟机开始执行时,它会先进行一些初始化操作,最后进入PyEval_EvalFramEx函数,
它的作用是不断读取编译好的字节码并执行。函数内部主要是一个switch结构,根据字节码的不同执行不同的代码。
9 何时生成.pyc文件?如何自己手动生成pyc文件?py与pyc谁优先被使用?
1)PyCodeObject对象是在模块加载即import的时候创建的
A 执行 python test.py 会对test.py进行编译成字节码并解释执行,但不会生成test.pyc
B 如果test.py中加载了其他模块,如import urllib2,那么python会对urllib2.py进行编译成字节码,生成urllib2.pyc,然后对字节码解释执行。
2) 如果想生成test.py可用python -m test.py
3) 优先使用pyc,若pyc编译时间早于py,则重新编译py文件生成新的pyc
10 PyCodeObject对象是怎样的? PyFlameCode对象是怎样的?
1) PyCodeObject对象如下:
typedef struct {
PyObject_HEAD
int co_argcount; /* 位置参数个数 */
int co_nlocals; /* 局部变量个数 */
int co_stacksize; /* 栈大小 */
int co_flags;
PyObject *co_code; /* 字节码指令序列 */
PyObject *co_consts; /* 所有常量集合 */
PyObject *co_names; /* 所有符号名称集合 */
PyObject *co_varnames; /* 局部变量名称集合 */
PyObject *co_freevars; /* 闭包用的变量名集合 */
PyObject *co_cellvars; /* 内部嵌套函数引用的变量名集合 */
/* The rest doesn’t count for hash/cmp */
PyObject *co_filename; /* 代码所在文件名 */
PyObject *co_name; /* 模块名|函数名|类名 */
int co_firstlineno; /* 代码块在文件中的起始行号 */
PyObject *co_lnotab; /* 字节码指令和行号的对应关系 */
void *co_zombieframe; /* for optimization only (see frameobject.c) */
} PyCodeObject;
2) PyFlameCode对象创建程序运行时的动态信息,即执行环境,
代码大致如下:
typedef struct _frame{
PyObject_VAR_HEAD //"运行时栈"的大小是不确定的
struct _frame *f_back; //执行环境链上的前一个frame,很多个PyFrameObject连接起来形成执行环境链表
PyCodeObject *f_code; //PyCodeObject 对象,这个frame就是这个PyCodeObject对象的上下文环境
PyObject *f_builtins; //builtin名字空间
PyObject *f_globals; //global名字空间
PyObject *f_locals; //local名字空间
PyObject **f_valuestack; //"运行时栈"的栈底位置
PyObject **f_stacktop; //"运行时栈"的栈顶位置
//...
int f_lasti; //上一条字节码指令在f_code中的偏移位置
int f_lineno; //当前字节码对应的源代码行
//...
//动态内存,维护(局部变量+cell对象集合+free对象集合+运行时栈)所需要的空间
PyObject *f_localsplus[1];
} PyFrameObject;
每一个 PyFrameObject对象都维护了一个 PyCodeObject对象,这表明每一个 PyFrameObject中的动态内存空间对象都和源代码中的一段Code相对应。
11 python虚拟原理是什么?栈帧结构是怎么样的
1)python虚拟机模拟可执行程序在x86机器上的运行。
2)栈帧结构如下:
栈底
[] 较早的帧
[] 调用者的帧
[] 当前帧
[]
[] 栈指针
栈顶
参考:
https://blog.csdn.net/qq_41312839/article/details/79470823
12 Python中内存管理是怎样的?
python引入内存池,将不用内存放入内存池,
Pymalloc用于管理小块内存申请和释放(<256k)
malloc和free管理大于256k的内存。
参考:
https://blog.csdn.net/sinboo833/article/details/39179055
https://blog.csdn.net/weixin_33919950/article/details/88014835
13 python垃圾回收机制是如何实现的?垃圾回收的局限是什么?有哪些内存分析工具?
1)python垃圾回收机制采取的是:
引用计数为主,标记-清除+分代回收为辅的回收策略
引用计数是因为变量是对内存数据区域引用。
A.引用计数的原理是:
创建,复制,计数+1
引用销毁,计数-1
减为0,就释放该内存空间。
作为参数传递给函数时,先复制引用,所以计数+1
del语句消除引用关系,没有_self自我引用,则内存释放
B.标记-清除回收机制的原理是:
解决循环引用问题
解决思路: 内存分为两部分,一部分保存真实的引用计数,
另一部分作为引用计数的副本,在副本上进行引用计数的试验
C.分代回收
分代回收作用是提升垃圾回收效率
垃圾检测->垃圾回收
同一代对象:垃圾回收频率相近的对象
判断对象的代:根据生存时间。
代码持续访问的活跃对象,会从零代链表转移到一代,再转移
到二代,python处理零代最为频繁,其次一代
2) 局限
A 对于循环引用无效,需要垃圾回收模块gc
B 重载类的__del__方法,指出了删除对象释放内存空间以外的操作
del语句先查看待删除对象引用计数如果为0,就释放内存并执行__del__方法。
collect方法默认不对重载了__del__方法的循环引用对象回收
3)
A 查看对象的引用
sys.getrefcount(xxx):
作用:查看对象xxx有几个引用
gc.collect([generation])
作用:如果没有参数,进行一次全面的回收
gc.garbage
是一个列表,每个元素是才能在的垃圾对象
B pyrasite
作用: 连接运行的python程序,类似交互终端
gdb安装检测:
直接输入gdb
如果没有按转gdb,执行如下命令安装
yum install gdb -y
pip install pyrasite
pyrasite-memory-viewer pid
作用:查看该进程中各种类型对象数量和内存占用情况
pyrasite-shell pid
作用: 进入到进程的shell
14 python如何实现跨平台?python为什么叫解释型语言和动态语言?
1) 因为python的虚拟机机制,提供和宿主平台无关的编译环境。
2) python被称为解释型语言是因为python虚拟机将py文件编译为字节码
一条一条执行,所以叫做解释型语言。
之所以被称为动态语言,是因为python虚拟机一边将py文件翻译为字节码并
一边执行。
15 type, object, class的关系是什么?元类是什么?type用法?
1) type()是内建函数,可以动态创建类,接收类名作为参数,返回类。
本质是创建所有类的元类。
type可以动态创建类,接收字典为类定义属性
用法: type(类名,父类的元组【针对继承的情况,可以为空】,包含属性或方法的字典)
类的类型是type
object是最顶层基类,object是type的实例。
2) 元类用于创建类,是类的类。
元类可以通过继承type实现。
class ListMetaClass(type):
# __new__(当前准备创建的类的对象,类的名字,类继承的父类集合,类的方法集合)
def __new__(cls, name, bases, attrs):
attrs['add'] = lambda self, value: self.append(value)
return type.__new__(cls, name, bases, attrs)
3)
a = 1
print(type(a)) #
print(type(int)) #
16 对象的3个特征是什么?None代表什么?
1)身份(对象在内存的地址,用id()查看)
类型(int, bool等)
值
2) None表示为空,全局唯一,在内存地址指向同一个。
17 为什么python中一切皆对象?
图如下:
-实例-
| |
| V
|---type<----实例----object
^ -----继承----> ^
| |
实例 继承
------------------int
解释:
1)object是最顶层基类
2)type是本身的实例,object,int等都是type的对象,
总结:
object是type的对象,object又是最顶层基类,则type继承自object
形成循环,则python中一切皆为对象。
参考:
https://yq.aliyun.com/articles/647974
https://www.cnblogs.com/hpu0623/p/10965285.html
18 什么是反射?python中有反射吗?
如何实现python的反射?反射的使用场景有哪些?
反射的本质是什么?请编写具体反射的样例。
1)反射是将字符串映射到实例的变量/方法并调用的操作。
2)python中有反射。
3)python中通过
getattr:获取指定字符串名称的对象属性
setattr:为对象设置属性
hasattr: 判断对象是否有某个属性
delattr:删除指定属性
4)反射常用于动态加载模块。
样例:
class TestObj(object):
def __init__(self, name, age):
self.name = name
self.age = age
def displayName(self):
print("displayName方法执行,打印姓名:", self.name)
def AAA(self):
print("I am AAA.")
def main():
to = TestObj("Tom", 23)
if hasattr(to, "AAA"):
print("实例 to 中有 AAA 属性。")
getattr(to, "AAA")()
else:
print("实例 to 中没有 AAA 属性,将会设置。")
setattr(to, "AAA", AAA) # 参数:实例、方法名称、具体方法 相当于 to.AAA = AAA 第一个AAA是函数在实例中的名称, 第二个AAA是把哪个函数放进去,两者只是恰好这里名称一样
# to.AAA(to) # 这里一定要主动传递一个实例进去,因为它不会自动装配self
getattr(to, "AAA")(to)
5)反射的本质是将字符串反射为为内存对象。
6)通过字符串来导入模块
A 通过字符串导入模块
temp = "re"
model = __import__(temp)
def main():
txt = "hj123uo"
pattern = model.compile(r"[0-9]+")
print(model.search(pattern, txt).group())
B 以字符串形式使用模块方法
temp = "re" # 要引入的模块
func = "compile" # 要使用的方法
model = __import__(temp) # 导入模块
function = getattr(model, func) # 找到模块中的属性
参考:
https://www.cnblogs.com/rexcheny/p/9498091.html
1 用python实现单链表
class Node(object):
def __init__(self, data=None, nextNode=None):
self.data = data
self.nextNode = nextNode
def buildList(head, arr):
if not arr or not head:
return head
currNode = head
for i, value in enumerate(arr):
newNode = Node(value)
currNode.nextNode = newNode
currNode = newNode
return head
2 用python实现队列
class Node(object):
def __init__(self, data=None, nextNode=None, prevNode=None):
self.data = data
self.nextNode = nextNode
self.prevNode = prevNode
class MyQueue(object):
def __init__(self):
self.head = Node()
self.prev = self.head
self.tail = None
self.len = 0
def getLen(self):
return self.len
def isEmpty(self):
if self.len <= 0:
return True
else:
return False
def push(self, data):
curr = Node(data)
self.prev.nextNode = curr
curr.prevNode = self.prev
self.prev = curr
self.tail = self.prev
self.len += 1
def pop(self):
if self.isEmpty():
print "queue is empty, it can not pop"
return
temp = self.head.nextNode
self.len -= 1
self.head.nextNode = self.head.nextNode.nextNode if self.head.nextNode else None
return temp.data
def getHead(self):
if self.isEmpty():
print "queue is empty, it can not get head"
return None
return self.head.nextNode.data
def getTail(self):
if self.isEmpty():
print "queue is empty, it can not get head"
return None
else:
return self.tail.data
3 用python实现栈
class MyStack(object):
def __init__(self):
self.length = 0
self.list = list()
def push(self, data):
self.length += 1
self.list.append(data)
def isEmpty(self):
if self.length >= 1:
return False
else:
return True
def pop(self):
if self.isEmpty():
print "Stack is empty, it can not pop"
else:
del self.list[self.length - 1]
self.length -= 1
def getHead(self):
if self.isEmpty():
print "Stack is empty, it can not pop"
else:
result = self.list[self.length - 1]
return result
4 用python实现树
class BinaryTreeNode(object):
def __init__(self, data, left=None, right=None):
self.data = data
self.left = left
self.right = right
def buildBinaryTree(data, begin, end):
if not data:
return
if end < begin:
return
if end == begin:
node = BinaryTreeNode(data[begin])
return node
length = end - begin + 1
middle = length / 2 + begin
root = data[middle]
rootNode = BinaryTreeNode(root)
leftNode = buildBinaryTree(data, begin, middle - 1)
rightNode = buildBinaryTree(data, middle + 1, end)
rootNode.left = leftNode
rootNode.right = rightNode
return rootNode
5 用python实现快速排序算法
def partition(array, low, high):
if not array or low < 0 or high < 0 or high < low:
return array
value = array[low]
while low < high:
while low < high and array[high] >= value:
high -= 1
# 这样确保array[low]一定是< value的,用于下面的比较
array[low] = array[high]
while low < high and array[low] <= value:
low += 1
array[high] = array[low]
array[low] = value
return low
def quickSort(array, low, high):
if not array or low < 0 or high < 0:
return
if low < high:
index = partition(array, low, high)
quickSort(array, low, index)
quickSort(array, index + 1, high)
6 用python实现一个堆排序算法
def down(array, size):
if not array or size <= 0:
return
# 自上向下调整,每次用父节点和左右子孩子中最大者比较,
# 如果父节点元素小于左右孩子中最大者,则父节点与左右孩子中最大者交换
# 直到当前节点没有左右孩子为止
i = 0
while i < size:
# 如果当前节点没有左右孩子
left = 2 * i + 1
right = 2 * i + 2
if left >= size:
break
# 如果有两个孩子
if right < size:
maxValue = max(array[left], array[right])
if array[i] >= maxValue:
break
else:
if maxValue == array[left]:
array[i], array[left] = array[left], array[i]
i = left
else:
array[i], array[right] = array[right], array[i]
i = right
else:
if array[i] < array[left]:
array[i], array[left] = array[left], array[i]
i = left
else:
break
def up(array, size):
if not array or size <= 0:
return
length = len(array)
if size > length:
return
# 假设当前元素的下标为i
# 那么左孩子下标为2*i + 1,右孩子下标为2*i + 2
# 父节点下标为: (i-1)/2
for i in range(size/2, -1, -1):
leftIndex = 2 * i + 1
rightIndex = 2 * i + 2
# 说明当前节点没有孩子,则过滤
if leftIndex > size - 1:
continue
# 如果有左右两个孩子
if rightIndex <= size - 1:
maxValue = max(array[leftIndex], array[rightIndex])
# 无需调整
if array[i] >= maxValue:
continue
else:
if array[leftIndex] == maxValue:
array[i], array[leftIndex] = array[leftIndex], array[i]
else:
array[i], array[rightIndex] = array[rightIndex], array[i]
# 如果只有左孩子
else:
if array[i] < array[leftIndex]:
array[i], array[leftIndex] = array[leftIndex], array[i]
def maxHeapSort(array):
if not array:
return
length = len(array)
# 第一次,先自底向上构建符合要求的二叉树
up(array, length)
# 进行 length - 1次自底向上调整(如果用自上向下,会带来问题)
for i in range(length - 1, 0, -1):
# 将array[i]与头部元素交换
array[0], array[i] = array[i], array[0]
# 开始调整
up(array, i)
1 bytes、str与unicode的区别是什么?
python2:
str: 二进制序列
unicode: unicode序列
str转unicode: 用decode('utf-8'),从二进制解码为正常字符串
unicode转: 用encode('utf-8'),将正常字符串编码为二进制序列
python3:
bytes: 二进制序列
str: unicode序列
bytes转str: 用decode('utf-8'),从二进制解码为正常字符串
str转bytes: 用encode('utf-8'),将正常字符串编码为二进制序列
def to_str(strOrUnicode):
if isinstance(strOrUnicode, unicode):
strOrUnicode = strOrUnicode.encode('utf-8')
return strOrUnicode
def to_unicode(strOrUnicode):
if isinstance(strOrUnicode, str):
strOrUnicode = strOrUnicode.decode('utf-8')
return strOrUnicode
2 什么是切片?如何使用切片?
1)切片的作用是访问序列中某些元素构成的子集
2)
myList[start:end],注意不包含end所指的元素
myList[-start:] : 表示从倒数第start个开始直到最后一个元素,
注意: 这里的 - 表示倒数. 最后一个元素是倒数第一,即 -1
arr[:1024]只会取前1024个元素
arr[-1024:] 只会取最后1024个元素
3 如何使用步进式切割?如何用一行代码反转字符串?
1)格式: myList[start:end:stride]
从每n个元素中取出一个元素
stride: 步进值
2) result = string[::-1]
-1表示从尾部开始,向前选取,步长为1
4 什么叫做列表推导?map函数如何使用?filter函数如何使用?为何使用列表推导要好于map+filter?请编写一个样例。
1) 列表推导就是根据一个列表制作另一个列表的表达式,它是一个表达式
2)
map(function, arr): 对arr这个序列中每个元素应用function方法,返回处理后的新序列
3)
filter(function, arr): 对arr这个序列中每个元素引用function方法,
凡是function(元素)结果返回为假的元素都会被过滤,返回处理后的新序列
4)列表推导相比较于 map和filter能够更加清晰地处理问题,无需编写lambda表达式, 因此推荐用列表推导
列表推导可以跳过某些元素,如果用map就必须借助filter
5) 列表推导样例代码
def listComprehensionWithEven(arr):
result = [value**2 for value in arr if value % 2 == 0]
return result
5 二层列表推导如何使用?
1)
result = [value for row in matrix for value in row]
注意: 二层列表推导,最前面的是外循环,第二层是内循环
6 列表推导与生成器有什么区别?如何使用生成器?
1)列表推导要把所有数据保存在内存中可能导致崩溃
生成器估值为迭代器,迭代器每次根据生成器表达式产生数据
2)将列表推导用的[]替换为(),就构成生成器表达式。
iter = (len(line) for line in open(fileName))
iter = ((a, a**0.5) for a in iter)
7 为什么使用enumerate?如何使用enumerate?
1) enumerate可以把各种迭代器包装为生成器,方便产生输出值
每次生成一对值,(下标,值)
2)
def useEnumerate(dataList):
for i, value in enumerate(dataList, 1):
print "index: {index}, value: {value}".format(
index=i,
value=value
)
8 zip的作用是什么?python 2中使用zip会有问题吗?如何使用?
1)zip的作用是把两个或两个以上的迭代器封装为生成器,可以稍后求值
zip生成器会从每个迭代器中获取当前迭代器的下一个值,然后把值汇聚成元组
2)python 2中的zip会把迭代器产生的值汇聚成元组,并把元组组成的列表返回。所以不是zip迭代器
带来内存问题,使用itertools.izip函数
zip中只要有一个迭代器运行完成,就不会产生元组了
3)
lens = [len(value) for value in data]
print lens
maxLen = -1
maxValue = ""
for length, value in itertools.zip(lens, data):
if length > maxLen:
maxValue = value
maxLen = length
9 try/except/else如何使用?try/finally如何使用?for和while循环后面写else块如何使用?
1)try/except/else :如果try块没有失败,就执行else
2)try/finally: 如果try块执行过,就执行finally块
3)
只有整个循环主体都没有遇到break语句,循环后面的else块才会执行
def isPrime(a, b):
for i in range(2, min(a, b) + 1):
if a % i == 0 and b % i == 0:
print "not coprime"
break
else:
print "coprime"
10 为什么尽量用异常来表示特殊情况, 而不要返回None?如何解决?
1)函数返回None会使调用它的人写出错误的代码,因为None, 0, 空字符串都会被
条件表达式评估为False
2)解决方法:发生异常,直接抛给调用者
11 python中有哪些常见优化方式?
1) 用生成器来该写直接返回列表的函数
调用生成器函数时,不会真正运行,而是返回迭代器,
在迭代器调用next函数时,迭代器会把生成器推进到下一个yield表达式
12 迭代器的实现原理是什么?python中的容器是什么?如何编写自己的容器? 如何判断某个值是迭代器还是容器?
1)执行类似for x in data这样的语句时,python实际会调用iter(data)
内置的iter函数又会调用data.__iter__方法,该方法必须返回迭代器对象。
该迭代器实现了__next__方法。
fox x in data --> iter(data) --> data.__iter__ --> __next__
2) python中的容器就是实现了自己的迭代器__iter__方法
3)步骤: 编写一个类,给该类实现__iter__方法,用yield返回生成器
样例:
class ReadData(object):
def __init__(self, fileName):
self.fileName = fileName
def __iter__(self):
with open(self.fileName, "rb") as fr:
for line in fr:
yield int(line)
4)
判断某个值是否时迭代器还是容器,就用值作为参数,两次调用iter函数,结果相同则为迭代器。
13 什么是闭包?闭包使用会有什么问题?如何实现闭包内影响闭包外的变量?python3如何实现影响闭包外变量的?
1)闭包是一个函数,引用了该作用域的变量
2)闭包里面的赋值是重新定义一个变量,而不是引用闭包外面的同名变量。
3)利用列表是可修改的,能从闭包内影响闭包外的变量。
def sortPriorityInfluenceExternalScope(values, group):
found = [False]
def helper(value):
if value in group:
# 充分利用列表可修改的特点来实现闭包内影响闭包外变量
found[0] = True
return (0, value)
return (1, value)
values.sort(key=helper)
print found[0]
return values
4) python3用nonlocal影响外围作用域中的变量
14 *args参数作用?星号参数的限制是什么?
1)星号参数是可选位置参数,常记为*args
2)变长参数传递给函数会先转换为元组,python需要遍历,如果参数过多,
会带来内存问题。
不要对生成器用*操作符,可能导致崩溃。
15 什么是关键字参数?为什么使用它?
1)关键字参数是指func(a=1, b=2)中类似a=1这种关键字参数a
2)关键字参数用于兼容代码。
16 为什么用None和文档字符串来描述具有动态默认值的参数?
1) 代码一旦加载,参数默认值不变,可以通过设置如果参数值为None,
就设置默认值。
17 python中有哪些参数?
*之前的参数叫做位置参数,*参数叫做可变位置参数,
**参数叫做关键字参数。
18 具名元组是什么?如何使用
1) 具名元组是collections.namedtuple
2) collections.namedtuple(元组名称,(属性名称1,属性名称2,...,属性名称n))
样例:
Grade = collections.namedtuple('Grade',('score', 'weight'))
19 如何将类的对象变成可调用对象?编写一个样例
1)使用__call__方法,让类的对象变成可调用对象
x()与x.__call__()相同
2)
class Point(object):
def __init__(self, size, x, y):
self.x = x
self.y = y
self.size = size
def __call__(self, x, y):
self.x = x
self.y = y
20 python中的多重继承如何实现?编写一个样例。
1) 多重继承编写mix-in类,只定义其他类可能需要提供的一套附加方法,
而不定义自己的实例属性,不要求使用者调用自己的__init__构造器
2)
class ToDictMixin(object):
def to_dict(self):
return self._traverse_dict(self.__dict__)
class JsonMixin(object):
@classmethod
def fromJson(cls, data):
kwargs = json.loads(data)
return cls(**kwargs)
def toJson(self):
return json.dumps(self.to_dict())
class DataTools(ToDictMixin, JsonMixin):
def __init__(self, switch=None, machines=None):
self.switch = Switch(**switch)
self.machines = [
Machine(**kwargs) for kwargs in machines
]
ss
21 类成员变量可见度有哪些?特点是什么?private属性的作用是什么?
1)可见度种类: public, proteced(单下划线的属性)和private(双下划线的属性)
2)
A.类的外面直接访问private字段会引发异常
B.子类无法访问父类的private字段
原理: python对私有属性名称做了简单变换
3)private属性用来解决父类和子类属性名称冲突的问题
22 如何实现自定义容器类型?
1) 如果定制子类简单,可以继承python的容器类型
如果比较复杂,可以从collections.abc模块的抽象基类中继承
collections.abc: 定义了抽象基类,提供每种容器类型具备的常用方法
23 @property的作用是什么?
1) 访问类的成员变量,并可以向其中添加复杂的逻辑
24 什么是描述符类?类的成员变量赋值背后调用了什么方法?查找属性的路径是什么?weakref有什么作用?
1) 描述符类: 可以提供__get__和__set__方法
2) 原理:
exam.writingGrade = 40
会被转换为
Exam.__dict__['writingGrade'].__set__(exam, 40)
获取属性会被转换为
Exam.__dict__['writingGrade'].__get__(exam, Exam)
之所以存在上述转换: 是因为object类的__getattribute__方法
3)类实例没有某个属性 ---> 在类中查找同名属性(
类属性,如果实现了__get__和__set__方法,则认为该对象遵循描述符协议)
4) weakref提供WeakKeyDictionary的特殊字典
特点:如果运行系统发现这种字典所持有的引用是整个程序里面执行Exam
实例的最后一份引用,那么系统就会将实例从字典的键中移除
WeakKeyDictionary保证描述符类不会泄露内存
class Grade(object):
def __init__(self):
self._values = weakref.WeakKeyDictionary()
def __get__(self, instance, instance_type):
if instance is None:
return self
return self._values.get(instance, 0)
def __set__(self, instance, value):
if not (0 <= value <= 100):
raise ValueError('Grade must be between 0 and 100')
self._values[instance] = value
25 __getattr__、__getattribute__和__setattr__如何实现按需生成的属性?__getattr__与__getattribute__的区别?注意点有哪些?
1)
A. 若某个类定义了__getattr__, 且无法在该类对象的实例字典中找到查询的属性,
系统就会调用这个方法
B. __getattribute__即使属性字典里面已经有了该属性,也会调用__getattribute__方法
应用场景: 每次访问属性时,检查全局事务状态
C. __setattr__可以拦截对属性的赋值操作
2) __getattr__只会在待访问属性缺失时触发,而__getattribute__会在每次访问属性时触发
3) 若要在__getattribute__和__setattr__方法中访问属性,需要通过super()来避免无限递归
class ValidaingDB(object):
def __init__(self):
self.exists = 5
def __getattribute__(self, name):
print "Called __getattribute__(%s)" % (name)
try:
return super(ValidaingDB, self).__getattribute__(name)
except AttributeError:
value = "Value for %s" % name
setattr(self, name, value)
return value
26 元类如何使用?元类中如何验证子类?如何实现注册子类? 如何用用元类来注解类的属性?
1)元类继承自type,包含: 元数据,使用元类的那个类的名称,使用元类的那个类的父类,使用元类的那个类的类属性
返回时,返回 type.__new__(meta, name, bases, class_dict)
使用元类的类在类中定义一个类属性如下:
__metaclass__ = MyMetaClass
2) 元类可以在生成子类对象之前验证子类的定义是否符合要求
子类的class语句处理完成后,会调用其元类的__new__方法。
样例:
class ValidatePolygon(type):
def __new__(meta, name, bases, class_dict):
# 不要验证基类本身,而是验证基类的子类
if bases != (object,):
if class_dict['sides'] < 3:
raise ValueError('Polygons need more than three sides')
return type.__new__(meta, name, bases, class_dict)
class Polygon(object):
__metaclass__ = ValidatePolygon
@classmethod
def interiorAngles(cls):
return (cls.sides - 2) * 180
class Triangle(Polygon):
sides = 3
3)元类可以在程序中自动注册类型,对于需要反向查询的场合有用。
可以在简单的标示符与对应的类之间建立映射关系。
原因: 定义完子类的class语句体后,元类可以拦截这个新的子类,
就可以注册新的类型
样例代码:
class Meta(type):
def __new__(meta, name, bases, class_dict):
cls = type.__new__(meta, name, bases, class_dict)
# 获取新的类后,注册该类
registerClass(cls)
return cls
class RegisteredSerializable(BetterSerializable):
__metaclass__ = Meta
class Vector3D(RegisteredSerializable):
def __init__(self, x, y, z):
super(Vector3D, self).__init__(
x, y, z
)
self.x = x
self.y = y
self.z = z
registry = {}
# 建立: <类名,类>的映射,用于后续根据序列化的数据建立类的实例
def registerClass(targetClass):
registry[targetClass.__name__] = targetClass
4) 在类定义好但是还没有使用的时候,提前修改或注解类的属性
该写法通常会与描述符搭配。
应用场景:
定义某个类,希望该类的相关属性与数据库表的每一列建立关系。
class Meta(type):
def __new__(meta, name, bases, class_dict):
for key, value in class_dict.items():
if isinstance(value, Field):
value.name = key
value.internalName = "_" + key
cls = type.__new__(meta, name, bases, class_dict)
return cls
class DatabaseRow(object):
__metaclass__ = Meta
class BetterCustomer(DatabaseRow):
first_name = Field()
last_name = Field()
prefix = Field()
suffix = Field()
class Field(object):
def __init__(self):
# 下面的属性已经被元类设置了
self.name = None
self.internalName = None
def __get__(self, instance, instance_type):
if instance is None:
return self
return getattr(instance, self.internalName, '')
def __set__(self, instance, value):
return setattr(instance, self.internalName, value)
27 并发与并行的区别?如何运行子进程?
1)并发是轮流执行程序的方式。
并行是同一时间执行多个程序。
并行比并发快。
2)subprocess模块是最简单的子进程管理模块,可管理输入流和输出流
特点: 子进程会独立于父进程运行
父进程: 是python的解释器
可以给communicate传入timeout参数,避免死锁
def useSubprocess():
proc = subprocess.Popen(
['echo', 'I like python!'],
stdout=subprocess.PIPE
)
out, err = proc.communicate()
result = out.decode("utf-8")
print result
28 CPython运行python的过程是怎样的?GIL是什么?GIL的缺点是什么?
1) 源码->字节码->解释器运行字节码,为保证一致性使用GIL。
2) GIL全局解释器锁,本质是互斥锁,是防止多线程导致字节码解释器不一致性。
多进程中每个进程有一个解释器,则不受GIL约束。
3)GIL缺点是无法实现多线程,同一时刻只有一条线程可以执行。
29 如何用用协程来并发地运行多个函数?yield from用法?请编写一个样例。
1) 使python程序看上去同时运行多个函数,
原理:
A)生成器函数执行到yield表达式,消耗生成器的代码,通过send方法给
生成器传回一个值,生成器收到了send函数传进来的这个值后,会视为是yield
表达式的执行结果
B)协程是独立函数,消耗外部环境传入的输入数据,并产生输出结果
与线程的不同点:
协程会在生成器函数中的每个yield表达式那里暂停,等到外界再次
调用send方法,才会继续执行到下一个yield表达式。
注意: 在生成器调用send方法前,需要先调用一次next函数,将生成器
进入到第一条yield表达式
2) python2中没有yield from表达式
把两个生成器组合起来,需要在委派给另一个线程的地方,多写一层循环
python2的生成器不支持return语句,需要定义自己的异常类型。
并在需要返回某个值的时候,抛出该异常
3) 样例代码:
def minimize():
current = yield
while True:
value = yield current
current = min(value, current)
def useMinimize():
it = minimize()
next(it)
print it.send(10)
print it.send(4)
print it.send(22)
print it.send(-1)
30 多进程为什么可以提高性能?多进程的处理过程怎样?多进程适用哪些场景?
1)multiprocessing的模块。
原理: 会以子进程的形式,平行运行多个解释器,从而令
Python程序能够利用多核心的CPU来提升执行速度。
由于子进程与主解释器分离,所以它们的全局解释器锁也是独立的。
子进程可以接收主进程发送过来的指令,把计算结果返回给主进程。
2)
pickle对数据序列化为二进制形式->
通过本地套接字将序列化数据从主进程发给子进程->
子进程用pickle对数据反序列化得到python对象->
对运行结果序列化为字节,并通过socket复制到主进程->
主进程对字节反序列化还原为python对象
3) 适用运行函数不需要与程序中其他部分共享状态;主进程与子进程传递数据量小
31 如何实现可靠的pickle操作?如何使用?copyreg作用是什么?
1)pickle模块能够将Python对象序列转化为字节流,并反序列化为Python对象
2)pickle.dump(obj, file, protocol=None):
obj: 待序列化的对象
file: 存储序列化对象的文件
pickle.load(file)
file: 存储序列化对象的文件
用法:
state = GameState()
state.level += 1
state.lives -= 1
statePath = "/tmp/gameState.bin"
with open(statePath, "wb") as f:
pickle.dump(state, f)
with open(statePath, 'rb') as f:
stateAfter = pickle.load(f)
3)copyreg
定义: 注册函数,python对象的序列化由这些函数负责
作用:
1) 为缺失的属性提供默认值
2) 用版本号来管理类
在copy_reg注册的函数中添加表示版本号的参数,
根据版本号,在关键字参数中根据版本号参数做相应的处理
class GameState(object):
def __init__(self, level=0, lives=4, points=0, name='chao'):
self.level = level
self.lives = lives
self.points = points
# self.name = name
def pickleGameState(gameState):
kwargs = gameState.__dict__
kwargs['version'] = 2
return unpickleGameState, (kwargs,)
def processCopyregPickle():
copy_reg.pickle(GameState, pickleGameState)
state = GameState()
state.points += 1000
serialized = pickle.dumps(state)
stateAfter = pickle.loads(serialized)
print stateAfter.__dict__
32 什么是UTC时间?如何进行时区转换?如何正确处理时间?
1) utc是协调世界时间,是标准时间表示方式,与时区无关
2) datetime加上pytz可以进行时区转换
3) 应用国际化处理时间的正确流程如下:
存储在数据库的是utc时间<---api查询传入的也是utc时间<--前端页面展示的是获取的utc时间+根据当前时区转换后的本地时间
33 常见数据结构有哪些?
1)双端队列collections.deque
可以从队列的头部或尾部插入或移除一个元素消耗常数级别的时间
与list的区别:
list尾部插入是常数时间,但是头部插入/移除是线性级别时间
python的Queue.Queue()中就是使用双端队列。
2) 有序字典collections.OrderedDict
含义: 特殊字典,按照插入顺序,保留键值对在字典中的顺序
适用: 对顺序有先后要求的场合,例如最长前缀匹配
3) 带有默认值的字典collections.defaultdict:
如果字典里面没有待访问的键,那么就会把默认值与这个键关联起来。只需要提供返回默认值的函数
4) 优先级队列heap模块
实现了优先级队列,提供heappush, heappop, nsmallest,实现在list中创建堆结构
特点: 优先级上执行操作,消耗线性级别时间
5) 二分查找bisect模块
提供bisect_left函数提供了二分查找算法
bisect_left函数返回索引表示待搜寻的值在序列中的插入点
6) 与迭代器有关的工具
itertools模块: 组合操作迭代器
A. 把迭代器连接起来的函数:
chain: 把多个迭代器按顺序连接成一个迭代器
B. 从迭代器中过滤元素的函数
islice: 在不进行复制的前提下,根据索引值来切割迭代器
C. 从迭代器中过滤元素的函数
product: 根据迭代器中元素计算笛卡尔积,并返回
permutations: 用迭代器中元素构建长度为N的有序排列,并将所有排列返回
combination: 用迭代器中的元素构建长度为N的各种无序组合并返回
34 如何定义包?包的作用是什么?如何减少暴露给外部的API?
1) 在目录中放入__init__.py
只要目录里有__init__.py,就可以用相对于该目录的路径来引入目录中的其他文件
2) 把模块分配到不同名称空间中,即使存在同名文件都可以
3) 通过为包或者模块编写__all__的特殊属性,
减少暴露给外部的API。
__all__是列表,每个名称是本模块中暴露给外部的API。
样例:
__all__ = [
'TriggerCUDPublisher',
'TriggerInstancePublisher']
35 重视精度的场合用什么?
有decimal.Decimal: 提供28个小数位,进行定点计算
用法: decimal.Decimal(numStr),
其中numStr是字符串表示的数字,例如"3.29'
指定精度和舍入方式:
decimal.Decimal.quantize(decimal.Decimal('0.01'), rounding=ROUND_UP)
36 如何解决循环依赖?import机制是什么?
1)解决循环依赖的最佳方法时
把两个模块相互依赖的代码重构为单独模块并放在依赖树的底部。
最简单的方式就是动态引入,即真正用到待引入的模块时,才在函数中引入该模块。
2)查找模块->加载模块代码,创建模块的空对象->运行模块对象的代码
查找,加载,运行
37 如何将开发环境对软件包的依赖关系导入?virtualenv用法?
1)pip freeze > requirements.txt
2)
安装:
yum install python-virtualenv
或者
pip install virtualenv
生成一个虚拟环境:
virtualenv ENV
让虚拟环境生效:
source ENV/bin/activate
安装依赖文件
pip install -r requirements.txt
让虚拟环境失效:
deactivate
38 repr的作用是什么?
1)根据某个对象,返回可打印的字符串
repr(mystr) 等同于 print "%r" % (mystr)
在类中编写__repr__方法来打印该类的对象
39 unittest模块如何使用?有什么需要注意的?
1)特点:
A 测试以TestCase形式组织,每个以test开头
B assertRaises(错误类型,方法名称,传给该方法的参数)
验证是否会抛出异常。
setUp方法: 执行每个测试前,会调用一次
tearDown: 执行每个测试后,会调用一次
2)注意测试文件必须以test开头,否则无法找到;待测试的方法也必须
以test开否,否则无法找到
40 pdb是什么?如何使用?
1)pdb是python交互调试器
2)修改程序,在需要调试的代码前一行加上如下内容
import pdb;pdb.set_trace()
bt: 打印当前执行点的调用栈信息
up: 回到函数调用栈的上一层
down: 把调试范围沿着函数调用栈下移一层
step: 跳入函数内部
next: 执行下一行代码,可直接用n代替
return: 直接运行到当前函数的return语句开头
continue: 运行到下一个断点
1 pecan库作用是什么?原理是什么?
1)Pecan是一个路由对象分发的python web框架。
本质上可以将url通过分割为每一部分,然后对每一部分查找对应处理该URL部分的处理类,
处理后,继续交给后面部分的URL处理,直到所有URL部分都被处理后,
调用最后分割的URL对应的处理函数处理。
2)pecan库首先根据配置文件中配置的主入口Controller类获取到请求,
然后对url分割,查找对应的Controller对象,经过处理后,根据default或者lookup方法
把除了当前路径外剩余的路径给当前Controller下一个处理的Controller对象处理,直到所有URL都被处理。
2 django是什么?架构是什么?逻辑处理流程是什么?各个文件作用是什么?如何实现路由分发?
1)django是一个python的web应用框架,采用MTV框架模式。
主要包含创建模型的关系对象映射,URL分派,模板等。
对象关系映射:ORM,用python累定义数据模型,然后ORM将模型与关系数据库连接,可以方便使用数据库。
URL分派:使用正则表达式匹配URL,而后可以调用对应视图中的方法处理
模板系统:设计了模板语言。
2)djamgo的mtv模型
M:Model,模型,是数据存取层,内嵌ORM框架,
T:模板,Template,处理页面展示相关,包含模板引擎
V:View,视图,业务逻辑层。适当的时候调用Model和Template.
Url控制器:负责路径与视图函数的映射关系
3) django逻辑处理流程
s1: mangager.py runserver启动Django服务器,载入settingspy,读取配置信息等
s2: 访问url时,根据ROOT-URLCONF的设置加载URLConf
s3: 按照顺序匹配URLConf中的url pattern,找到就调用对应的视图函数,
并把httpRequest对象作为参数传入
s4: 视图函数返回一个HttpResponse对象
输入url->url控制器->根据url匹配相应的视图函数->去models中获取数据->model去数据库中获取数据
->将数据返回给views->views将数据返回给模板->渲染模板(html文件)->展示给用户
4)django中目录文件作用
manager.py: 与项目交互的命令行工具入口,项目管理,开启项目
wsgi.py: python的web服务器网关接口,是python应用与web服务器之间的接口
urls.py: url配置文件
settings.py: 项目的配置文件,包含数据库等各种配置
template目录:存放模板文件,例如html
5)django的url路由分发原理
作用:解析请求的url,匹配找到对应的view函数来处理。
ResolverMatch就是匹配结果,包含匹配成功后需要的信息;
URLPattern是一条url映射信息的对象,包含了url映射对应的可调用对象等信息;
URLResolver是实现url路由,解析url的关键的地方,它的url_patterns既可以是URLPattern也可以是URLResolver。
正是因为这种设计, 实现了对URL的层级解析。
最核心的就是看resolve方法。这里面在遍历url_patterns的时候,这里面的pattern可能是urlpattern,也可能是URLResolver;
如果是urlpattern,匹配成功则返回ResolverMatch;如果是URLResolver,则会递归调用下去。
所以这就可以解释为什么可以进行多级的url配置。
3 什么是orm?sqlalchemy架构是什么?如何实现表的多对多关系?
1)orm就是对象关系映射,把对数据库中某个表的操作转换为对某个对象的操作。
2) 架构图如下:
SQLAlchemy ORM: Object Relational Mapper
SQLAlchemy Core: Schema/Types, SQL Expression Language, Engine
Pool Dialect
SQLAlchemy主要分为ORM层和Core层。
ORM层: 提供接口给用户,让用户操作python对象来实现操作数据库
Core层: 包含Schema/Types, SQL Expression Language, Engine
Schema/Types: 实现对SQL的DDL数据定义语言的封装,用Table类表示一个表,
用Column类表示列。
SQL Expression Language: 是SQL表达系统,实现对DML数据操纵语言封装,
主要就是对SELECT, DELETE, UPDATE等语句的封装。
Engine: 对不同数据库的封装。对数据库的操作需要通过这个Engine对象来进行。
包含Pool和Dialect。
Pool: 管理程序到数据库的连接
Dialect: 对接不同的数据库驱动,这些驱动要实现DBAPI接口。
---Pool-----
connect() <-Engine DBAPI------Database
---Dialect--
3) 多对多表关系的实现需要三张表,是在sqlalchemy的relationship中secondary参数指明关系表,
该关系表中包含需要包含多对多关系表的各自主键,以及自己的主键。
样例如下:
class Blog(Base):
__tablename__ = "blog"
id = Column(BIGINT, primary_key=True, autoincrement=True)
title = Column(String(64))
tag_list = relationship("Tag", secondary=lambda: BlogAndTag.__table__)
class Tag(Base):
__tablename__ = "tag"
id = Column(BIGINT, primary_key=True, autoincrement=True)
name = Column(String(64))
class BlogAndTag(Base):
__tablename__ = "blog_and_tag"
id = Column(BIGINT, primary_key=True, autoincrement=True)
blog = Column(BIGINT, ForeignKey("blog.id"), index=True)
tag = Column(BIGINT, ForeignKey("tag.id"), index=True)
参考:
[1] 器 参考:
https://www.liaoxuefeng.com/wiki/1016959663602400/1115615597164000
[2] 类 参考:
Effectiv Python 编写高质量Python代码的59个有效方法
[3] 进程池与线程池 参考:
https://blog.csdn.net/hao119119/article/details/47156977
https://blog.csdn.net/u011726005/article/details/82670730
[4] 协程 参考:
https://www.xuebuyuan.com/3227958.html
https://greenlet.readthedocs.io/en/latest/
http://dwise1.net/pgm/sockets/blocking.html
https://www.cnblogs.com/maociping/p/5129858.html
https://www.cnblogs.com/maociping/p/5132583.html
https://www.xuebuyuan.com/3227958.html
[5] 实现原理 参考:
python相关模块源码
python源码剖析
https://blog.csdn.net/qq_41312839/article/details/79470823
https://blog.csdn.net/sinboo833/article/details/39179055
https://blog.csdn.net/weixin_33919950/article/details/88014835
https://yq.aliyun.com/articles/647974
https://www.cnblogs.com/hpu0623/p/10965285.html
https://www.cnblogs.com/rexcheny/p/9498091.html
https://www.jianshu.com/p/db75baf61cdd
[6] 算法 参考:
python程序员面试宝典
[7] 基础 参考:
Effectiv Python 编写高质量Python代码的59个有效方法
[8] python重要框架原理 参考:
https://blog.csdn.net/qq527631128/article/details/90245555
https://blog.csdn.net/qq527631128/article/details/90426673
https://blog.csdn.net/qq527631128/article/details/91478794
https://blog.csdn.net/bbwangj/article/details/79935500
https://baike.baidu.com/item/django/61531?fr=aladdin
https://blog.csdn.net/liangkaiping0525/article/details/80864318
https://www.cnblogs.com/52forjie/p/7825164.html
https://www.cnblogs.com/xiugeng/p/9507263.html
https://www.jianshu.com/p/cecd54265bff
https://msd.misuland.com/pd/2884250034537241602
https://www.cnblogs.com/panwenbin-logs/p/5731265.html
https://segmentfault.com/a/1190000004261891
https://segmentfault.com/a/1190000004466246
https://www.ctolib.com/topics-87780.html
https://www.ucloud.cn/yun/45410.html
http://www.cnphp.info/sqlalchemy-many-to-many.html
https://www.cnblogs.com/cainingning/p/10684206.html
https://www.cnblogs.com/songzhixue/p/11277809.html
https://www.osgeo.cn/sqlalchemy/orm/index.html
https://blog.csdn.net/u011801189/article/details/54585402
https://www.oschina.net/translate/sqlalchemy-vs-orms?print