Python易忽视的面试题及解析(持续更新中......)

通过阅读一些面试题博客,有些问题新手比较容易忽视,却不得不提。特此总结如下。

Python 中新式类和经典类的区别是什么?

在Python 2.x版本中,由任意内置类型派生出的类(只要一个内置类型位于类树的某个位置),都属于“新式类”,都会获得所有“新式类”的特性;反之,即不由任意内置类型派生出的类,则称之为“经典类”。换言之:在python 2.x中,某个类的继承类树中,只要有系统内置的类,就是新式类。

“新式类”和“经典类”的区分在Python 3之后就已经不存在,在Python 3.x之后的版本,因为所有的类都派生自内置类型object(即使没有显示的继承object类型),即所有的类都是“新式类”。

经典类的钻石继承是深度优先,即从下往上搜索;新式类的继承顺序是采用C3算法(非广度优先)。

经典类中是没有__new__方法,只有__init__方法,新式类才有__new__方法。

python中的DocStrings(解释文档)有什么作用?

DocStrings是一个很重要的工具,他可以使你的程序文档更加易懂,你应该尽量使用它,同时,你通过help函数获取的其他函数的文档信息的原理就是这样的只是将这个内容封装在里面了。
格式:在函数下方用一个三引号进行划定,调用的时候用函数.doc
注意doc左右两边都是双下划线。

Python 3 中的类型注解有什么好处?如何使用?

前几天有同学问到,这个写法是什么意思:

def add(x:int, y:int) -> int:
    return x + y

Python 是一种动态语言,变量以及函数的参数是不区分类型。因此我们定义函数只需要这样写就可以了:

def add(x, y):
    return x + y

这样的好处是有极大的灵活性,但坏处就是对于别人代码,无法一眼判断出参数的类型,IDE 也无法给出正确的提示。

于是 Python 3 提供了一个新的特性:函数注解。
也就是开头的这个例子:

def add(x:int, y:int) -> int:
    return x + y

用 : 类型 的形式指定函数的参数类型,用 -> 类型 的形式指定函数的返回值类型。

要强调的是,Python 解释器并不会因为这些注解而提供额外的校验,没有任何的类型检查工作。也就是说,这些类型注解加不加,对你的代码来说没有任何影响。

这么做的好处是:

  • 让别的程序员看得更明白
  • 让 IDE 了解类型,从而提供更准确的代码提示、补全和语法检查(包括类型检查)

什么是上下文?with 上下文管理器原理?

with 语句是 Pyhton 提供的一种简化语法,适用于对资源进行访问的场合,确保不管使用过程中是否发生异常都会执行必要的“清理”操作,释放资源,with 语句主要是为了简化代码操作。

# 创建一个文件test.txt,若存在则打开,写入Hello Python
# 创建/打开文件
f = open('test.txt', 'w')
f.write("Hello Python")
# 关闭这个文件
f.close()
 
 
# 使用with
with open('test.txt', 'w') as f:
    f.write('Python')
  • with:文件使用后自动关闭
  • with的执行过程:
  • 在执行 with 语句时,首先执行 with 后面的 open 代码
    • 执行完代码后,会将代码的结果通过 as 保存到 f 中,
    • 然后在下面实现真正要执行的操作,在操作后面,并不需要写文件的关闭操作,文件会在使用完后自动关闭。
  • with的执行原理:
  • 在文件操作时,并不是不需要写文件的关闭,而是文件的关闭操作在 with 的上下文管理器中的协议方法里已经写好了。当文件操作执行完成后, with语句会自动调用上下文管理器里的关闭语句来关闭文件资源。
    • 上下文管理器中有 enterexit 两个方法,以with为例子,enter 方法会在执行 with 后面的语句时执行,一般用来处理操作前的内容。比如一些创建对象,初始化等;exit 方法会在 with
      内的代码执行完毕后执行,一般用来处理一些善后收尾工作,比如文件的关闭,数据库的关闭等。

什么是全缓冲、行缓冲和无缓冲?

1、全缓冲
。全缓冲指的是系统在填满标准IO缓冲区之后才进行实际的IO操作;注意,对于驻留在磁盘上的文件来说通常是由标准IO库实施全缓冲。调用flush函数冲洗一个流。冲洗意味着将缓冲区的内容写到磁盘上。

