1、曾经被问到的面试题
1、曾经被问到的面试题
1、python中的鸭子对象
一句话概述,看起来像是鸭子,走路像鸭子,叫声像鸭子他就是鸭子
对于代码来说,比如可以iter(obj) == True 就是可迭代对象,str, list,没有__next__方法,但是它实现了__getitem__()方法,所以也是可迭代对象
2、python3和python2中for * in xx,xx是什么,为什么要改变
可迭代对象
具有惰性计算特点的序列称为惰性序列,Python 中的迭代器就是一个惰性序列,调用 iter() 返回一个 iterator 并赋值给一个变量后不会立即进行求值,而是当你用到其中某些元素的时候才去求某元素的值。
惰性计算还可以在大规模数据处理中平滑处理时间,提高内存使用率。当处理大规模数据时,一次性进行处理往往是不方便的。
3、python中的深浅拷贝
浅拷贝通常只复制对象本身,而深拷贝不仅会复制对象,还会递归的复制对象所关联的对象。深拷贝可能会遇到两个问题:一是一个对象如果直接或间接的引用了自身,会导致无休止的递归拷贝;二是深拷贝可能对原本设计为多个对象共享的数据也进行拷贝。Python通过copy模块中的copy和deepcopy函数来实现浅拷贝和深拷贝操作,其中deepcopy可以通过memo字典来保存已经拷贝过的对象,从而避免刚才所说的自引用递归问题;此外,可以通过copyreg模块的pickle函数来定制指定类型对象的拷贝行为。
deepcopy函数的本质其实就是对象的一次序列化和一次返回序列化,面试题中还考过用自定义函数实现对象的深拷贝操作,显然我们可以使用pickle模块的dumps和loads来做到,代码如下所示。
import pickle
my_deep_copy = lambda obj: pickle.loads(pickle.dumps(obj))
列表的切片操作[:]相当于实现了列表对象的浅拷贝,而字典的copy方法可以实现字典对象的浅拷贝。对象拷贝其实是更为快捷的创建对象的方式。在Python中,通过构造器创建对象属于两阶段构造,首先是分配内存空间,然后是初始化。在创建对象时,我们也可以基于“原型”对象来创建新对象,通过对原型对象的拷贝(复制内存)就完成了对象的创建和初始化,这种做法更加高效,这也就是设计模式中的原型模式。
4、正则表达式的match方法和search方法有什么区别?
match方法是从字符串的起始位置进行正则表达式匹配,返回Match对象或None。search方法会扫描整个字符串来找寻匹配的模式,同样也是返回Match对象或None。
5、Python中为什么没有函数重载
首先Python是解释型语言,函数重载现象通常出现在编译型语言中。其次Python是动态类型语言,函数的参数没有类型约束,也就无法根据参数类型来区分重载。再者Python中函数的参数可以有默认值,可以使用可变参数和关键字参数,因此即便没有函数重载,也要可以让一个函数根据调用者传入的参数产生不同的行为。
6、python中为什么要引入全局解释器锁
Python引入全局解释器锁(GIL,Global Interpreter Lock)的主要原因是为了简化Python解释器的设计和实现,并确保解释器内部数据结构在多线程环境下的安全性。
GIL是Python解释器中的一种机制,它是一把全局锁,用于保护解释器免受多线程并发访问的影响。这意味着在Python中,同一时刻只允许一个线程执行Python字节码。
当一个线程执行Python字节码时,其他线程将被阻塞,即使系统具有多个CPU核心,Python的多线程程序也不能同时利用它们。
GIL的引入可以追溯到Python的早期设计。在Python的设计初期,为了简化解释器的实现,并确保多线程环境下的线程安全,设计者决定引入GIL。通过GIL,Python解释器不需要在共享数据上实现复杂的同步机制,从而降低了实现的复杂性。然而,GIL的存在也带来了一些限制和挑战。由于GIL的存在,Python的多线程在CPU密集型任务上并不能提供真正的并行性。这意味着在多核CPU上,Python的多线程程序可能无法充分利用硬件资源。
为了解决这个问题,Python社区已经提出了一些解决方案,如使用多进程(multiprocessing)代替多线程,或者使用支持并行计算的库(如NumPy、SciP等)。
总之,Python引入GIL是为了简化解释器的设计和实现,并确保多线程环境下的线程安全。然而,这也带来了一些限制和挑战,需要在使用Python多线程时特别注意。
7、如何理解异步IO
- 同步 vs. 异步:
- 同步(Synchronous):同步操作是指在发起一个操作后,必须等待该操作完成才能继续执行后续的操作。在同步操作中,程序会阻塞(Block)当前线程或进程,直到操作完成。
- 异步(Asynchronous):异步操作是指在发起一个操作后,可以立即返回并继续执行后续的操作,而不必等待该操作完成。在异步操作中,程序不会阻塞当前线程或进程,而是使用回调函数、事件循环等机制来处理操作的结果。
- IO操作:
- 输入/输出(IO)操作:指的是与外部设备(例如磁盘、网络等)进行数据交换的操作。在计算机中,IO操作是相对于CPU执行的计算操作而言的。常见的IO操作包括读取文件、发送网络请求、接收网络响应等。
- 异步IO:
- 异步IO(Asynchronous IO):指的是在进行IO操作时,不需要等待IO操作完成才能继续执行后续的操作。相反,程序可以继续执行其他任务,而IO操作在后台进行。当IO操作完成时,程序可以通过回调函数、事件通知等方式获取IO操作的结果。
在编程中,异步IO通常与事件循环(Event Loop)结合使用,例如在Python中,使用 asyncio 模块来实现异步IO操作。异步IO的优点在于它可以提高程序的并发性和吞吐量,使程序能够更有效地利用系统资源,从而提高性能。
总的来说,理解异步IO就是理解在IO操作时,程序如何以非阻塞的方式继续执行其他任务,并在IO操作完成后获取操作结果的过程。
8、异步IO和事件循环的关系
在 Python 中,异步IO和事件循环密切相关,它们通常一起使用来实现异步编程。
- 事件循环(Event Loop):
- 事件循环是一个在程序中运行的循环,它负责处理和调度异步任务(例如IO操作、定时器等)。
- 在事件循环中,任务被添加到事件队列中,并在适当的时候执行。事件循环负责选择要执行的任务,并确保任务按正确的顺序执行。
- Python 中常用的事件循环实现是
asyncio模块提供的asyncio.EventLoop。
- 异步IO(Asynchronous IO):
- 异步IO是一种编程模型,它允许程序在执行IO操作时不阻塞当前线程或进程,而是可以继续执行其他任务。
- 在Python中,异步IO通常使用
asyncio模块来实现,它提供了异步编程的基础设施,包括异步IO操作、协程等。
- 关系:
- 异步IO依赖于事件循环来调度和执行异步任务。当需要执行一个异步IO操作时,任务会被添加到事件循环的事件队列中,并在事件循环的控制下执行。
- 事件循环负责管理异步任务的执行顺序、任务的状态和执行过程中的异常处理等。
- 在事件循环中,异步IO操作通常以协程(Coroutine)的形式表示,而协程是一种可以暂停和恢复执行的函数,适用于异步编程。
简而言之,事件循环是异步IO编程的基础,它负责调度和执行异步任务,而异步IO操作则是在事件循环的管理下执行的。通过事件循环,异步IO可以实现非阻塞的IO操作,并使程序能够更高效地利用系统资源
9、__new__ 方法是什么?
__new__ 方法是Python中的一个特殊的静态方法,用于创建类的新实例。它是在__init__方法之前被调用的,并且负责返回类的新实例。__new__方法通常不需要被直接调用,而是由Python解释器在实例化对象时自动调用。
__new__方法的主要作用是创建一个新对象,而__init__方法则用于初始化这个新创建的对象。__new__方法只接收类本身(通常用cls表示)作为第一个参数,后面可以跟任意数量的参数,这些参数将传递给__init__方法。
class Singleton:
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super(Singleton, cls).__new__(cls)
return cls._instance
def __init__(self):
pass # 这里可以初始化实例的属性
# 创建Singleton的两个实例
singleton1 = Singleton()
singleton2 = Singleton()
# 检查两个实例是否是同一个对象
print(singleton1 is singleton2) # 输出: True
__new__方法通常用于以下情况:
- 控制对象的创建:比如实现单例模式。
- 继承不可变类型:比如
int、str、tuple等,因为这些类型的实例是不可变的,所以需要在__new__方法中创建新实例。 - 多态实例化:根据传入的参数动态决定创建哪种类型的对象。
在大多数情况下,你不需要自定义__new__方法,除非你有特殊的对象创建需求。通常情况下,只需要定义__init__方法来初始化对象即可。
10、__init__ 方法是什么?
__init__ 方法是Python中类的构造器,用于在创建类的新实例时初始化对象。当你创建一个新对象时,Python会自动调用__init__方法。这个方法通常用于设置对象的初始状态,比如给对象的属性赋初始值。
__init__方法的第一个参数始终是self,它代表类的实例本身,允许我们访问类的属性和方法。在__init__方法中,你可以定义其他参数来接收初始化数据,并根据这些数据来设置对象的状态。
如果你不显式定义__init__方法,Python会提供一个默认的__init__方法,这个默认的方法什么也不做。自定义__init__方法可以让你控制对象创建时的行为。
11、__new__ 的作用
依照Python官方文档的说法,__new__方法主要是当你继承一些不可变的class时(比如int, str, tuple), 提供给你一个自定义这些类的实例化过程的途径。 首先我们来看一下第一个功能,具体我们可以用int来作为一个例子: 假如我们需要一个永远都是正数的整数类型,通过集成int,我们可能会写出这样的代码:
工厂模式的实现
class Fruit(object):
def __init__(self):
pass
def print_color(self):
pass
class Apple(Fruit):
def __init__(self):
pass
def print_color(self):
print("apple is in red")
class Orange(Fruit):
def __init__(self):
pass
def print_color(self):
print("orange is in orange")
class FruitFactory(object):
fruits = {"apple": Apple, "orange": Orange}
def __new__(cls, name):
if name in cls.fruits.keys():
return cls.fruits[name]()
else:
return Fruit()
fruit1 = FruitFactory("apple")
fruit2 = FruitFactory("orange")
fruit1.print_color()
fruit2.print_color()
13、__init__与__new__的区别
从上述过程中我们可以发现,这两个方法区别在于:
作用区别,
init实例级别,new类级别- 1.
__init__通常用于初始化一个新实例,控制这个初始化的过程,比如添加一些属性, 做一些额外的操作,发生在类实例被创建完以后。它是实例级别的方法。 - 2.
__new__通常用于控制生成一个类实例的过程。它是类级别的方法
- 1.
执行顺序,先new 后
init