python面试题总结

1.python多线程:

1.1 不是真正的多线程

python中存在一个全局解释器锁(GIL),在任意时刻只能由一个线程在解释器中运行。因此Python中的多线程是表面上的多线程(同一时刻只有一个线程),不是真正的多线程。

1.2 python多线程使用场景:

适用于io密集型程序。可以让IO堵塞的时间切换到其他线程做其他的任务,很适合爬虫或者文件的操作。

 

2.python内存管理

2.1.垃圾回收

2.1.1 被回收的对象

当Python运行时,会记录其中分配对象(object allocation)和取消分配对象(objectdeallocation)的次数。当两者的差值高于某个阈值时,垃圾回收才会启动,将没用的对象(引用计数为0的对象)清除。阈值查询方法:

import gc
print(gc.get_threshold())  # (700, 10, 10) 700表示启动垃圾回收的阈值,后面两个10分别表示每10次0代垃圾回收,会配合1次1代的垃圾回收;而每10次1代的垃圾回收,才会有1次的2代垃圾回收。

2.1.2 回收策略-分代回收

  • Python将所有的对象分为0,1,2三代;新建的对象为0代对象,经历过一次垃圾回收幸存下来的对象升级为1代对象。经过两次及以上次数的回收幸存下的为2代对象。
  • 垃圾回收启动时,一定会扫描所有的0代对象。如果0代经过一定次数垃圾回收,那么就启动对0代和1代的扫描清理。当1代也经历了一定次数的垃圾回收后,那么会启动对0,1,2,即对所有对象进行扫描。具体经过多少次启动下一代对象垃圾回收可通过以下方法查询
    import gc
    print(gc.get_threshold())  # (700, 10, 10) 700表示启动垃圾回收的阈值,后面两个10分别表示每10次0代垃圾回收,会配合1次1代的垃圾回收;而每10次1代的垃圾回收,才会有1次的2代垃圾回收。

2.1.3 回收策略-标记清除:

 通过“标记-清除”解决容器对象可能产生的循环引用的问题

注:垃圾回收时,Python不能进行其它的任务,频繁的垃圾回收将大大降低Python的工作效率;

2.2 内存分配:内存池

2.2.1 内存池

python分为大内存和小内存。大小以256字节为界限,大内存使用Malloc进行分配,小内存则使用内存池进行分配。

2.2.2 小整数池

Python实现int的时候有个小整数池。为了避免因创建相同的值而重复申请内存空间所带来的效率问题, Python解释器会在启动时创建出小整数池,范围是[-5,256],该范围内的小整数对象是全局解释器范围内被重复使用,永远不会被垃圾回收机制回收。

注:字符串也有类似的机制,较为复杂可参考下面博客

https://www.cnblogs.com/TMesh/p/11731010.html

 

3.单例模式

https://www.cnblogs.com/liuxuelin/p/14483561.html

 

4.Python2和Python3区别

1、Python3 使用 print 必须要以小括号包裹打印内容,比如 print('hi')

Python2 既可以使用带小括号的方式,也可以使用一个空格来分隔打印内容,比如 print 'hi'

2、Python2 range(1,10)返回列表,python3中返回迭代器,节约内存

3、Python2中使用ascii编码,python中使用utf-8编码

4、Python2中unicode表示字符串序列,str表示字节序列

Python3中str表示字符串序列,byte表示字节序列

5、Python2中为正常显示中文,引入coding声明,python3中不需要

6、Python2中是raw_input()函数,python3中是input()函数

7、Python2 中除法默认向下取整,因此 1/2 = 0,为整型。而 Python3 中的除法为正常除法,会保留小数位,因此 1/2 = 0.5,为浮点型。

 

5.python位运算符

 

 6.协程

协程,英文Coroutines,是一种比线程更加轻量级的存在。

协程不是进程,也不是线程,它就是一个可以在某个地方挂起的特殊函数,并且可以重新在挂起处继续运行。所以说,协程与进程、线程相比,不是一个维度的概念。