2、行缓冲 。在这种情况下,标准IO在输入和输出中遇到换行符时执行IO操作;注意,当流涉及终端的时候,通常使用的是行缓冲。

3、无缓冲 。无缓冲指的是标准IO库不对字符进行缓冲存储;注意,标准出错流stderr通常是无缓冲的。

什么是序列化和反序列化?

1、什么是序列化、反序列化?

  • 序列化就是将内存中的数据结构转换成一种中间格式存储到硬盘或者基于网络传输
  • 反序列化就是硬盘中或者网络传来的一种数据格式转换成内存中数据结构。

2、Python中有两种序列化命令

  • json、pickle
  • 区别:
    • pickle是Python独有的,
    • json不能序列化函数,pickle能序列化函数
  • 使用:
    • 第一组:文件 —— 持久化存储
      • dump/load
    • 第二组:字符串 —— 用于传输 (重要)
      • dumps/loads

3、JSON 中 dumps 转换数据时候如何保持中文编码?
Python 3中的json在做dumps操作时,会将中文转换成unicode编码,并以16进制方式存储,再做逆向操作时,会将unicode编码转换回中文。

在python3中,ensure_ascii=False就解决了问题

dict = {'name': '中文'}
s = json.dumps(dict, ensure_ascii=False)

s输出就是正常的中文。

Python 中的字符串格式化的方法有哪些? f-string 格式化知道吗?

python格式化字符串有三种方法,第一是早期就有的%,其次是2.5之后的format(),还有就是3.6添加的f字符串调试。

f-string是2015年python 3.6 根据PEP
498新添加的一种字符串格式化方法,f-string实际上是在运行时计算的表达式,而不是常量值。在Python源代码中,f-string是一个文字字符串,前缀为’f’,其中包含大括号内的表达式。表达式会将大括号中的内容替换为其值。

例如:

import datetime
name = "zings"
age = 17
date = datetime.date(2019,7,18)
print(f'my name is {name}, this year is {date:%Y},Next year, I\'m {age+1}')  # my name is zings, this year is 2019,Next year, I'm 18

字典中的元素如何排序?sorted 排序函数的使用详解?

sorted()可对所有可迭代对象进行排序操作。

# sorted() —— 排序
# sorted(可迭代对象,key=排序规则)
lst = [9, 5, 3, 7, 8, 5, 2, 1, 4, 7, 1, 9, 0, 2]
print(sorted(lst, reverse=False))  # [0, 1, 1, 2, 2, 3, 4, 5, 5, 7, 7, 8, 9, 9]

lst = [1, 2, 3, 4, 33, 55, 77, 444, -111]
print(sorted(lst, key=abs))  # [1, 2, 3, 4, 33, 55, 77, -111, 444]

字典如何合并? 字典解包是什么?

字典对象内置了一个 update 方法,用于把另一个字典更新到自己身上。

profile = {'name': '张三', 'sex': '男'}
ext_info = {'age': 24}
profile.update(ext_info)
print(profile)  # {'name': '张三', 'sex': '男', 'age': 24}

使用 ** 可以解包字典,解包完后再使用 dict 或者 {} 就可以合并。

profile = {'name': '张三', 'sex': '男'}
ext_info = {'age': 24}
print({**ext_info, **profile}) # {'age': 24, 'name': '张三', 'sex': '男'}

字典推导式使用方法?字典推导式如何格式化 cookie 值?

字典推导式和列表推导式差不多。例:如何把两个列表中的值合为一个字典?

list1 = ['name', 'age', 'gender']
list2 = ['Tom', '18', '男']
dict = {list1[i]:list2[i] for i in range(len(list1))}

字典推导式如何格式化Cookie值?

  • 首先分析一下浏览器中cookie的结构
  • 基本上是:key=value; key=value; key=value
  • 其中key=value之间用一个分号和一个空格分开

如:Cookie

PHPSESSID=et4a33og7nbftv60j3v9m86cro; Hm_lvt_51e3cc975b346e7705d8c255164036b3=1561553685; Hm_lpvt_51e3cc975b346e7705d8c255164036b3=1561553685

用字典推导式格式化Cookie:

