InterviewQS
Python基础
list和tuple的区别?
列表是动态的,属于可变序列,可以使用append()、extend()、insert()、remove()和pop()等方法实现添加和修改列表元素,
元组是静态的,属于不可变序列,无法增加、删除、修改元素,除非整体替换
元组比列表更轻量:
1.元组占用空间更小(空的小16字节,列表是动态数组,会额外预留一部分空间,元组空间大小固定)2.元组初始化速度快5倍(python -m timeit “x=(1,2,3)” 和 python -m timeit “x=[1,2,3]”)
3.元组的访问速度快2倍
4.元组可以在字典中作为键
5.元组底层也是数组,但是做了优化(为了避免效率,避免频繁调用系统函数free,malloc向系统释放申请空间,tuple源文件中定义了一个free_list,所有申请过的,小于一定大小的元组,在释放的时候会被放进这个
free_list
中以供下次使用。也就是说,如果以后需要再去创建同样的tuple
,Python 就可以直接从缓存中载入)
可变和不可变对象?
int、float、bool、string、tuple
list、dict、set
检查一个字符串是否仅仅包含数字?
isnumeric() 方法检测字符串是否只由数字组成。这种方法是只针对unicode对象
str = u"this2009";
print(str.isnumeric())
返回一个整数的二进制?
bin(3)
检查一个字符串是否仅仅包含字母?
str.isalpha()
检查字符串是否只包含数字和字母?
str.isalnum()
remove、del和pop有什么区别?
remove 删除第一个匹配的值。
del按索引删除元素。
pop 按索引删除一个元素并返回该元素。
列表随机化?
>from random import shuffle >x = ["keep", "the","me", "fighting"] >print(shuffle(x))
is和==区别?
is运算符比==速度快
==:比较的是两个对象的值
is:比较的是两个对象是否是同对象(不会考虑子类)
isinstance:判断两个类型是否相同(isinstance),类似 type
diff:
type:不会认为子类是一种父类类型,不考虑继承关系
isinstance:会认为子类是一种父类类型,考虑继承关系
浅拷贝和深拷贝以及赋值区别?
深拷贝⛳:复制对象后并创建了一个新对象,内存地址不一样(无论是否嵌套,都是一个独立对象),改变其中一个值不影响另一个.
浅拷贝和copy,切片🎃:分两中情况
内置类型的构造方法或[:]以及copy.copy做的是浅拷贝
不可变对象时(int,string,tuple):直接原对象引用(和赋值一样),内存地址一样,改变其中一个值不影响另一个(不可变对象:改变值,实际新创建了一个对象)
可变对象时:外层是新对象,内存地址不一样(改变其中一个值不影响另一个),嵌套层是引用同一对象,内存地址一样(嵌套层改变其中一个值,另一个相应改变)
赋值🍳:直接对原对象引用(不可变对象时:改变值,实际新创建了一个对象,不影响,可变对象时:改变其中一个值,外层或嵌套层相应改变)
装饰器是什么?
函数装饰器:
1.装饰器是可调用函数,参数是一个函数,返回另一个函数或可调用对象,用于在源码中标记函数,已某种方式增强函数的行为
2.装饰器在加载模块时立即执行
>def time_calc(func): @wraps(func) def wrapper(*args, **kwargs): start_time = time.perf_counter() result = func(*args, **kwargs) speed_time = time.perf_counter()-start_time return result return wrapper
实例方法,静态方法,类方法区别?
三种方法获取类的属性和变量范围不同
实例方法
定义:第一个参数必须是实例对象,该参数名一般约定为“self”,通过它来传递实例的属性和方法(也可以传类的属性和方法);
调用:只能由实例对象调用。
范围:既可以获取构造函数定义的变量,也可以获取类的属性值
类方法
定义:@classmethod。第一个参数必须是当前类对象,该参数名一般约定为“cls”,通过它来传递类的属性和方法(不能传实例的属性和方法);
调用:类和实例对象都可以调用。
范围: 不能获取构造函数定义的变量,可以获取类的属性值
静态方法
定义:@staticmethod。参数随意,没有“self”和“cls”参数,但是方法体中不能使用类或实例的任何属性和方法;
调用:类和实例对象都可以调用。
范围:不能获取构造函数定义的变量,也不可以获取类的属性
example:如有一个学生类和一个班级类
功能:执行班级人数增加的操作、获得班级的总人数
这个问题用类方法做比较合适,为什么?因为我实例化的是学生,
但是如果我从学生这一个实例中获得班级总人数,在逻辑上显然是不合理的。
同时,如果想要获得班级总人数,如果生成一个班级的实例也是没有必要的
参数传递是引用传递还是值传递?
参数的传递是赋值传递,或叫对象的引用传递
所有都是对象,新变量和原变量指向相同对象
1、如果对象是可变的,当其改变时,所有指向这个对象的变量都会改变。
2、如果对象不可变,简单的赋值只能改变其中一个变量的值,其余变量则不受影响
闭包?
内函数运用了外函数的临时变量,并且外函数返回值是内函数的引用
def outer(): b = 10 def inner(a): # nonlocal b的值会一直保持 nonlocal b print("b的值=:",b) b +=a print("b+=a的值:",b) print("a+b的值:",a + b) return inner
魔术方法?
#打印输出
__str__
__repr__
__format__
# 删除
__del__
# 是否可callable
__call__
# len(object)时候触发
__len__
# bool(object)时候触发
__bool__
# dir(object)时候触发
__dir__
# 类相关
__init__
__new__
__class__:和type类似查看对象所属类
__dict__:和dir类似,__dict__返回字典,有些对象没有__dict__
__slots__:限制实例有哪些属性
# 迭代器相关
__iter__(self) 定义当迭代容器中的元素的行为
__next__
# 上下文管理器
__enter__(self)
__exit__(self, exctype, excvalue, traceback)
# hash类型(不可变对象)需要实现,对象的生命周期中,散列值是不变的
__hash__
__eq__ 或者__cmp__
# 比较相关
__lt__(self,other) x<y调用x.lt(y)
__le__(self,other) x<=y调用x.le(y)
__eq__(self,other) x==y调用x.eq(y)
__ne__(self,other) x !=y 调用x.ne(y)
__gt__(self, other)x > y 调用 x.**gt(y)
__ge__(self, other)x >= y 调用 x.ge(y)
# 算术运算相关
__add__(self, other) 定义加法的行为:+
__sub__(self, other) 定义减法的行为:-
__mul__(self, other) 定义乘法的行为:*
# 定义当被 int() 调用时的行为(需要返回恰当的值)
__int__(self)
# 定义当被 float() 调用时的行为(需要返回恰当的值)
__float__(self)
# 排序
__cmp__(self,) sorted调用时候
# 找不到键
__missing__
垃圾回收机制和内存管理?
gc.collect()手动出发回收机制
python垃圾回收机制:
>1.引用计数机制为主 >2.标记-清除 mark and sweep:首先标记对象(垃圾检测),然后清除垃圾(垃圾回收) >3.分代收集 generation collection:这种思想简单点说就是:对象存在时间越长,越可能不是垃圾,应该越少去收集 >两种机制为辅的策略 ># 1.引用计数机制:(简单,实时)python里每一个东西都是对象,它们的核心就是一个结构体:PyObject >(1:PyObject是每个对象必有的内容,其中ob_refcnt就是做为引用计数。 >(2:对象有新的引用时,它的ob_refcnt就会增加,当引用它的对象被删除,它的ob_refcnt就会减少 >(3:当引用计数为0时,该对象生命就结束了。 >优点:简单,内存直接释放,实时性高,处理内存回收的时间分摊了 >缺点:维护引用技术消耗资源,循环引用 ># 2.标记清除:基于追踪回收,分为标记阶段和回收阶段,解决了容器对象可能产生的循环引用问题 >1.基本思路是先按需分配,等到没有空闲内存的时候从寄存器和程序栈上的引用出发 >2.遍历以对象为节点、以引用为边构成的图 >(可达的(reachable)对象标记为活动对象,不可达的对象就是要被清除的非活动对象。根对象就是全局变量、调用栈、寄存器。) >把所有可以访问到的对象打上标记,然后清扫一遍内存空间,把所有没标记的对象释放。 >缺点:必须扫描整个内存 ># 3.分带回收:建立在标记-清除基础上,以空间换时间的方法提高了垃圾回收效率 >将内存根据对象存活的时间分为不同的集合(年轻代:0,中年代:1,老年代:2),对应三个链表 >1.新创建的对象分配在年轻代,年轻代链表总数达到上限会触发回收机制,可以回收的对象回收,不能回收的被移到中年代 >2.老年代存活时间最长,甚至整个系统的生命周期
GIL
>GIL:Global interperter Lock全局解释器锁(并不是python的特性,CPython时所引入的概念,python完全可以不依赖GIL) >1.GIL全局解释锁:每个线程在执行过程都要先获取GIL,保证同一时刻只有一个线程运行,目的是解决多线程之间数据完整性和状态同步。 >Python的多线程在多核CPU上,只对于IO密集型计算产生正面效果;而当有至少有一个CPU密集型线程存在,那么多线程效率会由于GIL而大幅下降 >2.因为引用计数管理内存,所以某个对象的引用计数不能被两个线程同时增加和减少,或内存泄露:GIL对线程间共享的所有数据结构加锁可以保证引用计数变量的安全性 >3. GIL锁的释放: >### 1.协同式多任务处理:IO密集型的操作,在较长的或者不确定的时间(IO阻塞,python标准库中所有阻塞性I/O和time.sleep()都会释放),没有运行Python代码的需要,线程便会让出GIL; >### 2.抢占式多任务处理:CPU密集型的操作,解释器运行一段时间就主动释放GIL,这种机制叫间隔式检查(check_interval),每隔一段时间Python解释器就会强制当前线程去释放 GIL而不需要经过正在执行代码的线程允许,这样其他线程便能运行。 >(在python3中,这个时间间隔是15毫秒) >控制多线程同步的原语:Locks、RLocks、Semaphores、Events、Conditions和Barriers(python3.2之后引入),还有Queue,可以继承这些类,实现自己的同步控制原语。 >4.GIL缺陷: 1.抢占式多任务处理(CPU密集型):(每个线程在多个cpu交替执行:cpu调度线程唤醒->去拿GIL->没拿到->在等待:1.线程上下文切换,2.争抢不到GIL会让cpu等待,都浪费cpu时间) 2.Python的每个版本中也在逐渐改进GIL和线程调度之间的互动关系。例如先尝试持有GIL在做线程上下文切换,在IO等待时释放GIL等尝试。 3.但是无法改变的是GIL的存在使得操作系统线程调度的这个本来就昂贵的操作变得更奢侈了 >5.避免GIL(multiprocess替代Thread,每个进程都有独立的GIL) >但是:它的引入会增加程序实现时线程间数据通讯和同步的困难
什么是元类?
元类是制造类的工厂
常用模块?
random,re,sys,os(os.system()),json,time,datetime,hashlib,collections,pillow,threading,multiprocessing,selenium,BeautifulSoup,concurrent,django,pydantic Fastapi
进程和线程的区别?
1.进程是操作系统资源分配的最小单位,而线程是程序执行的最小单位
2.进程之间相互独立,每个进程都有自己的地址空间、数据栈、文件描述符等,而线程共享进程的地址空间和资源
3.进程之间通信需要使用IPC(Inter-Process Communication)机制,如管道、消息队列、共享内存等,而线程之间可以通过共享内存、信号量、互斥量等方式进行通信。
4.进程有(执行入口/顺序执行/执行开销大),线程不能独立运行(依附于进程,执行开销小)
5.键壮性:崩溃不影响其他进程,一个线程崩溃整个进程崩掉
6.进程的创建和销毁需要较大的系统开销,而线程的创建和销毁则比较轻量级
7.切换:进程切换资源消耗大,线程切换消耗小
(进程切换需要切换页表,页表切换后,TLB失效,地址转化时需要重新查找页表。线程切换不需要切换页表)
列表底层原理实现?
Python的列表是一种动态数组,数组中每个元素都一个指向存储真实数据的指针,扩容时会重新分配一块更大的内存空间,并将原来的值复制到新内存空间,这种实现方式使列表的随机访问非常高效,但在插入和删除元素时需要移动大量的数据,效率较低
>typedef struct { PyObejct_VAR_HEAD PyObject **ob_item; Py_ssize_t allocated; >}PyListObject >ob_item:指向列表对象的指针数组 >allocated:申请内存的槽的个数(通常分配的槽的大小大于列表的大小,为了避免每次列表添加元素的时候调用分配内存的函数扩容) >append:追加元素的操作平均复杂度为 O(1) >insert:插入操作平均复杂度为 O(n) >pop:取出最后一个的平均复杂度为 O(1) >remove: 删除指定元素的复杂度为 O(n)
字典底层实现原理?
Python中字典是通过hash表来实现的
>typedef struct { Py_ssize_t me_hash; PyObject *me_key; PyObject *me_value; >}PyDictEntry; >存储内容有 hash,key,value >字典是否有序? >Python3.5以前:字典是不能保证插入顺序的,底层使用一个二维数组 >创建字典: 初始化一个二维数组,8行3列,字典的键值对数量超过当前数组的2/3时,数组会进行扩容 >往字典添加一个值: hash(key)&掩码 或 hash(key)后的值对8取余数,余数为二维数组的索引,二维数组记录该索引(hash值,key的内存地址,vlaue的内存地址) >取值: hash(key)后的值对8取余数,余数为二维数组的索引 >Python3.6后:字典插入有序了,且占用内存空间变小了,底层使用两个一维数组 >创建字典: >indices = [None,None,None,None,None,None,None,None] >entries = 二维数组(hash值,key的内存地址,value的内存地址) >往字典添加一个值: hash后取余后的值为indices上的索引,在该索引上记录entries存值的索引 >插入新的数据只在entries后面添加数据,确保了插入的顺序
设计模式?
创建型模式(5种):工厂方法模式、抽象工厂模式、创建者模式、(原型模式)、单例模式
结构型模式(7种):适配器模式、桥模式、组合模式、装饰模式、外观模式、享元模式、代理模式
行为型模式(11种):解释器模式、责任链模式、命令模式、迭代器模式、中介者模式、备忘录模式、观察者模式、状态模式、策略模式、访问者模式、模版方法模式
参考:设计模式
单例模式?
import 使用模块
使用装饰器
使用new,加锁解决多线程问题
使用元类,metaclass
分布式系统中的单例任务是怎么实现的?
把这个单路模式序列化并存储到外部共享存储区(如Redis),进程在使用这个单例模式时候,读取出来并反序列化成对象
框架
django生命周期?
请求阶段:当用户发送请求时,Django会根据URL路由规则找到对应的视图函数,并将请求传递给该函数处理。
视图函数阶段:视图函数会处理请求,并返回一个HttpResponse对象,该对象包含了要返回给用户的内容。
中间件阶段:在视图函数处理请求之前或之后,Django会执行中间件的相应方法,例如处理请求头、身份验证、缓存等。
模板渲染阶段:如果视图函数返回的是一个模板,Django会将模板渲染成HTML页面,并将其包含在HttpResponse对象中返回给用户。
响应阶段:最后,Django会将HttpResponse对象发送给用户的浏览器,完成请求响应过程。
在整个生命周期中,Django还会执行一些其他的操作,例如数据库查询、缓存读写、日志记录等。
ORM有哪些?
Django orm
Sqlalchemy
Turtoise
数据库
如何优化慢查询?
pg与mysql的区别有哪些?
数据类型:PG支持更多的数据类型,如数组、JSON、XML等,而MySQL则不支持。
ACID支持:PG支持完全的ACID(原子性、一致性、隔离性、持久性)事务,而MySQL只支持部分ACID事务。
复杂查询:PG支持更复杂的查询,如递归查询、窗口函数等,而MySQL则不支持。
存储引擎:MySQL支持多种存储引擎,如InnoDB、MyISAM等,而PG只支持一种存储引擎。
性能:在高并发、大数据量的情况下,PG的性能比MySQL更好。
开源许可证:PG采用的是BSD许可证,而MySQL采用的是GPL许可证
总的来说,PG更适合处理复杂的数据结构和高并发的场景,而MySQL则更适合处理简单的数据结构和小型应用