一个进程可以包含多个线程,一个线程也可以包含多个协程,也就是说,一个线程内可以有多个那样的特殊函数在运行。但是有一点,必须明确,一个线程内的多个协程的运行是串行的。如果有多核CPU的话,多个进程或一个进程内的多个线程是可以并行运行的,但是一个线程内的多个协程却绝对串行的,无论有多少个CPU(核)。这个比较好理解,毕竟协程虽然是一个特殊的函数,但仍然是一个函数。一个线程内可以运行多个函数,但是这些函数都是串行运行的。当一个协程运行时,其他协程必须挂起。

 

7.@property

@property将方法转为属性,该属性为只读属性,只可访问但是不可以修改,使用对象.方法名来访问该属性,但是方法不能再加小括号

 

8.cookie和session

http协议是无状态的。即http协议对事务的处理没有记忆能力。为了弥补这种不足,产生了两项记录http状态的技术,一个叫做Cookie,一个叫做Session。session与cookie属于一种会话控制技术.常用在身份识别,登录验证,数据传输等

cookie

cookie是远程浏览器存储数据以此追踪用户和识别用户的的机制,从实现来说,cookie是存储在客户端上的一个数据片段。

1.客户端向服务端发起一个http请求.
2.服务端设置一个创建cookie的指令,响应给客户端.
3.客户端收到服务端响应的指令,根据指令在客户端创建一个cookie.
4.挡下一次请求时,客户端携带这个cookie向服务端发送请求.

session

1.客户端向服务端发起请求,建立通信
2.服务端根据设置的session创建指令,在服务端创建一个编号为sessionid的文件,里面的值就是session具体的值(组成部分 变量名 | 类型 :长度:值).
3.服务端将创建好的sessionid编号响应给客户端,客户则将该编号存在cookie中(一般我们在浏览器存储的调试栏中会发现cookie中有一个PHPSESSID的键,这就是sessionid,当然这个名称,我可以通过设置服务端是可以改变的).
.当下一次请求时,客户端将这个sessionid携带在请求中,发送给服务端,服务端根据这个sessionid来做一些业务判断.

 

9. 字符串前导r的作用

Python 中字符串的前导 r 代表原始字符串标识符,该字符串中的特殊符号不会被转义,适用于正则表达式中繁杂的特殊符号表示。

最典型的例子,如要输出字符串 \n,由于反斜杠的转义,因此一般的输出语句为:

 print "\\n"

这里的 \\ 将被转义为 \ 。而采用原始字符串输出时,则不会对字符串进行转义:

 print r"\n"

 

10. dict方法总结:

1.fromkeys方法

  • 为dict的一个静态方法,用于根据序列创建一个新字典,key为序列的值,value为None
d = dict.fromkeys(['a', 'b'])
print(d)
e = d.fromkeys(['c', 'd'])
print(d, e)

执行结果:

{'a': None, 'b': None}
{'a': None, 'b': None} {'c': None, 'd': None}

 

11.list方法总结

1.remove()

list.remove(obj)表示移除列表中某个值的第一个匹配项

 

2.+、+=

  • 对于+=操作,如果是可变对象,则操作前后序列的id值不变,如果是不可变对象,则操作前后序列的id值改变
lis = [1,3,2]
a = id(lis)
lis += [4,5]
b = id(lis)
print(a==b)  # True

 

3.sorted(list)

  • 使用sorted()进行排序会生成新的序列,生成的新序列和原序列id值必然不同
lis = [1, 3, 2]
a = id(lis)
lis = sorted(lis)
b = id(lis)
print(a == b)   # False

 4.列表乘法

在python中, 如果用一个列表a乘一个数字会得到一个新的列表b, 这个新的列表b的元素是a的元素重复n次。
a = [1,2]
b = a * 2  # [1, 2, 1, 2]
此外还要注意一点,当a中的元素为引用类型时,如:
a = [[1,2]]
b = a * 2  # b -> [[1, 2], [1, 2]]
 
# 修改b[0][0], b[0][1]同样被修改了, 这是因为b[0]与b[1]有相同的地址
b[0][0] = 3
print(b)  # [[3, 2], [3, 2]]
 