cookie = 'PHPSESSID=et4a33og7nbftv60j3v9m86cro; Hm_lvt_51e3cc975b346e7705d8c255164036b3=1561553685; Hm_lpvt_51e3cc975b346e7705d8c255164036b3=1561553685'
def cookie_to_dic(cookie):
    return {item.split('=')[0]: item.split('=')[1] for item in cookie.split('; ')}
print(cookie_to_dic(cookie))  # {'PHPSESSID': 'et4a33og7nbftv60j3v9m86cro', 'Hm_lvt_51e3cc975b346e7705d8c255164036b3': '1561553685', 'Hm_lpvt_51e3cc975b346e7705d8c255164036b3': '1561553685'}

字典的键可以是哪些类型的数据?

字典中的key一定是不可变数据类型且唯一的,所以key可以是:整形、字符串、元组等不可变数据类型,不能是列表、集合、字典等可变数据类型。

因为Python中对key通过哈希函数计算,通过计算的结果决定values的存储地址。从Python3.6版本以后,字典是有序的。

变量的作用域是怎么决定的?

LEGB原则。
什么是LEGB?

  • L: local 函数内部作用域
  • E: enclosing 函数内部与内嵌函数之间
  • G: global 全局作用域
  • B: build-in 内置作用
    python在函数里面的查找分为4种,称之为LEGB,也正是按照这是顺序来查找的。

类 class 和元类 metaclass 有什么区别?

元类是什么?

在python里面,一切皆对象,类本身也是一个对象,元类就是创建所有python类的类。即类就是元类的实例,除了type以外,所有对象要么是类的实例,要么是元类的实例。

实际上type就是这个元类,即python所有的类都是由type创建的,这也是为什么type可以用来动态创建类的原因。

换言之,元类type就是创建python类这种对象的东西,即可以称为一个类工厂。

描述一下抽象类和接口类的区别和联系?

抽象类

规定了一系列的方法,并规定了必须由继承类实现的方法。由于有抽象方法的存在,所以抽象类不能实例化。可以将抽象类理解为毛坯房,门窗,墙面的样式由你自己来定,所以抽象类与作为基类的普通类的区别在于约束性更强。

接口类

与抽象类很相似,表现在接口中定义的方法,必须由引用类实现。

它与抽象类的根本区别在于用途:
与不同个体间沟通的规则,你要进宿舍需要有钥匙,这个钥匙就是你与宿舍的接口,你的舍友也有这个接口,所以他也能进入宿舍,你用手机通话,那么手机就是你与他人交流的接口。

区别和关联:

1.接口是抽象类的变体,接口中所有的方法都是抽象的,而抽象类中可以有非抽象方法,抽象类是声明 方法的存在而不去实现它的类;
2.接口可以继承,抽象类不行;
3.接口定义方法,没有实现的代码,而抽象类可以实现部分方法;
4.接口中基本数据类型为static而抽象类不是。

内存泄露是什么?如何避免?

内存泄漏指由于疏忽或错误造成程序未能释放已经不再使用的内存。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费。

del() 函数的对象间的循环引用是导致内存泄露的主凶。不使用一个对象时使用: del object 来删除一个对象的引用计数就可以有效防止内存泄露问题。

通过Python扩展模块gc 来查看不能回收的对象的详细信息。

可以通过 sys.getrefcount(obj) 来获取对象的引用计数,并根据返回值是否为0来判断是否内存泄露。

python函数重载机制?

函数重载主要是为了解决两个问题:

  • 1.可变参数类型。
  • 2.可变参数个数。

另外,一个基本的设计原则是,仅仅当两个函数除了参数类型和参数个数不同以外,其功能是完全相同的,此时才使用函数重载。如果两个函数的功能其实不同,那么不应当使用重载,而应当使用一个名字不同的函数。

对于情况 1 ,函数功能相同,但是参数类型不同,python 如何处理?答案是根本不需要处理,因为 python 可以接受任何类型的参数,如果函数的功能相同,那么不同的参数类型在 python 中很可能是相同的代码,没有必要做成两个不同函数。

对于情况 2 ,函数功能相同,但参数个数不同,python 如何处理?大家知道,答案就是缺省参数。对那些缺少的参数设定为缺省参数即可解决问题。因为你假设函数功能相同,那么那些缺少的参数终归是需要用的。

