一片文章概括大部分python面试基础常考题(部分有详解)
本片文章部分参考地址:https://segmentfault.com/a/1190000018737045
python是动态解释性的强类型定义语言
强类型:不允许不同类型相加。例如:整形+字符串会报类型错误。
动态:不使用显示数据类型声明,且确定一个变量的类型是在第一次给它赋值的时候。
脚本语言:一般是解释性语言,运行代码只需要一个解释器,不需要编辑。
Python
面向对象OOP
魔法方法:
__init__
用来做变量初始化 或 赋值 操作,在类实例化对象的时候,会被自动调用
__new__
返回父类的new出来的实例self,或直接是object的new出来的实例self–>比init先调用生成实例供init用
__str__
return 一个数据,并且只有self一个参数,当在类的外部 print(对象) 则打印这个数据
__del__
析构函数,当删除对象时,python解释器也会默认调用
python面向对象特性:
封装: 封装就是把类中的属性和方法定义为私有的,python会自动将其转换为_类名__属性名(方法名)的格式,子类无法对进行修改
继承: 关于__mro__
方法解析顺序,新式类是广度优先查找,经典类是深度优先,
多态: python弱化了多态概念:不管对象类型,只要对象有该方法就可执行。关于面向对象的多态理解,建议参考鸭子类型。
------ 鸭子类型: Python在使用传入参数的过程中不会默认判断参数类型,只要参数具备执行条件就可以执行
新式类和经典类:
2.2出现新式类,3全是新式类,新式类是广度优先查找,经典类是深度优先;
重点: 新式类的广度优先解决了经典类的mro
查找忽略同一级别的属性的bug
使用dir()
方法也可以看出新式类中定义很多新的属性和方法
(new,call,dict模块\属性\方法\doc,class对象属于的类,...等
),
而经典类好像就2个(doc文档,module属于哪个模块
):
静态方法和类方法和实例方法
\ | 实例方法 | 类方法 @classmethod传cls | 静态方法@staticmethod不传self和cls |
---|---|---|---|
a = A() | a.foo(x) | a.class_foo(x) | a.static_foo(x) |
A | 不可用 | A.class_foo(x) | A.static_foo(x) |
Python自省(特性)
自省就是面向对象的语言所写的程序在运行时,所能知道对象的类型.简单一句就是运行时能够获得对象的类型.
比如id(),type(),dir(),getattr(),hasattr(),isinstance()
Python元类 type(future_class_name, future_class_parents元祖, uppercase_attr字典)
#返回一个类
创建类的类,元类要继承type,创建一个类的时候,他还没有在内存中生成,直到被调用:
python做了如下操作:依次在—>当前类中–>父类中–>模块层次中–>查看有没有__metacalss__
属性,都没有就会调用内置元类type创建这个类对象。
ORM(对象关系映射)核心:数据描述符和元类
优缺点:
1)只需要面向对象编程, 不需要面向数据库编写代码,对数据库的操作都转化成对类属性和方法的操作.不用编写各种数据库的sql语句.
2)实现了数据模型与数据库的解耦, 屏蔽了不同数据库操作上的差异.不在关注用的是mysql,oracle…等.通过简单的配置就可以轻松更换数据库, 而不需要修改代码.
3)在映射过程中有性能缺失,面向对象编程到sql语句之间的映射需要过程时间,造成性能缺失
定义一个ORM
:
class Field:
pass
class CharField(Field):
'''数据描述符, 好处在于可以在各方法中校验传入值的合理性'''
def __init__(self, col_name, max_length):
if col_name is None or not isinstance(col_name, str):
raise ValueError("col_name must be given as str")
if max_length is None or not isinstance(max_length, numbers.Integral):
raise ValueError("max_length must be given as int")
self._col_name = col_name
self._max_length = max_length
def __get__(self, instance, owner):
# return getattr(instance, self._col_name)
return instance.fields[self._col_name]
def __set__(self, instance, value):
# 这里如果col_name和数据描述符对应的名字一样的话,如name=CharField(col_name="name",10)
# 用setattr(instance, self._col_name, value)即user.name=value会再次进入此__set__方法,导致无限递归
instance.fields[self._col_name] = value
class ModelMetaClass(type):
'''元类'''
def __new__(cls, cls_name, base_class, attrs):
if cls_name == "Model":
return super().__new__(cls, cls_name, base_class, attrs)
fields = {}
for k, v in attrs.items():
if isinstance(v, Field):
fields[k] = v
attrs["fields"] = fields
_meta = {}
attrs_meta = attrs.get("Meta", None)
if attrs_meta is not None and isinstance(attrs_meta, type):
_meta["tb_name"] = getattr(attrs_meta, "tb_name", cls_name)
del attrs["Meta"]
else:
_meta["tb_name"] = cls_name.lower()
attrs["_meta"] = _meta
return super().__new__(cls, cls_name, base_class, attrs)
class Model(metaclass=ModelMetaClass): # 指定要使用的元类
def __init__(self, **kwargs):
self.fields = {}
for k, v in kwargs.items():
setattr(self, k, v)
# def more_func(self):
# pass
class User(Model):
name = CharField(col_name="name", max_length=10)
sex = CharField(col_name="sex", max_length=1)
age = IntField(col_name="age", min_length=1, max_length=10)
class Meta:
tb_name = "User"
设计模式
单例模式:
该模式的主要目的是确保某一个类只有一个实例存在。当你希望在整个系统中,某个类只能出现一个实例时,单例对象就能派上用场。(比如系统回收站)
# __new__方法实现
class Singleton(object):
def __new__(cls,*args,**kw)
if not hasattr(cls,'_instance')
cls._instance = super(Singleton,cls).__new__(cls,*args,**kw)
return cls._instance
# 创建一个类的实例或者继承这个类的就是单例类
# 装饰器方法
def singleton(cls,*args,**kwargs):
instances={}
def getinstance():
if cls not in instances:
instances[cls]=cls(*args,**kwargs)
return instances[cls]
return getinstance
@singleton
class MyClass(object):
pass
# 还有import 一个类的实例,就是天然的单例
工厂模式
核心的思想是: 通过传递给类或函数某种产品的信息来创建产品并返回。当我们想得到产品a对象,只需把产品a的名字传递给工厂函数就能得到产品a对象。
import abc
class A(object):
def __init__(self, product_type):
self.product_type = product_type
def __str__(self):
return 'product %s' % self.product_type
class B(object):
def __init__(self, product_type):
self.product_type = product_type
def __str__(self):
return 'product %s' % self.product_type
class Abstract_factory(object):
__metaclass__ = abc.ABCMeta
@abc.abstractmethod
def yield_product(self):
pass
class Factory_a(Abstract_factory):
def yield_product(self):
return A('A')
class Factory_b(Abstract_factory):
def yield_product(self):
return B('B')
def factory(product_type):
if product_type == 'A':
return Factory_a()
if product_type == 'B':
return Factory_b()
if __name__ == '__main__':
factory_a = factory('A')
a = factory_a.yield_product()
factory_b = factory('B')
b = factory_b.yield_product()
print(a)
print(b)
Python基础
Py2和Py3区别
区别 | py2 | py3 |
---|---|---|
print是一个打印语句 | 函数 可以多个位置的参数 | |
input | raw_input得到str | input得到str |
整除 | 3/2 -->1 但3/2.0–>1.5 | 3//2 得到1 |
range | range(4)–>[0,1,2,3]列表 | range(4) 得到可迭代对象(type是对象) |
xrange | xrange(4)–>生成器 | 只有range |
unicode | 默认ASC II ,unicode() 是单独的 | 源码文件默认使用utf-8编码 |
nonlocal关键字 | 函数内变量就是局部变量 | 可声明为非局部变量,可以在外使用 |
True.False | 视为全局变量,随意赋值 | 变成关键字,指向固定对象,不能重新赋值。 |
Python中的list
是分离式的元素外置顺序表,表头区,元素区存id地址,数据区存数据可以不连续的内存
dict底层结构
-
为了支持快速查找使用了哈希表作为底层结构
-
哈希表平均查找时间复杂度为o(1)
-
CPython解释器使用二次探查解决哈希冲突问题
字符串处理方法
mystr.find(str, start=0, end=len(mystr)) # 有返回下标索引==无返回-1
mystr.strip() # 删除两边空白字符
mystr.splitlines() # 按行分隔成列表
mystr.split(",", 1) # 默认全部分割,1代表分割第一个
a.isalnum # 判断全是字母数字
"_".join(list) #每个元素后面加一个字符串_变成str
赋值、浅拷贝和深拷贝
-
直接赋值:其实就是对象的引用(别名)。=
-
浅拷贝(copy):拷贝父对象,不会拷贝对象的内部的子对象。切片,工厂函数list(), copy.copy()
-
深拷贝(deepcopy): copy 模块的 deepcopy 方法,完全拷贝了父对象及其子对象。
python函数式编程map,filter,reduce
# 高阶函数--》函数的参数能够接收别的函数。
map(lambda x: x * x, [1,3,4])) # 每个数都平方得到一个新迭代器
filter(lambda x:x%2==1,[1,2,3,4,5,6,7]) # 过滤掉列表中的奇数,第二个参数是可迭代对象或者迭代器
reduce(lambda x, y: x+y, [1,2,3,4,5]) # 计算列表和:1+2+3+4+5
sorted([36, 5, -12, 9, -21], key=abs[,reverse=True]) # 按绝对值大小排序
[3,4,2].sort(key=lambda x : x+1) # 对原列表更改
类装饰器:要实现init和call方法
闭包装饰器:外函数返回内函数的引用,内函数使用外函数的局部变量并调用被装饰函数
def log(func):
@functools.wraps(func) # 让now函数的名字还是原来的__name__
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper
@log # 把@log放到now()函数的定义处,相当于执行了语句:log = log(now)
def now():
print('2015-3-25')
# 装饰器传参
def log(text):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print('%s %s():' % (text, func.__name__))
return func(*args, **kw)
return wrapper
return decorator
@log('execute') # 相当于执行 now = log('execute')(now)
def now():
print('2015-3-25')
垃圾回收机制
-
(主)引用计数(reference counting): 引用计数为0时,该对象生命就结束了。维护引用计数消耗资源,循环引用L.append(L) L一直不回收
-
(辅)标记清除机制(mark and sweep): 不改变真实引用计数,复制一份,如果A引用了B,将B计数-1,B也引用了A,将A也-1,这样就显示出了真是的引用计数
-
(辅)分代计数: 将系统中的所有内存块根据其存活时间划分为不同的集合,每一个集合就成为一个“代”,垃圾收集的频率随着“代”的存活时间的增大而减小。也就是说,活得越长的对象,就越不可能是垃圾,就应该减少对它的垃圾收集频率。那么如何来衡量这个存活时间:通常是利用几次垃圾收集动作来衡量,如果一个对象经过的垃圾收集次数越多,可以得出:该对象存活时间就越长。
面试回答: Python的GC模块主要运用了“引用计数”来跟踪和回收垃圾。在引用计数的基础上,还可以通过“标记-清除”解决容器对象可能产生的循环引用的问题。通过“分代回收”以空间换取时间来进一步提高垃圾回收的效率。
sys模块几个常用方法
- argv 命令行参数list,第一个是程序本身的路径
- path 返回模块的搜索路径
- modules.keys() 返回已经导入的所有模块的列表
- exit(0) 退出程序
OS模块几个常用方法
os.system(command) #函数用来运行shell命令
os.path模块:
abspath(path) #返回绝对路径
basename§ #返回路径的最后一个/后面的内容
dirname(file) #返回当前文件/的目录名 __file__也可以目录
exits(path) #测试一个目录存在与否
isabs(s) #判断一个路径是否是绝对路径
isdir(s) #判断是不是目录
isfile(path) #判断是不是文件
join(‘路径’,‘路径/文件’) #os.path.join(os.path.dirname(BASE_DIR), “logs/meiduo.log”)
globals/locals(可以变相操作代码)
- globals中保存了当前模块中所有的变量属性与值
- locals中保存了当前环境中的所有变量属性与值
实现从1-100每三个为一组分组
print([[x for x in range(1,101)][i:i+3] for i in range(0,100,3)])
单元测试
import unittest
class MyTest(unittest.TestCase): # 继承自TestCase 单独运行文件
# 测试开始的方法
def setUp(self):
print("setup")
#测试方法 必须test_开头 可以多个 光标放在哪个函数的内部,就执行哪个测试方法
def test_login(self):
print("test_login")
# 测试结束的方法
def tearDown(self):
print("teardown")
GIL全局解释器锁
-
同一时间只能有一个线程执行,竞争的是cpu内存资源,CPython的特点,其他解释器不存在
-
cpu密集型:多进程+进程池
-
io密集型:多线程/协程
- **释放:**GIL会根据执行的字节码行数以及时间片释放GIL,GIL在遇到io的操作时候主动释放
tip:什么是Cpython==将python解释成C代码工具
进程,线程,协程,锁
- 进程:系统资源分配的最小单位, 一个运行的程序(代码)就是一个进程,进程拥有自己独立的内存空间,所以进程间数据不共享,开销大
- **进程做并发处理:**Linux系统函数
fork()
可以在父进程中创建一个子进程,这样的话,在一个进程接到来自客户端新的请求时就可以复制出一个子进程让其来处理,父进程只需负责监控请求的到来,然后创建子进程让其去处理,这样就能做到并发处理。
- **进程做并发处理:**Linux系统函数
- 线程:调度执行的最小单位, 依赖进程存在. 一个进程至少有一个线程,叫主线程,而多个线程共享内存(数据共享,共享全局变量),从而极大地提高了程序的运行效率.
- 协程:用户层面的轻量级的线程,由程序员根据需要自己调度。我们把一个线程中的一个个函数叫做子程序,那么子程序在执行过程中可以中断去执行别的子程序;别的子程序也可以中断回来继续执行之前的子程序,这就是协程。
- 协程包gevent有个monkey.patch_all()补丁,让gevent框架识别耗时操作,比如:time.sleep,网络请求延时
锁
-
线程互斥锁:当多个线程同时修改同一条数据时可能会出现脏数据,产生了互斥锁保证同一时刻只有一个线程访问数据
threading.Lock()
-
线程安全:因为多线程是共享系统资源的,有了互斥锁,每个线程对数据进程操作都能得到正确的结果
避免死锁
死锁:2个线程互相等待对方的锁,互相占用着资源不释放, 某一个线程没有正常释放锁
利用上下文管理器with 自动申请并释放锁
with在执行时需要这两个方法:__enter__与__exit__
比如open就有
进程与线程的使用场景
- 多进程适合在CPU密集型操作(CPU指令比较多, 如位数多的浮点运算).
- 多线程适合在IO密集型操作(读写数据操作较多的, 比如爬虫)
**进程间通讯 **multiprocessing中的Manager类和Pipe类和Queue类,进程池用Manager().Queue(10)
-
Manager().dict()
progress_dict = Manager().dict() # 传给子进程任务函数
-
Pipe(适用于两个进程) 管道
recevie_pipe, send_pipe = Pipe() # 传给接受和发送的任务函数
-
Queue(10) 消息队列
queue = Queue(10) # 传给子进程 queue.get(),queue.put()
unix进程间通信方式(IPC)
-
管道pipe:两个亲戚进程
-
命名管道named pipe:允许不是亲戚的进程间通信
-
信号signal:信号是比较复杂的通信方式,用于通知接受进程有某种事件发生
-
消息队列message: 息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺
-
共享内存、使得多个进程可以访问同一块内存空间,是最快的可用IPC形
-
内存映射:内存映射允许任何多个进程间通信,每个进程通过把一个共享的文件映射到自己的进程地址空间来实现它
-
信号量: 主要作为进程间以及同一进程不同线程之间的同步手段。
-
套接口: 更为一般的进程间通信机制,可用于不同机器之间的进程间通信。
生成器和迭代器
- 实现
__next__
和__iter__
方法的对象就是迭代器- 可迭代对象只需要实现__iter__方法
- 使用生成器表达式或者yield的生成器函数(生成器是一种特殊的迭代器)
- 节省资源,在用户需要访问数据的时候 才延迟产生,
- yield会记录代码停止的地方,从哪里跌倒从哪里爬起来,send(None给yield传参), next(gen)
编程题
对字典列表按年龄进行倒序排序d_list = [{'name':'a','age':18},....]
d_list.sort(key=lambda x:x['age'],reverse = True)
如何将一个可迭代对象的每个元素变成一个字典的所有键?
{}.fromkeys(['jim','han'],21) # output:{'jim': 21, 'han': 21}
一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
fib = lambda n: n if n <=2 else fib(n-1) + fib(n-2)
实现删除一个 L1=[1,2,3,3,2]里面的重复元素
L2 = list(set(L1))
L2.sort(key = L1.index)
# 使用列表生成时 append
L2 = []
[L2.append(i) for i in L1 if i not in L2]
合并两个有序列表
def loop_merge_sort(l1, l2):
tmp = []
while len(l1) > 0 and len(l2) > 0:
if l1[0] > l2[0]:
tmp.append(l2[0])
del l2[0] # 列表删除时间复杂度大
else:
tmp.append(l1[0])
del l1[0]
tmp.extend(l1)
tmp.extend(l2)
return tmp
# 不删除元素,时间复杂度低
def text(listone,listtwo):
join_list=[]
i,j=0,0
while True:
if len(listone) <=i:
join_list.extend(listtwo[j:])
break
elif len(listtwo)<=j:
join_list.extend(listone[i:])
break
if listone[i]>= listtwo[j]:
join_list.append(listtwo[j])
j+=1
else:
join_list.append(listone[i])
i+=1
return join_list
linux基础
多路复用io
其实所有的I/O都是轮询的方法,只不过实现的层面不同罢了.
python的select模块,提供了:select、poll、epoll三个方法,分别调用系统的 select,poll,epoll 从而实现IO多路复用。
-
select
- 连接数受限
- 查找配对速度慢
- 数据由内核拷贝到用户态
-
poll
- 比select提高链接数的并不多,改善第一个缺点
-
epoll
-
适用于连接数量较多,但活动链接数少的情况 改善select三个缺点
说一些你比较常用linux指令
-
ls/ll、cd、cat、tree、touch、mkdir、rm-rf、cp、mv、ps -aux| grep xxx、kill -9、top、tar -xvf file.tar
权限 ls -l
chmod u=rw, g=x, o=r test.py
搜索文本grep grep -n ‘^a’ file.txt
查找文件:find find ./ -name ‘*.sh’
远程登录ssh 用户名@IP
SSH(openssh-server) 是专为远程登录会话和其他网络服务提供安全性的协议。常用于远程登录,以及用户之间进行资料拷贝
远程拷贝 (前提是已经安装了openssh)如果是文件夹+-r
scp 本地文件名 远程用户名@远程ip:文件
本地上传到远程
scp 远程用户名@远程ip:远程文件 本地文件名
复制远程到本地
-
FileZilla 软件
可以通过图形化操作的方式进行远程主机的文件上传和下载.
vim(vi)编辑器
有命令模式、输入模式、末行模式三种模式。
命令模式:查找内容(/abc、跳转到指定行(20gg)、跳转到尾行(G)、跳转到首行(gg)、删除行(dd)、插入行(o)、复制粘贴(yy,p)
输入模式:编辑文件内容
末行模式:保存退出(wq)、强制退出(q!)、显示文件行号(set number)
在命令模式下,输入a或i即可切换到输入模式,输入冒号(:)即可切换到末行模式;在输入模式和末行模式下,按esc键切换到命令模式
排序算法
快速排序:不稳定,最优O(nlogn),差O(n*n)
取第一个值为中间值,其他的与之比较,放左边和右边,再对左右两边分别排序
def quick_sort(arr):
if len(arr) < 2:
return arr
mid = arr[0]
less = [i for i in arr[1:] if i <= mid]
more = [i for i in arr[1:] if i > mid]
return quick_sort(less) + [mid] + quick_sort(more)
归并排序:稳定,最优O(nlogn),最差O(nlogn) 产生了多一份的空间
先拆分成最小的单元,然后两两判断合并
def merge(left, right):
L,R = 0, 0 # 先考虑左边一个和右边一个进行比较 添加到新列表中
slist = []
while L < len(left) and R < len(right):
if left[L] <= right[R]:
slist.append(left[L])
L += 1
else:
slist.append(right[R])
R += 1
slist += left[L:]
slist += right[R:]
return slist
def merge_sort(arr):
if len(arr) < 2:
return arr
else:
mid_i = len(arr) // 2 # 分而治之
left = merge_sort(arr[:mid_i])
right = merge_sort(arr[mid:])
return merge(left, right)
插入排序: 稳定,最优O(n),差O(n*n)
认定一个值是有序的,后面的依次浮动比较前一个值,小了就交换,目的是把后面的插入到前面的有序序列
def insert_sort(arr):
for i in range(1,len(arr)):
j = i # 1,2,3,4 ...n-1
while j > 0:
if arr[j] < arr[j-1]:
arr[j], arr[j-1] = arr[j-1], arr[j]
j -= 1
else:
break
# 希尔排序
gap = len(arr) // 2
while gap >0 :
...插入排序里面的1换成gap
gap //= 2
冒泡排序: 稳定, 最优O(n),差O(n*n)
临近的两个值比较,大的放后面,第一遍的时候最后一个正确位置,第二遍时后两个都正确。。。。
def bubule_sort(arr):
for i in range(len(arr)-1,0,-1) # n-1, n-2,... 2, 1
flag = False
for j in range(i):
if arr[j+1] < arr[j]:
arr[j], arr[j+1] = arr[j+1], arr[j]
flag = True
if not flag:
break
选择排序: 不稳定,最优最坏都是O(n*n)
假设最后一个最大,从左到右(除了最后一个)依次与最大值进行比较并交换
def selection_sort(arr):
for i in range(len(arr)-1, 0, -1): # n-1, n-2,... 2, 1
for j in range(i):
if arr[j] >arr[i]:
array[j], array[i] = array[i], array[j]
return array
堆排序[heapq模块] 不稳定,时间复杂度与归并一样 nlogn,nlogn
堆是一颗完全二叉树:除了最后一层之外的其他每一层都被完全填充,并且所有结点都保持向左对齐的树
-
最大堆:任意节点的值不大于其父亲节点的值。
-
最小堆:任意节点的值不小于其父亲节点的值。
-
堆有什么用途?
堆最常用于优先队列以及相关动态问题。
优先队列指的是元素入队和出队的顺序与时间无关,既不是先进先出,也不是先进后出,而是根据元素的重要性来决定的
from heapq import nsmallest
def heap_sort(_list):
return nsmallest(len(_list),_list)
二分查找: 最优O(1),差O(logn)
二分查找必须是有序的序列,
def binary_find(arr, item):
'''非递归''' # 定义头和尾的游标,依次进行判断进行移动游标
first,end = 0, len(arr)-1
while first <= end:
mid = (first + end) //2
if item < arr[mid]:
end = mid
elif item > arr[mid]:
first = mid + 1
else:
return mid
return False
print(binary_find([1,2,3], 1))
def binary_search(alist, item):
'''递归实现'''
if len(alist) == 0:
return False
else:
mid = len(alist)//2
if alist[mid]==item:
return True
else:
if item < alist[mid]:
return binary_search(alist[:midpoint],item)
else:
return binary_search(alist[midpoint+1:],item)
栈与队列
class Stack(object):
"""栈"""
def __init__(self):
self.items = []
def is_empty(self):
"""判断是否为空"""
return self.items == []
def push(self, item):
"""加入元素"""
self.items.append(item)
def pop(self):
"""弹出元素"""
return self.items.pop()
def peek(self):
"""返回栈顶元素"""
return self.items[len(self.items)-1]
def size(self):
"""返回栈的大小"""
return len(self.items)
class Queue(object):
"""队列"""
def __init__(self):
self.items = []
def is_empty(self):
return self.items == []
def enqueue(self, item):
"""进队列"""
self.items.insert(0,item)
def dequeue(self):
"""出队列"""
return self.items.pop()
def size(self):
"""返回大小"""
return len(self.items)