# 打印b[0]和b[1]的 id
print(id(b[0]))  # 1904838110536
print(id(b[1]))  # 1904838110536

 5切片

t1 = (1,2,3)
t2 = t1[:]
print(t1 is t2)  # True

lis1 = [1,2,3]

lis2 = lis1[:]

print(id(lis1)==id(lis2))   # False

Python出于对性能的考虑,但凡是不可变对象,在同一个代码块中的对象,只有是值相同的对象,就不会重复创建,而是直接引用已经存在的对象

6.index()

在Python3中,list.index(obj)表示从列表中找出某个值第一个匹配项的索引位置

 

12.str方法总结

1.upper()、lower()、capitalize()、title()

str.upper() # 把所有字符中的小写字母转换成大写字母

str.lower() # 把所有字符中的大写字母转换成小写字母

str.capitalize() # 把第一个字母转化为大写字母,其余小写

str.title() # 把每个单词的第一个字母转化为大写,其余小写 

a = "abCdEF123tsgtB"
print(a.upper(), a.lower(), a.capitalize(), a.title())

执行结果:

ABCDEF123TSGTB abcdef123tsgtb Abcdef123tsgtb Abcdef123Tsgtb

2.find()

strs.find(str, beg=0, end=len(strs))表示在strs中返回第一次出现str的位置下标,没有则返回-1,beg表示在strs中的开始索引,默认为0,end结束索引,默认为strs的长度

 

3.strip()

将字符串首尾出现的括号里的所有字符都删除 

if __name__ == '__main__':
    strs = 'abbacabb'
    print(strs.strip('ab'))  # 输出:c

 4.count(),index(), in

a.count(b) 获取b在a中出现的次数,没有返回0
a.index(b) 获取b在a中出现的位置,没有会报错value Error
b in a 判断a是否包含b
 

5.center(width[,fillchar])

width 为字符串的总宽度,fillchar为填充字符,该方法返回一个原字符串居中,并使用空格填充至长度width的新字符串。

 

13.可哈希对象

可哈希对象:数字类型(int,float,bool)、字符串str、元组tuple、自定义类的对象

不可哈希对象:列表,集合和字典

 

14.Python 中的Magic Method(魔术方法)或特殊变量

1.__new__()

  • __new__是在实例创建之前被调用的,因为它的任务就是创建实例然后返回该实例,是个静态方法

2.__getitem__()

  • 根据索引访问对象元素,会调用__getitem__(), 如obj[1]
  • 对对象的迭代(for i in obj ...)需要调用__iter__,如果没有定义该方法,python会调用__getitem__(),让迭代和in运算符可用

3.__len__()

  • len(obj)是调用这个方法
  • 如果需要将一个对象变为一个序列,必须实现两个方法:__len__和__getitem__

4.__slots__

  • . __slots__是python类中的特殊变量,用于限制实例对象的属性,如__slots__='x','y',那么实例对象的属性就只能从这些里面找
  • 继承关系中,如果子类没有定义__slots__属性,则不会继承父类的__slots__属性,如果子类定义了__slots__属性,则子类对象允许的实例属性包括子类的__slots__加上父类的__slots_。
class A():
    __slots__ = 'x', 'y'
    def __init__(self):
        self.x = 1
        self.y = 3
        self.ss = 4   # 实例化时候会报错:AttributeError: 'A' object has no attribute 'ss'

    def ab(self):
        print(1)

 

魔术方法汇总:
__new__、__init__、__del__ 创建和销毁对象相关
__add__、__sub__、__mul__、__div__、__floordiv__、__mod__ 算术运算符相关
__eq__、__ne__、__lt__、__gt__、__le__、__ge__ 关系运算符相关
__pos__、__neg__、__invert__ 一元运算符相关
__lshift__、__rshift__、__and__、__or__、__xor__ 位运算相关
__enter__、__exit__ 上下文管理器协议
__iter__、__next__、__reversed__ 迭代器协议
__int__、__long__、__float__、__oct__、__hex__ 类型/进制转换相关
__str__、__repr__、__hash__、__dir__ 对象表述相关
__len__、__getitem__、__setitem__、__contains__、__missing__ 序列相关
__copy__、__deepcopy__ 对象拷贝相关
__call__、__setattr__、__getattr__、__delattr__ 其他魔术方法

 

 

 

