面试题汇总
1.生成器
使用了yield关键字的函数称为生成器,生成器是一个自定义的迭代器。函数中有yield关键字时,函数名加()不会执行函数体代码,而是会生成一个生成器。生成器内只有__iter__和__next__方法。生成器对比return可以返回多次值,可以挂起保存函数的运行状态,而遇到return就直接结束。
2.python的可变和不可变数据类型是什么?
可变和不可变数据类型是描述数据类型的一种方式。可变数据类型是当数据类型中的某个数据发生改变时,该数据类型绑定的内存地址不发生改变。不可变数据类型是当数据值发生改变时,绑定的内存地址也发生改变,也就是产生一个新的值。
3.深浅拷贝
深浅拷贝是指在复制对象时,是否复制其子对象。浅拷贝只复制对象本身,而不复制其子对象,而深拷贝则会递归复制对象及其子对象。在Python中,浅拷贝创建一个新对象,但是只复制原始对象的基本数据类型和对象的引用,而不复制对象的子对象。这意味着,如果原始对象包含可变对象(例如列表、字典等),则新对象和原始对象将共享这些可变对象。因此,如果在新对象上进行更改,则原始对象也会受到影响。另一方面,深拷贝创建一个新对象,并递归复制原始对象及其所有子对象。这意味着,如果原始对象包含可变对象,则新对象将包含与原始对象完全相同的副本,而不是共享它们。因此,如果在新对象上进行更改,则原始对象不会受到影响。
4.双下new和双下init有什么区别?
1.双下new和双下init都是新生成一个类的实例时调用的,双下new是创建实例时调用,双下init是初始化一个实例时调用。所以双下new在双下init之前执行。
2.双下new是一个静态方法,双下init是一个实例方法
3.双下new会返回一个类的实例,双下init什么都不返回,双下new返回新创建的对象,被当做双下init的第一个参数对其进行实例化的过程。
4.双下new在双下init之前执行
5.数据库三大范式
第一范式(1NF):保证数据库中每一列必须具有原子性,也就是数据表中的每一个字段都是不可再拆分的最小数据单元。
第二范式(2NF):在满足第一范式的基础上,如果表是单主键,那么主键以外的列必须完全依赖于主键。如果表是复合主键,那么主键以外的列必须完全依赖于主键,并且不能依赖于主键的一部分(其中一个或几个字段)。
第三范式(3NF):在满足第二范式的基础上,表中每一个非主键列都必须和主键直接相关,非主键列和主键之间不能传递依赖,它们之间是相互独立的。
6.mysq1有哪些索引类型,分别有什么作用?
按照逻辑来区分:
1.普通索引(辅助索引):是mysql中最基本的索引类型,它没有任何限制,唯一的任务就是加快对数据库的访问速度。普通索引允许在定义索引的列中插入重复值和空值。
举例:在 tb_student 表中的 id 字段上建立名为 index_id 的索引:
CREATE INDEX index_id ON tb_student(id);
2.唯一索引:不是为了提高访问速度而是为了避免数据重复。唯一索引的值必须唯一,允许有空值。如果是组合索引,那么组合的值必须唯一。创建唯一索引通常使用 UNIQUE 关键字。
举例:在tb_student 表中的 id 字段上建立名为 index_id 的索引,SQL 语句如下:
CREATE UNIQUE INDEX index_id ON tb_student(id);
3.主键索引(聚簇索引):专门为主键字段创建的索引,也属于索引的一种。主键索引是一种唯一索引,如果不存在主键,隐藏一个主键,不允许重复或者为空。创建主键索引通常使用 PRIMARY KEY 关键字。不能使用 CREATE INDEX 语句创建主键索引。
4.联合索引(组合索引或多列索引):组成主键的字段为多个,多个字段组成唯一的索引。
7.事物的特性和隔离级别?
事物的特性:
1.原子性:事务中的各项操作是不可分割的整体,要么同时成功,要么同时失败。举例:A给B转账,B的余额未增加,那么A余额一定未减少
2.一致性:使数据库从一个一致性状态到另一个一致性状态的改变。和上述转账例子一样,如果A余额减少,B余额未增加,那么就不符合一致性
3.隔离性:多个事务之间彼此互不干扰
4.持久性:事务一旦提交,对数据库的改变是永久的
事务的隔离级别:
1.read uncommitted(未提交读):事务中的修改即使没有提交,对其他事物也都是可见的。事务可以读取未提交的数据,这一点也称为‘脏读’。
2.read committed(提交读):大多数数据库默认的隔离级别。一个事务从开始直到提交之前所做的任何操作对其他事物都是不可见的,事务中的数据如果没有最终提交,对其他事物可见的都是硬盘当中真正的数据。解决了脏读和数据丢失,但会出现一个事务范围内两个相同的查询出现了不同的结果,也就是不可重复读。
3.repeatable read(可重复读,mysql默认的隔离级别):在开始读取数据(事务开启)时,不再允许修改操作,这样就可以保证在同一个事务内两次读到的数据是一样的,因此称为是可重复读隔离级别,能够解决‘脏读’(因为读期间不能进行修改操作),不能解决‘幻读’(依然可以允许增加操作)。幻读是指当某个事务在读取某个范围内的记录时,另外一个事务又在该范围内插了新的记录,所以我原来的事物再次插入这条重复的数据时无法插入。InnoDB和XtraDB通过多版本并发控制(MVCC)及间隙锁策略解决该问题
4.serializable(可串行化):强制事务串行执行,很少使用该级别。一个事务执行完毕之后再执行另一个事物,执行效率低。可以避免脏读、不可重复读、幻读。
8.脏读、不可重复读、幻读是什么?mysql5.7以后默认的隔离级别是什么?
脏读:如果一个事务读到了另一个未提交事物修改的数据,就是脏读。
脏读最大的问题就是可能会读到不存在(回滚)的数据。比如事务B的更新数据被事务A读取,但是事务B回滚了,更新数据全部还原,也就是说事务A刚刚读到的数据并没有存在于数据库中。 从宏观来看,就是事务A读出了一条不存在的数据,这个问题是很严重的。
不可重复读:不可重复读指的是在一个事务内,最开始读到的数据和事务结束前的任意时刻读到的同一批数据出现不一致的情况。如果一个事务修改了另一个未提交事务读取的数据,就意味着发生了不可重复读。
幻读:如果一个事务先根据某些查询条件查询出一些记录,在该事务未提交时,另一个事务写入了一些符合那些查询条件的记录(INSERT,DELETE,UPDATE操作),就意味着发生了幻读现象。
幻读是指当某个事务在读取某个范围内的记录时,另外一个事务又在该范围内插了新的记录。当之前的事务再次读取该范围内的记录时会产生幻行
不可重复读和幻读两者有些相似。但不可重复读重点在于update和delete,而幻读的重点在于insert。
脏写:如果一个事务修改了另一个事务提交修改的数据,就意味着发生了脏写现象。
mysql5.7以后默认的隔离级别是可重复读。其余数据库默认隔离级别是提交读。
详细:https://www.cnblogs.com/liuqingzheng/p/16480047.html
8.什么是qps,tps,并发量?
qps:Queries Per Second,指一台服务器每秒能够响应查询的次数,用于衡量查询服务器在规定时间内所处理的请求数量。
如何估算自己项目的QPS?
使用日志估算即可,比如在中间件里记录访问日志,最终统计1s内有多少个访问,qps就是多大;
一个接口的qps跟业务流程有关,跟是否使用缓存有关
不使用缓存:大约8核16G机器,qps 400多,如果横向扩展,10台8核16g的机器,qps大约4000多
使用缓存:大约8核16G机器,qps破千没问题
tps:Transactions Per Second,每秒钟系统能够处理的交易或事务的数量。一个tps过程包括:客户端请求服务端、服务端内部处理、服务端返回客户端。例如,访问一个 Index 页面会请求服务器 3 次,包括一次 html,一次 css,一次 js,那么访问这一个页面就会产生一个T,产生三个Q
并发量:系统同时处理的请求或事务数,可以直接理解为:系统同时处理的请求数量
QPS = 并发量 / 平均响应时间
并发量 = QPS * 平均响应时间
例如当前系统QPS为1w,每个请求的响应时间都是2s,那么并发量就是2w
9.什么是pv、uv?
pv:Page View,指网站的页面浏览量或者点击量。
uv:Unique Visitor,访问一个网站的一台电脑客户端为一个访客,根据ip地址来区分访客数量,在一段时间内重复访问,也算是一个uv(如一个用户一天只能为一篇文章提供一个阅读量)。可以统计服务一天的访问日志并根据用户的唯一标识去重得到(放在集合中)。
10.DAU(日活)、MAU(月活)?
DAU(Daily Active User),日活跃用户数量。常用于反映网站、app、网游的运营情况。DAU通常统计一日(统计日)之内,登录或使用了某个产品的用户数(去除重复登录的用户),与UV概念相似。公司内部项目不用考虑这些指标,互联网项目可以考虑。
MAU(Month Active User):月活跃用户数量,指网站、app等去重后的月活跃用户数量。
11.什么是接口幂等性?如何解决?
接口幂等性就是用户对同一操作发起一次请求或者多次请求的结果是一致的,不会因为多次点击而产生副作用,get请求幂等,put请求幂等,delete请求幂等,post请求不幂等。我们尤其在涉及数据库修改操作时需要注意保证接口的幂等性。首先,我们要知道什么场景下可能不幂等,在发送请求到收到响应的这一段时间,请求端不知道是否发送了请求,则可能多次的发送了请求,响应端也并没有对多次请求做校验,所以多次处理了相同的请求。
针对这一点,我们有三种方案保证接口的幂等:
方式一:唯一主键,unique,新增用户名或者手机号唯一,但是不能解决订单
方式二:token机制:
1.服务端首先需要提供获取token的接口。如果业务存在幂等的问题,就在执行业务之前(比如商城类项目,在进入支付页面时)访问接口,服务端将token保存在redis中,并且返回给前端一份
2.调用业务的接口(比如支付接口)请求时,携带token
3.服务端判断token是否存在redis中,存在表示第一次请求,然后删除redis中的token,执行业务
4.如果token不在redis中,就表示是重复操作,直接返回一个标记给前端,就可以避免接口重复执行
方式三:前端限制按钮只能点击一次
12.python有哪些优缺点?
python是一种解释型语言,它的优点是:
1.语法简单,不要求在每个句子的最后写分号
2.python代码是开源的
3.python功能强大,有许多模块,扩展性强
4.python是免费的
缺点:
1.python是一门解释型语言,运转速度慢
2.代码加密困难,与编译语言不同,源代码将被编译成可执行程序,Python直接运行源代码,因此很难加密源代码
13.什么是GIL锁?有什么作用?
GIL锁全称全局解释器锁,本质就是一个大的互斥锁,这是Cpython的一种机制,只存在于cpython。作用就是限制多线程同时执行,使得在同一进程内任何时刻仅有一个线程在执行,它限制了一个线程只有获取了gil锁才能执行,没有锁就不能执行。某个线程运行完毕后会释放锁,其他线程才能运行。如果线程运行过程中遇到了io操作,则锁会解开,使其他线程运行。GIL只能确保同进程内多线程数据不会被垃圾回收机制乱删,并不能确保程序里的数据是否安全。
python垃圾回收机制需要有一条线程来做,如果同一时刻有多个线程正在执行,垃圾回收机制可能会把正在其他线程正在使用的变量给回收掉。
14.python的垃圾回收机制GC是怎样的?
垃圾回收机制是python的内存管理机制,通过对数据做引用计数、标记清除、分代回收等策略进行内存上的回收。
引用计数:有多少个变量指向数据值,它的引用计数就为几,如果数据值的引用计数为0就会被垃圾回收机制清除
标记清除:引用计数在遇到循环引用时,引用计数无法减为0。标记清除是针对循环引用现象,对这些循环引用的内存空间做标记并且定期清除
分代回收:系统会根据对数据的检索频率将数据分成'新生代'、'青春代'、'老年代',数据值被引用的次数越高,数据被检索的频率越低,数据越不会被系统清除。相反被引用的次数越低,被检索的频率越高,可能会被垃圾回收机制清除。
15.解释为什么计算密集型用多进程,io密集型用多线程?
进程:资源分配的最小单位
线程:cpu调度的最小单位
计算密集型是指更多需要cpu参与的程序,io密集型是指阻塞态更多的程序。
由于GIL锁的存在,即使是多核机器,同一时刻也只能有一个线程在执行,线程需要cpu去调度执行。
如果是计算密集型,开启多线程不能利用多核优势(cpython中的多线程实际是由于单个线程在不同程序中来回切换产生的),开启多进程能利用多核优势。io操作不消耗cpu,并且多线程可以更快地在不同程序之间切换,开启多线程在一个时间段内可以产生并发效果。
16.为什么有了gil锁还要互斥锁?
gil锁全称全局解释器锁,是解释器级别的锁,gil锁本质也是一个互斥锁。是一个简单的机制确保同一时间只有一个线程访问内存,线程要执行首先要获得gil锁。但它并不是为了数据安全设计的。
互斥锁是程序级别的锁,为了保证多线程并发操作数据而设置的锁,保证在加锁和释放锁之间,其他线程不能操作。
互斥锁操作:获得锁:Lock.acquire();释放锁:Lock.release()
例子1:一个进程下有两个线程t1和t2,它们都要去修改全局变量data。假设t1获得gil锁,此时gil抢到了互斥锁。碰巧在修改之前t1遇到了io操作(或者时间片到了),那么gil锁就会被释放被t2抢到,如果t1没有互斥锁那么此时t2直接可以修改data。有了互斥锁的存在此时t2也只能释放gil锁,t1获得cpu可以直接修改data。
例子2:全局的a=0,线程1和线程2都要去修改全局的a,序号代表执行顺序:
线程1:
(1).线程1拿到gil锁
(2).读取a=0
(3).cpu时间片到了,释放gil锁,释放cpu
(4).等待下次被调度执行
(10).轮到线程1执行,计算a+1
(11).将结果赋值给a,a=1
(12).释放gil锁
线程2:
(5).线程2获得了gil锁
(6).读取a=0
(7).计算a+1
(8).把结果赋值给a,此时全局a=1
(9).释放gil锁
以上两个例子说明如果没有互斥锁,会导致数据错乱,出现并发安全问题
但是我们不可能给一个函数中所有的代码都加上锁,这样会降低代码执行的效率。我们将出现并发安全问题的这段代码称为临界区,加锁也要在临界区加锁。上述例子2中的的临界区为:
"""
计算a+1
把结果赋值给a,此时全局a=1
"""
17.进程,线程和协程代码如何实现?你在哪里用过?
进程:资源分配的最小单位,一个应用程序至少有一个进程,进程管理器中就可以看到一个个的进程。进程间通信可以使用消息中间件(在中间件中修改redis中的数据,数据放在redis中)。
线程:cpu调度的最小单位,一个进程下至少有一个线程。
协程:单线程下的并发,程序层面控制任务切换
进程代码实现:
方法1:写一个类,继承Process,重写类的run方法,实例化得到对象,对象.start开启了进程
方法2:通过Process类实例化得到一个对象,传入任务 ,调用对象.start 开启了进程
在哪里用过:遇到计算密集型操作使用多进程,io密集型操作一般开多线程
方法2示例:
from multiprocessing import Process
import time
def run(name):
print('%s is running' % name)
time.sleep(3)
print('%s finished his run' % name)
if __name__ == '__main__':
p = Process(target=run, args=('XWenXiang',)) # 创建一个进程对象
p.start() # 告诉操作系统创建一个新的进程
print('父进程')
# 执行结果:父进程 aaa is running
# 因为加载进程较慢,所以先执行父进程
aaa finished his run
线程代码实现:
方式1:写一个类,继承Thread,重写类的run方法,实例化得到对象,对象.start开启了线程
方式2:通过Thread类实例化得到一个对象,传入任务,调用对象.start开启线程
在哪里用过:爬取数据可以开多线程,爬虫io居多(爬取,下载都是io操作),在程序中异步做一件事也可以开多线程,例如异步发送邮件,邮件报警通知,钉钉通知,将数据写到文件中等等。但是在项目中我们也可以借助于第三方框架比如celery实现异步操作,celery中的worker就是进程线程架构,开启worker就相当于开启了一个进程,里面有一个线程在执行任务。
方式1实例:
from threading import Thread, Lock
import time
import random
sp = Lock()
class MyThread(Thread):
def run(self):
sp.acquire()
print(self.name)
time.sleep(random.randint(1, 3))
sp.release()
for i in range(10):
t = MyThread()
t.start()
方式2示例:
from threading import Thread
a=0
def task():
global a
a += 1
print(a)
for i in range(10):
t = Thread(target=task)
t.start()
# 执行结果:1 2 3 4 5 6 7 8 9 10
开启协程:
早期之前借助于gevent,基于greenlet写的。但是可以借助async(写在函数之前)和await关键字开启协程。执行协程函数:async def task()。
from greenlet import greenlet
def func1():
print(1)
gr2.switch()
print(2)
gr2.switch()
def func2():
print(3)
gr1.switch()
print(4)
gr1 = greenlet(func1)
gr2 = greenlet(func2)
gr1.switch()
# 执行结果:1 3 2 4
18.什么是鸭子类型?
鸭子类型是面向对象中描述接口的一种体现,区分于其他语言比如java,必须显示的继承一个接口,而python中实现接口遵循鸭子类型,不需要显示的继承一个接口(类),只要类中有对应的属性和方法,我们就可以称这几个类的对象为同一种类型。鸭子类型指不关心对象的具体类型,只关心它们的行为,比如走路像鸭子。说话像鸭子,我们可以叫它鸭子。
19.什么是猴子补丁?
程序运行的过程中,动态替换的一种技术。
gevent底层实现原理:gevent执行猴子补丁会执行代码:monkey.patch_all()
同步的代码遇到io操作会阻塞并且释放gil锁,这条线程就会释放cpu的执行。中的猴子补丁执行monkey.patch_all()就会执行猴子补丁。动态将所有的同步代码替换成异步代码,异步代码遇到io不会释放gil锁。time模块和socket模块都会阻塞,执行monkey.patch_all()之后time模块和socket模块已经是包装之后的,不再是原来的了。如下图patch_all的源码,只要传入参数的布尔值是True,那么gevent会默认替换布尔值为True的模块:
from gevent import monkey
monkey.patch_all()
20.什么是反射?
程序在运行的过程中通过字符串来操作对象的属性和方法。有以下几种方法:hasattr,getattr,setattr,delattr
21.http和https的区别?
(1)http使用的是超文本传输协议,使用的是明文传输。https是http+ssl/tls的加密(非对称加密)传输协议,相较于http更安全。
(2)默认端口不同,http端口是80,https是443。
(3)https要通过CA申请,一般需要收费。
22.浏览器输入一个地址到渲染数据经历的过程
(1).在浏览器中输入的地址,需要DNS解析成IP地址+端口形式才能发送页面申请。解析顺序:浏览器缓存>>>本机缓存>>>hosts文件>>>调用操作系统接口来请求外部DNS。
(2).向解析出的域名和端口建立TCP连接,三次握手建立连接
(3).向某个地址发送http的get请求
(4).如果后端服务使用nginx转发,nginx将http请求转发给web框架(django,flask),可以聊一下django的请求生命周期(25题)
(5).后端以http响应的形式返回给客户端浏览器
(6).客户端浏览器把http响应体中的内容展示在浏览器上(可以拓展响应状态码)
(7).四次挥手断开tcp连接(可能不断开,和http版本有关)
23.左连接,右连接,内连接,全连接是什么?
数据通常不在一个表中,这就涉及到连表操作,表间联系的方式有很多:
左连接:以左表为基准,将左表所有的数据展示出来,按照on后面的对应条件分别对应右表中的数据,没有值就用空补齐
右连接:以右表为基准,将右表中所有的数据展示出来,按照on后面的对应条件分别对应左表中的数据,没有值就用空补齐
全连接:以左右两表数据为基准,左右两表的数据都展示,没有的数据用空替代
24.union和union all的区别?
union和union all前后都跟select出来的结果。union会自动对取出的数据进行去重,并且进行排序。union all不会去除重复的数据
25.django请求生命周期?
django请求生命周期是指用户在输入url到用户看到网页的这个时间段,django后台所发生的事
(1)django中封装了wsgi服务器,监听端口接受本次请求,然后将请求交给框架(django)
(2)传送消息到中间件
(3)对请求进行校验或处理,再传送到路由系统中进行路由分发,匹配到对应对的视图函数或者视图类
(4)将请求传输到这个视图函数或者视图类中,进行业务逻辑的判断和处理
(5)调用models中表对象,通过orm拿到数据
(6)将封装了response的响应传输到中间件
(7)通过wsgi再进行封装处理,响应数据到浏览器展示给用户
26.tcp三次握手和四次挥手?
tcp就是通过三次握手和四次挥手保证数据的可靠,不会丢失
SYN:SYN=1表示要建立连接
ACK:ACK=1表示收到了请求,并且同意
FIN:表示断开连接的请求
seq:随机数,只要建立连接无论是客户端还是服务端都需要携带
ack:只要建立连接就需要携带,要回复seq+1
三次握手建立连接:
(1)如果客户端想通过tcp与服务端建立连接,客户端会首先发送一个通信请求询问能否与服务端建立连接,就是syn包,携带一个随机数。携带信息:SYN=1,seq=x。此时服务端由listen状态变成了SYN_RVCD状态。
(2)如果服务端同意,服务端会回复表示同意,携带ACK+syn包。此时服务端还会向客户端发送一个建立连接的请求,就是ACK。携带信息:ACK=1,ack=x+1,SYN=1,seq=y。客户端没收到服务端返回的信息是SYN_SEND状态,收到就是established状态。
(3)客户端收到之后再回复一个ACK包,会话开始。携带数据:ACK=1,ack=y+1。客户端建立好连接状态就是established状态。
为什么不是两次握手而是三次握手?
考虑到极端情况:如果服务端第一次向客户端发送sys包,但是因为各种原因请求没有成功到达服务端。此时客户端再发送一次,如果是两次握手,服务端返回一个sys+ACK包回话就可以开始。而此时第一次发送失败的sys包也被服务端收到,服务端无法判断是新的一次请求还是之前未成功送达的请求。所以需要第三次握手来验证客户端成功收到了服务端的同意信息。
四次挥手断连接:
(1)客户端向服务端发送断开连接的请求(FIN=x)
(2)服务端收到后,回复这个请求(ACK=1,ack=x+1)
(3)服务端向客户端发起断开连接的请求(FIN=y)
(4)客户端收到,同意断开连接(ACK=1,ack=y+1)
(HTTP1.1版本及之后TCP连接默认不关闭,可以被多个请求复用)
27.tcp和udp的区别?udp有哪些应用?
tcp是面向连接的可靠协议,udp是无连接的不可靠协议,都是处于传输层
(1)tcp协议也称可靠协议。提供的是端到端可靠的字节流,通信之前先建立连接,如果对方没有收到消息会反复发送。udp协议也称不可靠协议,udp是无连接的,发送之前不需要建立连接,是否收到消息也不进行回应,也不保证数据一定送达
(2)udp有较好的实时性,工作效率比tcp高,因为tcp正在传输数据之前需要先建立连接。udp适用于对高速传输和有实时性要求的通信
(3)UDP 的标头大小为 8 个字节,TCP 标头大小为 20 字节。UDP 适合一次性传输较小数据的网络应用,如 DNS,SNMP等。
(4)TCP 面向字节流,实际上是 TCP 把数据看成一连串无结构的字节流,UDP 是面向报文的一次交付一个完整的报文,报文不可分割,报文是 UDP 数据报处理的最小单位
udp:一些聊天软件、dns协议也用的是udp协议
tcp:http、mysql、redis客户端服务通信
28.osi七层协议,哪七层,每层有哪些?
osi七层协议:应用层、表示层、会话层、传输层、网络层、数据链路层、物理连接层
五层结构:应用层、传输层、网络层、数据链路层、物理连接层
应用层:有http协议、https协议、ftp协议、dns协议
表示层:https=http+ssl/tls,ssl处于表示层,主要负责加密解密的工作
会话层:负责建立、管理和终止会话连接
传输层:tcp和udp协议属于这一层,端口属于这一层
网络层:ip协议属于这一层,规定了所有接入互联网对的计算机都必须有一个ip地址,类似于身份证号
数据链路层:规定了计算机出厂时必须有MAC地址;数据帧、数据报也属于这一层
物理连接层:确保计算机之间的物理连接介质
29.python常用的设计模式有哪些?
(1)工厂模式:工厂模式是将创建对象的过程放在工厂类当中的模式,这样可以减少很多类实例化的代码。
(2)装饰器模式:装饰器模式是动态地为对象添加额外行为的模式。
(3)观察者模式:是一个通知的机制,在自身状态改变的前提下主动触发某些方法的执行。
(4)单例模式:单例模式是一个类产生的所有对象都具有相同的内存地址的设计模式。
30.wsgi,uwsgi,uWSGI,cgi,fastcgi分别是什么?
CGI:通用网关接口,定义了客户端和服务端如何传数据
FASTCGI:快速通用网关接口,是CGI的升级版,减少网页服务器与CGI程序之间的互动开销,从而使服务器可以同时处理更多的网页请求。常见的FASTCGI:Apache,Nginx
wsgi:定义web服务器和web框架之间的接口标准
uWSGI:一个web Server即一个实现了wsgi协议的服务器,处理发来的请求及返回响应
uwsgi:是uWSGI自有的一个协议,定义传输数据的类型
31.如何自定制上下文管理器?
一个对象如果实现了__enter__和__exit__方法,那么这个对象就支持上下文管理协议,及with语句。
上下文管理协议适用于那些进入和退出之后自动执行一些代码的场景,比如文件、网络连接、数据库连接或使用锁,s使用事务的编码场景等。比如当我们使用sqlarchemy时,将session.commit()和session.close()写在__exit__中。
使用:
class ComonSession:
def __enter__(self):
print('进入with语句块时执行此方法,此方法如果有返回值会赋值给as声明的变量')
self.session=session
return self.session
def __exit__(self, exc_type, exc_val, exc_tb):
self.session.close()
with ComonSession() as session:
session.filter()
print('上下文管理器结束了')
32.python是值传递还是引用传递?
值传递:一个数据值可以被多个变量名绑定,将其中一个变量名当做参数传入函数中,原来的值以及其他指向这个值的变量名不会受到影响
引用传递:一个数据值可以被多个变量名绑定,将其中的一个变量名当做参数传入函数中,原来的值以及指向这个值的变量名都会受到影响
严格意义上来讲,python既不是值传递也不是引用传递,python有自己的传递方式,规则是:
如果传递的是不可变类型,在函数中修改,就不会影响原来的变量
如果传递的值是可变类型,在函数中修改、添加个别元素,会影响原来的变量。如果是重新复制,例如a=新的列表,那么就不会影响原来的变量
33.进程间通信报的方式?
(1)匿名管道:匿名管道适用于父子进程通信的过程,需要pipe函数和fork函数搭配使用
(2)命名管道:命名管道可以实现两个毫不相关进程之间的通信。命名管道由mkfifo函数创建,由open函数打开。
(3)system V共享内存
(4)system V消息队列
(5)system V信号量
详情:https://blog.csdn.net/m0_52169086/article/details/125375655
33.什么是迭代器、生成器、装饰器?
迭代:一种不依赖于索引取值的方式,我们不需要关注它的位置,只要能一个个取值,它就称之为迭代,通过for循环或者__next__来取值。
可以迭代的python对象称为可迭代对象,包括字典、列表、字符串、元组、集合、文件
迭代器对象:可多迭代对象调用__iter__方法,就得到了迭代器对象,迭代器对象有__iter__和__next__方法。如果我们想自定义迭代器,我们只需要写一个类,重写__iter__和__next__方法,这个类的对象就是迭代器对象
生成器对象:本质就是迭代器,函数中有yield关键字,函数名加括号不会执行函数体代码而是会产生一个生成器对象。应用:只要放在列表中的数据,都可以放在生成器当中,比如用户的打卡日期等
(i+1 for i in [1,2,3])
装饰器:本质是一个闭包函数,作用是在不改变被装饰对象原代码的情况下,为函数增加新的功能。应用:django的信号使用装饰器,flask的路由系统使用装饰器、drf为接口增加判断条件(身份、职位,用认证的权限也可以做)
34.django中的信号如何使用,有哪些作用?
django的信号提供了一种通知机制,他是设计模式中的观察者模式(发布订阅模式),在发生变化的时候自动触发某个函数的执行
使用:例如我们只要在某个表中插入数据时,就删缓存
内置信号:如果是内置信号用起来简单,只需要写个函数,跟内置信号绑定,当信号被触发,函数就会执行
35.Dockerfile用过吗?常永命令有哪些?
Dockerfile是由一系列命令和参数构成的脚本文件,可以不基于容器而直接构件出一个新的镜像。
命令:
FROM centos:7 # 基于centos:7构建出来
MAINTAINER max # 镜像创建者是max
ENV name max # 设置环境变量
RUN ... # 输入命令
WORKDIR 目录 # 创建一个目录,以后执行命令就在这个目录下,并且我们一进来就在该目录下
36.Django提高查询效率对的方法有哪些?
(1)首先每个app中models中的表不能太多,一般不超过5个
(2)Django中默认惰性查询,只要查询结果是queryset,不打印执行结果,那么就不会走sql语句。我们可以使用only和defer关键字,它们都可以点括号内的名字,only查询括号内的字段不需要走sql语句,查括号内没有的字段需要走sql语句。defer刚好相反,查括号内有的字段会走sql语句,查括号内没有的字段会走sql语句。
(3)使用select_related对一对一和一对多的外键关系进行查询。第一次查询的时候它会查询到包含外键的所有字段信息,并且很消耗性能。但是之后的查询就不走数据库。
37.什么是并发、并行?
并发:同一时间段内,执行多个任务的能力
并行:同一时刻执行多个任务的能力,必须要多cpu执行
如何实现并发?
从操作系统层面来说,是通过多线程,从程序层面来说是通过协程
38.什么是同步、异步?
同步异步是在程序调用的角度来看:
同步是指一件事一件事的做,只有执行完毕前一个任务,才会执行下一个任务
异步是指:当一个任务开始执行,无需等待该任务完成,就可以切换到另一个任务执行
同步意味着有序,异步意味着无序。
异步举例:例如我们在网页上下载一个文件,当我们开始下载时,不需要等它下载完成,即可执行其他操作。
39.什么是阻塞、非阻塞?
阻塞、非阻塞是从程序执行对的角度来看:
阻塞:程序在等待某个操作完成期间,自身无法继续干别的事,则称该程序在操作上是阻塞的
非阻塞:程序遇到需要等待时,自身可以去干别的事,则称该程序在操作上是非阻塞的
40.什么是IPC,进程间通信方式有哪些?
IPC就是指进程间通信
进程间通信有两种情况:同一台机器上两个进程通信,不同机器上两个进程通信
通信方式:
1.python queue可以做进程间通信
from multiprocessing import Queue,Process
def write(q):
for i in ["a", "b", "c", "d"]:
q.put(i)
print("put {0} to queue".format(i))
def read(q):
while 1:
result =q.get()
print("get {0} from queue".format(result))
def main():
q = Queue()
pw = Process(target=write, args=(q,)) # 定义一个写的进程
pr = Process(target=read, args=(q,)) # 定义一个读的进程
pw.start()
pw.join()
pr.start()
print(q)
if __name__ == '__main__':
main()
'''
运行结果
put a to queue
put b to queue
put c to queue
put d to queue
<multiprocessing.queues.Queue object at 0x000002BF93EA7670>
get a from queue
get b from queue
get c from queue
get d from queue
'''
2.消息队列,可以使用redis(一个进程写进去,另一个进程拿出来)、rebbitmq、kafka
3.通过socket套接字,展现形式1:使用一个框架调用另一个框架的接口(例如我们 使用django框架写orm拿到mysql的数据,这就是通过套接字,一个进程从另一个进程中拿到数据)。展现形式2:RPC调用:远程过程调用(微服务使用较多)
41.什么是正向代理和反向代理?
正向代理:代理的是客户端(FQ就是正向代理,例如国外网站不能访问,我们先访问到一个代理,然后代理去拿到国外网站的数据返回给我们)
反向代理:代理的是服务端(我们发送的请求会被nginx(或者阿帕奇)代理,然后分发给某一台服务器,对于我们来说我们并不知道我们的请求被哪台服务器处理)(nginx在项目上线时用过)
42.黏包现象?
因为我们的tcp协议是流式协议,tcp客户端发送的数据就是一些没有结构的字节流一样流向服务端,多个数据报黏在一起,产生黏包现象。
应对方法:
1.每个包设置结束标志,http协议就采用/r/n作为结束符
2.每个包设置固定大小的头,头中包含包的大小
43.python如何实现并发?
多线程:使得某个线程的IO操作和另一个线程对的CPU计算可以同步执行,避免cpu等待,提高效率
多进程:multiprocess,利用cpu多核的特点,真正的并行的执行任务
异步IO:在单线程利用CPU和IO同时执行的原理,实现函数的异步执行
使用互斥锁对多个线程同时修改数据的操作加锁,保证数据安全
使用消息队列实现不同进程之间的数据通信
使用线程池、进程池管理线程、进程的数量,保证程序执行的效率
44.python数据量大时怎么优化?
1.使用列表生成式或者迭代器替代循环
2.python访问局部变量相较于全局变量消耗资源更少,效率更高,应当减少全局变量使用
3.在使用django的orm时可以利用惰性查询进行优化,使用defer和only减少数据库的压力
4.对于数据量大并且查询频率较高的数据量,可以使用缓存查询
45.http协议详情,http协议版本,http一些请求头?
http协议特点:
(1)基于tcp的应用层协议
(2)无状态无连接,不会做会话保持,所以才出现了cookie
(3)基于请求响应,服务端不能主动发信息给客户端,于是才出现了websocket
协议详情:
(1)请求协议:
请求首行:请求方式(get,post(天然接口不幂等),retrieve,delete,update);请求地址:请求http版本号
请求头:key-value形式,具体的请求头:cookie,useragent(浏览器相关信息),referer(请求来自于哪个页面),x-forward-for(拿到客户端真实的IP);
请求体:编码方式
(2)响应协议:
响应头:响应状态码、跨域问题(如果是简单请求,我们可以在响应头中添加键值对:{'Access-Control-Allow-Origin':'发起请求的页面'})
响应体:html格式、json格式
协议版本:
(1)0.9:最初版本,仅支持GET请求
(2)1.0:每次tcp连接客户端只能发送一个请求,服务端响应之后就会关闭这次连接,下次请求需要重新建立tcp连接,也就是不支持keep-alive
(3)1.1:引入持久连接,也就是默认不关闭tcp连接,一次连接可以被多个请求复用,不用声明keep-alive,客户端和服务端发现对方一段时间没有活动,就会主动关闭连接(主流还是1.1)。
(4)2.0:引入了多路复用,在1.1版本中即时开启了长连接,请求发送也是串行发送的,对带宽的利用率不够。2.0采用了多路复用的方式,可以并行的发送多个请求。
(5)3.0:3.0版本弃用了TCP协议,使用了UDP协议的QUIC协议来实现。
46.get请求和post请求的区别?
(1)post请求更安全,请求的信息不会作为url的一部分,不会保存在浏览器浏览记录中
(2)get请求有url长度的限制,post发送的请求更大
(3)post请求能发送更多的数据类型,get请求只能发送ASCII字符
(4)post请求速度比get请求更慢
(5)post用于修改和写入数据,get一般用于搜索排序和筛选之类的操作
47.websocket协议?
websocket是一种通信协议,区别于http请求,可在单个tcp连接上进行双工通信,允许服务端主动向客户端推送数据。浏览器和服务器只需要完成一次tcp连接,两者之间就可以建立持久性的连接,并且进行双向数据传输
使用场景:只要涉及服务端主动给客户端发送信息,就需要使用到wedsocket,例如:实时聊天、服务端发送变化,主动向客户端发送通知(流程审批)
使用:django中使用channles模块实现
https://zhuanlan.zhihu.com/p/371500343
48.乐观锁和悲观锁?
乐观锁和悲观锁它们都是一种思想,都是人们定义出来的概念,和语言无关
并发控制:当程序出现并发的问题时,我们需要保证在并发情况下数据的准确性,以保证当前用户在和其他用户一起操作时,得到的结果和他单独操作时得到的结果是一样的,没有做好并发控制,就可能导致脏读、幻读、不可重复读等问题
悲观锁:当要对一条数据进行修改时,为了避免数据同时被其他人修改,最好的办法,最好的方法就是直接对该数据进行加锁,让并行变成串行,其他线程想要访问数据时需要阻塞挂起,互斥锁就属于悲观锁
乐观锁:总是假设最好的情况,每次拿数据都认为别人不会修改,所以不会上锁,只在更新的时候会判断以下在此期间别人有没有更新这个数据,如果更新了,我们就不会修改
乐观锁的实现:
CAS:即比较并替换,是解决多线程并行情况下使用锁造成性能损耗的一种机制。CAS 操作包含三个操作数——内存位置(V)、预期原值(A)和新值(B),执行CAS操作的时候,将内存位置的值与预期原值比较,如果相匹配,那么处理器会自动将该位置值更新为新值,否则,处理器不做任何操作。CAS会出现ABA的问题:比如我们准备对一个数据进行修改,这个数是1,在准备修改和提交的这段时间内,另一个事物已经将数字修改成了2,然后再修改为1,那么虽然我准备修改的数字是1,但是已经不是原来的那个1了。
解决ABA问题,我们需要对数据增加一个版本控制字段,只要有人动过这个数据,就将版本增加,修改一次版本是1,在修改一次版本是2,就说明这个数字有人动过。
版本号控制就是典型的CAS的应用:在数据表中增加一个版本号字段,每次更新数据时将版本号加1,同时将当前版本号作为更新条件,如果当前版本号与更新时的版本号一致,则更新成功,否则更新失败
使用场景:
并发量:如果并发量不大,可以使用悲观锁解决并发问题,如果并发量非常大的话,悲观锁会产生很严重的性能问题
响应速度:要求响应速度搞,建议使用乐观锁,成功就成功,失败就失败
冲突频率:冲突频率很高建议使用悲观锁
重试代价:重试代价大,建议使用悲观锁
读多写少:推荐使用乐观锁
49.django中整形的数据类型分别表示的范围?
IntegerField:整形(4个字节,32位0或1),表示数字个数:232,表示范:-231-2^31-1
BigIntegerField:整形(8个字节,64位0或1),表示数字个数:264,表示范围:-263-2^63-1
SmallIntegerField:整形(2个字节,16位0或1),表示数字个数:216,表示范围:-215-2^15-1
50.redis的底层的数据?
String的底层是(简单动态字符串)
List的底层是(双向链表和压缩链表)
Hash的底层是(压缩链表和哈希表)
Set的底层是(整数数组和哈希表)
Sorted Set底层(压缩链表和跳表)
51.linux常用命令:
ps aux:查看进程
netstat nlp |grep 端口号:查看端口占用情况
kill -9 进程号:强制停掉进程
查看内存:cat /proc/meminfo、free -h
ping:查看与服务端的连接状态
echo:将文本写入文件
hostname -l:显示网络IP地址
ip addr show:显示IP地址
ifconfig:显示虚拟机IP地址
52.xss攻击是什么?
XSS攻击通常指的是通过利用网页开发时留下的漏洞,通过巧妙的方法注入恶意指令代码到网页,使用户加载并执行攻击者恶意制造的网页程序
53.celery异步任务执行的流程?
1.创建一个 Celery 实例,填入关键三个参数:broker, backend, include
2.在 include 中注册 task 文件,在该文件内编写任务,使用 Celery 实例装饰(如:@app.task)
3.使用 delay() 函数,将异步任务推入 Celery 任务队列。
4.当推入队列的异步任务被处理时,Celery 会去寻找可用的 worker 来运行任务。
5.任务在 worker 上执行完成后,worker 会将结果存储到 backend(如 Redis 或数据库等)。
6.访问另一个接口,获取该结果。
总之,异步任务的执行流程如下:创建任务 -> 推入任务队列 -> 执行任务 -> 返回结果 -> 查询结果
54.字典查询10调数据和10万条数据有什么区别?
1.时间延迟:查询10条数据通常会在几毫秒内完成,而查询十万条数据可能需要几秒钟或几分钟的时间。
2.内存占用:十万条数据将比10条数据占用更多内存,特别是如果数据集非常庞大时。
3.索引维护:当数据量增加时,字典的索引维护可能会比较困难,并需要使用更复杂的算法。
4.查询精度:查询更多的数据通常会带来更高的查询精度和准确性。
5.搜索结果:当查询十万条数据时,在结果中往往包含更多的相关内容,对于用户来说更有价值。
6.加载速度:加载十万条数据通常需要更长的时间,这可能会影响用户的体验
55.linux中find 与 grep 的区别?
如果你需要搜索文件系统中的文件和目录,那么使用 find 是更合适的选择。
如果你需要在文件中搜索文本,那么使用 grep 更加适合
56.mysql当中的索引什么时候会失效?
1.违反最左前缀法则
如果索引有多列,要遵守最左前缀法则
即查询从索引的最左前列开始并且不跳过索引中的列
2.在索引列上做操作:计算、函数、自动手动转换类型
3.使用不等号:!= <>
4.like以通配符开头('%abc')
- python中的>>和<<是什么意思?
>>
相当于前面的数除以2的后面的数次方
<<
相当于前面的数乘以2后面的数的次方
print(100>>3) # 相当于100除以2的三次方
print(3<<3) # 24
print(5<<3) # 40
58.僵尸进程,孤儿进程
僵尸进程是当子进程比父进程先结束,而父进程又没有回收子进程
在回收僵尸进程之前, 如果父进程结束了,则僵尸进程变为孤儿进程,进而被init进程接管、回收
59.mysql的存储引擎,它们有什么区别:
MyISAM:MySQL5.5之前默认的存储引擎,特点:存储速度快,但是功能较少,安全性较低。
(1)InnoDB支持事务,MyISAM不支持,对于InnoDB每一条SQL语言都默认封装成事务,自动提交,这样会影响速度,所以最好把多条SQL语言放在begin和commit之间,组成一个事务;
(2)InnoDB支持外键,而MyISAM不支持
60.什么是经典类和新式类?
经典类:不继承object及其子类的类
新式类:继承object及其子类的类
python2中有经典类和新式类,python3中只有新式类,所有类都默认继承object
61.类变量和实例变量的区别?
类变量中的对象是共有的,实例变量中的对象是私有的。如果一个对象点类名当中的属性修改之后,那么所有对象点这个属性都会拿到修改后的值。当实例的属性被修改后,改变的只有这个实例的值。
62.django orm如何优化?
(1)给字段添加索引
(2)在查询大量数据,并且只需要查询一次时使用QuerySets的iterator(),不会产生缓存
(3)在允许的情况下一次性取出所有数据,使用defer和only
(4)统计数量时用QuerySet.count()代替len(queryset)
(5)在判断queryset时,在queryset末尾点exists()结果会变成一个布尔值。有值是True,没有值是False,执行效率高于if判断
(5)queryset.update()或者delete()可以一次性处理多条数据,无需一条条处理
(6)如果批量插入多条数据,可以使用bulk_create()
(7)一对一和一对多使用select_related(),多对对使用prefetch_related()
63.通过import导入模块的查找顺序:
内建模块>>>当前路径>>>环境变量>>>python的安装路径
64.django中的session的底层实现原理?
设置session:request.session['k1'] = 'xxx'
获取session:request.session.get('k1')
设置session之后底层原理:
django内部产生一个随机字符串,操作django_session表(就是存session的地方),给客户端返回一个名为session_id的随机字符串cookie。
在中间件SessionMiddleware的process_response()方法内,
首先取出request.session的modify属性,判断是否是true,如果是true,表示在视图函数中修改过session,数据库同步修改;
如果是false,就不修改,返回给前端sessionid:随机字符串;
请求来的时候:
自动从浏览器请求中获取sessionid对应的随机字符串
拿着该随机字符串去django_session表中查找对应的数据
如果比对上了,则将对应的数据取出并以字典的形式封装到request.session中;如果比对不上 则request.session.get()返回的是None
在中间件SessionMiddleware的process_request()方法内,
通过sessionid获取水机字符串,拿这个随机字符串查找存放session的数据库表,
将查找到的session字典结果存放在request.session中
session_key = request.COOKIES.get(settings.SESSION_COOKIE_NAME)
request.session = self.SessionStore(session_key)
65.reduce和map怎么使用?
reduce的工作过程是 :在迭代序列的过程中,首先把 前两个元素(只能两个)传给 函数,函数加工后,然后把 得到的结果和第三个元素 作为两个参数传给函数参数, 函数加工后得到的结果又和第四个元素 作为两个参数传给函数参数,依次类推。reduce函数中有两个参数,第一个是运算的法则,第二个参数是函数或者迭代器:
reduce导入:from functools import reduce
使用1:求列表中所有参数的和:
from functools import reduce
list = [1, 2, 3, 4, 5, 6]
def sum(x, y):
return x + y
all = reduce(sum, list)
print(all) # 21
使用2:求10的阶乘:
from functools import reduce
def index(x, y):
return x * y
all = reduce(index, range(1,11))
print(all) # 3628800
map()方法会将 一个函数 映射到序列的每一个元素上,生成新序列,包含所有函数返回值。也就是说序列里每一个元素都被当做x变量,放到一个函数f(x)里,其结果是f(x1)、f(x2)、f(x3)......组成的新序列:
items = [1, 2, 3, 4, 5]
def f(x):
return x**2
squared = list(map(f, items))
print(squared)
66.python当中如何理解一切皆对象?
(1)对象是由类产生的,也就是说每一个对象都有产生其的类,所以我们可以通过print(type(数据))来查询该对象的父类
(2)对象可以使用类当中的属性。所以Python当中的数据类型有很多内置方法,这些内置方法其实就是类当中的一些方法
(3)我们自己定义的类,它的父类默认是type,说明我们自己定义的类也是一个对象
(4)很多数据的父类都是type,type是Python中内置的元类,我们继续print(type(type)),结果依然是type,也可以理解为type也是type的一个对象
67.python中的type和object有什么关系?
首先查看一个类的父类,可以用双下bases,查找一个类由什么实例化而来,用双下class
class Student:
pass
class Student1(Student):
pass
print(Student1.__class__) # <class 'type'>
print(Student1.__bases__) # (<class '__main__.Student'>,)
-1.可见任何类都是由type实例化得来
然后我们再用这两个方法测试type:
print(type.__class__) # <class 'type'>
print(type.__base__) # <class 'object'>
-2.可见type是由type本身实例化而来的,而它的父类却是object
然后再测试type:
print(object.__class__) # <class 'type'>
print(object.__base__) # None
-3.可见object没有父类,它是类中最顶端的存在。但是object却是由type实例化得来的
综上:可以的得到结论:
(1)所有类(包括object和type)都是由 type实例化得来的
(2)type的父类是object,object的无父类,object是类中最顶端的存在
68.python线程如何保证运行安全?
(1)使用互斥锁:即使有GIL锁的存在,也需要使用互斥锁,因为GIL锁只是保证正在执行的线程中的数据不被垃圾回收机制回收,并不能保证数据安全
from threading import Lock
(2)使用队列:
多个线程访问同一个资源,访问顺序不确定,可以使用队列来保证线程安全问题。多个线程可以通过put()和get()方法向队列中添加和获取元素
import queue
q = queue.Queue()
q.put(item)
item = q.get()
(3)使用信号量(Semaphore):信号量可以控制同时访问共享资源的线程数量,从而避免线程安全问题。
69.django是如何保证运行安全的?
(1)跨站请求伪造:为了防止请求页面不是恶意请求,需要添加csrf唯一标识。csrf保护的工作原理是检测每个POST请求的随机数,确保了恶意用户不能随意发送恶意请求。
(2)sql注入保护:当我们用django写原生sql校验时,例如校验用户名和密码,如果用户名存在,并且后面加了--(sql当中的注释),那么同样会校验通过。所以我们在django使用原生sql时需要使用cursor.execute(原生sql语句,(username, userpwd))来防止sql注入问题
(3)django可以通过请求头中的referer拿到是哪个页面发送了请求,通过限制策略可以保护用户的隐私
70.drf中有哪些视图类?
(1)View:视图类在路由系统中会.as_view(),在View中有一个as_view()方法,该方法的返回值是view。所以类名点as_view()相当于类名点view。在as_view()方法中有一个view方法,该方法的返回值是self.dispatch(request, *args, **kwargs),self是视图类的对象,在View类中可以找到dispatch方法,在dispatch方法中通过反射(handler = getattr(self, request.method.lower(), self.http_method_not_allowed))拿到了视图类中的get()方法和post()方法。所以我们在继承View类写接口时候需要在类中定义get()或post()或者其他需要的方法。
(2)APIView:APIView继承了View。类名点as_view(),as_view()方法在APIView中可以找到,并且返回了屏蔽csrf校验的view方法。view方法依旧是View中的闭包函数view,返回dispatch,但是这次的dispatch不在View中而是在APIView中就可以找到,在这里进行三大认证并且封装了最新的request,然后再去视图类中通过反射拿到get()和post()方法等。
71.如何自定义异常类?
写一个类继承Exception,重写init方法
class NameDoesNotExist(Exception):
def init(self,message,code):
self.code=code
super().init(message)
72.文件操作怎样控制光标移动?
seek
73.vim的三种模式有哪些?
命令模式、输入模式、编辑模式
74.python代码如果部署在用户的服务器上,如何保证代码不泄露?
(1).代码运行起来之后删除项目(代码运行起来运行在内存中,只要不关机就不会停止服务)
(2).pipinstaller运行成可执行文件
75.以下代码执行结果是什么?
def multipliers():
return [lambda x:i*x for i in range(4)]
print([m(2) for m in multipliers()])
multipliers函数的返回值是一个列表,套了四个匿名函数:
[lambda x:i*x,lambda x:i*x,lambda x:i*x,lambda x:i*x]
这里的i暂时不能直接带进去,因为匿名函数还没执行,需要将执行时的i带进去运算
此时的m是4个匿名函数:lambda x:i*x,此时的i是3,m(2)运算的结果是一个列表:
[6,6,6,6]
76.继承和混入的区别是什么?
(1)混入类功能应该通用并且单一,并且每个混入类只实现一种功能,按需继承。继承的功能可以按照父类的实际需要编写,不仅限于个别功能
(2)混入类自身并不进行实例化,仅提供某方面的特定行为,仅用作子类继承。而继承类本身可以实例化得到对象
(3)混入类只用于拓展子类的功能,而不能影响子类的主要功能,子类也不能依赖混入类。而子类可以全部使用继承类中的方法,继承类可以影响子类的功能