好了,鉴于情况 1 跟 情况 2 都有了解决方案,python 自然就不需要函数重载了。

编写函数的4个原则

1.函数设计要尽量短小;
2.函数声明要做到合理、简单、易于使用;
3.函数参数设计应该考虑向下兼容;
4.一个函数只做一件事情,尽量保证函数语句粒度的一致性。

对缺省参数的理解 ?

缺省参数指在调用函数的时候没有传入参数的情况下,调用默认的参数,在调用函数的同时赋值时,所传入的参数会替代默认参数。

*args是不定长参数,它可以表示输入参数是不确定的,可以是任意多个。

**kwargs是关键字参数,赋值的时候是以键值对的方式,参数可以是任意多对在定义函数的时候,不确定会有多少参数会传入时,就可以使用两个参数。

回调函数,如何通信的?

回调函数是把函数的指针(地址)作为参数传递给另一个函数,将整个函数当作一个对象,赋值给调用的函数。

请手写一个单例模式

class SingleInstance(object):
	__instance = None
	def __new__(cls,*args,**kwargs):
		if cls.__instance is None:
			cls.__instance = objecet.__new__(cls)
			return cls.__instance
		else:
			return cls.__instance

单例模式的应用场景有那些?

单例模式应用的场景一般发现在以下条件下:

  • 资源共享的情况下,避免由于资源操作时导致的性能或损耗等,如日志文件,应用配置。
  • 控制资源的情况下,方便资源之间的互相通信。
    • 如:1.线程池等,2.网站的计数器,3.应用配置,4.多线程池 5.数据库配置,数据库连接池,6.应用程序的日志应用。

python asyncio的原理?

asyncio这个库就是使用python的yield这个可以打断保存当前函数的上下文的机制, 封装好了selector摆脱掉了复杂的回调关系。

Python中实现协程的几种方式?

协程:微线程,它是实现多任务的另一种方式,只不过是比线程更小的执行单元。因为它自带CPU的上下文,这样只要在合适的时机,我们可以把一个协程切换到另一个协程。

通俗的理解:在一个线程中的某个函数中,我们可以在任何地方保存当前函数的一些临时变量等信息,然后切换到另外一个函数中执行,注意不是通过调用函数的方式做到的,并且切换的次数以及什么时候再切换到原来的函数都由开发者自己确定。

使用yield

在python中,yield(生成器)可以很容易的实现上述的功能,从一个函数切换到另外一个函数。

import time
def task1():
    for i in range(3):
        print('A' + str(i))
        yield
        time.sleep(1)
        
def task2():
    for i in range(3):
        print('B' + str(i))
        yield
        time.sleep(2)
        
if __name__ == '__main__':
    g1 = task1()
    g2 = task2()
    while True:
        try:
            next(g1)
            next(g2)
        except:
            break

缺点:由生成器实现的协程在yield value时只能将value返回给调用者,不能

使用greenlet 完成协程任务

greenlet是python的一个C拓展,旨在提供可自行调度的“微线程”,即协程。

在greenlet中,target.switch(value)可以切换到指定的协程(target),然后yield value。greenlet用switch来表示协程的切换,从一个协程切换到另一个协程需要显式指定。

import time

from greenlet import greenlet


def a():  # 任务A
    for i in range(5):
        print('A' + str(i))
        gb.switch()
        time.sleep(0.1)


def b():  # 任务B
    for i in range(5):
        print('B' + str(i))
        gc.switch()
        time.sleep(0.1)


def c():  # 任务C
    for i in range(5):
        print('C' + str(i))
        ga.switch()
        time.sleep(0.1)


if __name__ == '__main__':
    ga = greenlet(a)
    gb = greenlet(b)
    gc = greenlet(c)

    ga.switch()

当创建一个greenlet时,首先初始化一个空的栈, switch到这个栈的时候,会运行在greenlet构造时传入的函数(首先在test1中打印 12),如果在这个函数(test1)中switch到其他协程(到了test2 打印34),那么该协程会被挂起,等到切换回来(在test2中切换回来 打印34)。当这个协程对应函数执行完毕,那么这个协程就变成dead状态。