14.闭包的延迟策略

执行下列程序,输出结果为()

def fn():

    t = []

    i = 0

    while i < 2:

        t.append(lambda x: print(i*x,end=","))

        i += 1

    return t

for f in fn():

    f(2)

 

A. 4,4,   B 2,2,  C 0,1,   D 0,2,

 

解析

函数fn存在闭包现象,自由变量是i,由于python闭包采用延迟绑定,也就是当调用匿名函数时,循环早已结束,此时i的值为2,所以输出结果都为2*2=4,正确答案为A。

15.元组乘法

a = (1, 2, 3)

a*3 -> (1,2,3,1,2,3,1,2,3)

 

16.算数运算:

1.divmod()

如 divmod(100, 14)  -> (7, 2)

 divmod将除法运算和取余运算结合在一起,结果返回一个tuple(元组)(商和余数)

 

17.__name__

__name__这个系统变量显示了当前模块执行过程中的名称,如果当前程序运行在这个模块中,__name__ 的名称就是__main__如果不是,则为这个模块的名称。

 

18:try...except...else

try:
    result = 1 / 0
    print(1,end=" ")
except ZeroDivisionError:
    print(2,end=" ")
except Exception:
    print(3,end=" ")
else:
    print(4)

执行结果:

2

在try...except...else结构中,当执行try程序块的语句时,若出现异常的语句,则不会继续执行try还未执行的代码,而是直接跳到except程序块,由于0不能作为分母,其抛出的异常对象属于异常类ZeroDivisionError,结果输出2,当异常被处理完时,会直接跳出except程序块,当try程序块没有出现异常时,不会执行except而执行else语句,出现异常时则执行except而不执行else,所以最终输出结果是2。

 

19.类变量和实例变量

类属性可以为实例属性提供默认值,实例属性不会影响到类属性的值,也不会影响到其他实例属性。

class Base(object):

    count = 1

    def __init__(self):
        pass
b1 = Base()
b2 = Base()
b1.count = b1.count + 1 # b1本来没有count,这里会新增一个实例b1的count变量,默认值取类的count的值

print(b1.count,end=" ")

print(Base.count,end=" ")  # 实例的变量不影响类的变量

print(b2.count)  # b1实例的变量修改不会影响b2的实例的变量

输出:

2 1 1

 

20. 可变类型不可变类型

 

21.导入模块的搜索顺序:

  1. 程序的根目录(即当前运行python文件所在的目录)
  2. PYTHONPATH环境变量设置的目录
  3. 第三方扩展的site-package目录
  4. 任何能够找到的.pth文件的内容

上面路径都会加入到sys.path中,具体导入顺序可通过查看sys.path得到:

D:\work\code\test_pro\p_test   # 程序的根目录
D:\apps      # PYTHONPATH所在路径,这个环境变量我自己设置的
D:\apps\JetBrains\PyCharm 2019.1.4\helpers\pycharm_display
D:\work\python\Python38\python38.zip        # 这几个为python标准库目录
D:\work\python\Python38\DLLs
D:\work\python\Python38\lib
D:\work\python\Python38
D:\work\virtualenv\tenantinstance
D:\work\virtualenv\tenantinstance\lib\site-packages      # 第三方包目录
D:\apps\JetBrains\PyCharm 2019.1.4\helpers\pycharm_matplotlib_backend

 

22.python内建对象、内建模块、标准库,第三方库,自定义模块

https://blog.csdn.net/zhouyinlong1990/article/details/122704690

 

23.没有参数的lamdba表达式定义

a = lambda: print(2)
b = lambda i: print(i)
a()   # 2
b("ab") # ab

 

24.全局变量、局部变量