gevent实现协程

greenlet已经实现了协程,但是人工切换,太麻烦,Python中还有一个自动切换任务的模块:gevent。

gevent只要有耗时的操作,就会自动切换到greenlet,也就是gevent依赖于greenlet。

猴子补丁:属性在运行时的动态替换,叫做猴子补丁(Monkey Patch)。

在于使用gevent时,相关的一切都要更换成gevent的耗时,阻塞部分换成gevent库的实现。不使用猴子补丁就要把time.sleep(1)换成gevent.sleep(1)

import time

import gevent
from gevent import monkey

monkey.patch_all()   # 打猴子补丁
# 用于将标准库中大部分阻塞式调用修改为协作式运行

def a():  # 任务A
    for i in range(5):
        print('A' + str(i))
        time.sleep(0.1)

def b():  # 任务B
    for i in range(5):
        print('B' + str(i))
        time.sleep(0.1)

def c():  # 任务C
    for i in range(5):
        print('C' + str(i))
        time.sleep(0.1)


if __name__ == '__main__':
	# 创建一个Greenlet对象,
	#其实调用的是Greenlet.spawn(需要from gevent import Greenlet),
	#返回greenlet对象
    g1 = gevent.spawn(a)
    g2 = gevent.spawn(b)
    g3 = gevent.spawn(c)

    g1.join() # 等待此协程执行完毕
    g2.join()
    g3.join()

    print('*' * 10)

如何理解Python中的GIL?

GIL的全称是 Global Interpreter Lock,全局解释器锁。之所以叫这个名字,是因为Python的执行依赖于解释器(只在Cpython中才有GIL)。Python最初的设计理念在于,为了解决多线程之间数据完整性和状态同步的问题,设计为在任意时刻只有一个线程在解释器中运行。而当执行多线程程序时,由GIL来控制同一时刻只有一个线程能够运行。即Python中的多线程是表面多线程,也可以理解为伪多线程,不是真正的多线程。

全局解释器就是为了锁定整个解释器内部的全局资源,每个线程想要运行首先获取GIL,而GIL本身又是一把互斥锁,造成所有线程只能一个一个one-by-one-并发-交替的执行。

GIL什么时候释放?

  • 在当前线程执行超时后会自动释放;
    • Python 3.x使用计时器(执行时间达到阈值后,当前线程释放GIL);
    • Python 2.x,tickets计数达到100.
  • 在当前线程执行阻塞操作时会自动释放(比如网络请求、IO操作);
  • 当前线程执行完成时。

GIL的优缺点

优点:GIL可以保证在多线程编程时,无需考虑多线程之间数据完整性和状态同步的问题。
缺点:多线程程序执行起来是“并发”,而不是“并行”。因此执行效率会很低,会不如单线程的执行效率。

如何规避GIL带来的影响?

  • 用multiprocess(多进程)替代Thread(推荐)
    • 只有IO密集型场景下的多线程会得到较好的性能,CPU密集型建议使用多进程。
  • 用其他解释器,如:JavaPython(不推荐)
  • 线程这块代码使用其他语言实现:c/java
  • 使用多进程+协程提高效率。

网络编程

怎么实现强行关闭客户端和服务器之间的连接?

在 socket 通信过程中不断循环检测一个全局变量(开关标记变量),一旦标记变量变为关闭,则 调用 socket 的 close 方法,循环结束,从而达到关闭连接的目的。

简述TCP 和UDP 的区别以及优缺点?

UDP

UDP 是面向无连接的通讯协议, UDP 数据包括目的端口号和源端口号信息,以数据报的形式发送。
优点: UDP 速度快、操作简单、要求系统资源较少,由于通讯不需要连接,可以实现广播发送
缺点: UDP 传送数据前并不与对方建立连接,对接收到的数据也不发送确认信号,发送端不知道数据是否会正确接收,也不重复发送,不可靠。

TCP

TCP面向连接的通讯协议,通过三次握手建立连接,四次挥手关闭连接。
优点: TCP 在数据传递时,有确认、窗口、重传、阻塞等控制机制,能保证数据正确性,较为可靠
缺点: TCP 相对于 UDP 速度慢一点,要求系统资源较多

未完,更新中…

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值