a = 1
num = 2

def t():
    c = a + 1
    num = num + 1  # 会报错:UnboundLocalError: local variable 'num' referenced before assignment

t()

虽然在函数外部声明num 为全局变量,但若函数体内对num变量重新赋值,其结果会使得函数内部屏蔽外面的全局变量num,此时语句num += 1就会抛出异常,即num变量没有先赋值就直接引用了。

 

25.私有属性

私有属性,在类外部使用时,可以通过实例名._类名__xxx来访问。

class Cat:
    def __init__(self,color="白色"):
        self.__color = color

cat = Cat("绿色")
print(cat._Cat__color)

 

 

26.nonlocal、global 关键字的用法:

nonlocal 用来声明外层的局部变量。
global 用来声明全局变量。

a = 100
def outer():
    b = 10
    def inner():
        nonlocal b  # 声明外部函数的局部变量
        print(r"inner b:", b)
        b = 20
        global a  # 声明全局变量
        a = 1000
    inner()
    print(r"outer b:", b)
outer()
print("a :" ,a)

输出:

inner b: 10
outer b: 20
a : 1000

 

27python中主要存在四种命名方式:

1、object #公用方法
2、_object #半保护
                 #被看作是“protect”,意思是只有类对象和子类对象自己能访问到这些变量,
                  在模块或类外不可以使用,不能用’from module import *’导入。
                #__object 是为了避免与子类的方法名称冲突, 对于该标识符描述的方法,父
                  类的方法不能轻易地被子类的方法覆盖,他们的名字实际上是
                  _classname__methodname。
3、_ _ object  #全私有,全保护
                       #私有成员“private”,意思是只有类对象自己能访问,连子类对象也不能访
                          问到这个数据,不能用’from module import *’导入。
4、_ _ object_ _     #内建方法,用户不要这样定义
 

 28.python中的迭代器和生成器

 迭代器:可遍历且可以记住遍历所在位置的对象就是迭代器。

  • 把一个类作为一个迭代器使用需要在类中实现两个方法 __iter__() 与 __next__() 

生成器:使用了 yield 的函数被称为生成器(generator)。

  • 跟普通函数不同的是,生成器是一个返回迭代器的函数,只能用于迭代操作
  • 生成器就是一个特殊的迭代器

 

29. Python中为什么没有函数重载?

首先Python是解释型语言,函数重载现象通常出现在编译型语言中。其次Python是动态类型语言,函数的参数没有类型约束,也就无法根据参数类型来区分重载。再者Python中函数的参数可以有默认值,可以使用可变参数和关键字参数,因此即便没有函数重载,也要可以让一个函数根据调用者传入的参数产生不同的行为。

 

30. __init__和__new__方法有什么区别?

Python中调用构造器创建对象属于两阶段构造过程,首先执行__new__方法获得保存对象所需的内存空间,再通过__init__执行对内存空间数据的填充(对象属性的初始化)。__new__方法的返回值是创建好的Python对象(的引用),而__init__方法的第一个参数就是这个对象(的引用),所以在__init__中可以完成对对象的初始化操作。__new__是类方法,它的第一个参数是类,__init__是对象方法,它的第一个参数是对象。

31. python变量的作用域

 python的作用域一共有四层:局部作用域 L (Local)-->>闭包函数外的函数中 E ( Enclosing )嵌套作用域 -->> 全局作用域  G ( Global ) -->> 内建作用域 B (Built-in)。记成LEGB

 

 32. 闭包

闭包可以理解为能够读取其他函数内部变量的函数。正在情况下,函数的局部变量在函数调用结束之后就结束了生命周期,但是闭包使得局部变量的生命周期得到了延展。使用闭包的时候需要注意,闭包会使得函数中创建的对象不会被垃圾回收,可能会导致很大的内存开销,所以闭包一定不能滥用。

 

33.Mq消息丢失解决方案

https://blog.csdn.net/weixin_43409765/article/details/121283851

 

posted @ 2022-05-05 21:57  foreast  阅读(348)  评论(0编辑  收藏  举报