python面试题 part2

日萌社

人工智能AI:Keras PyTorch MXNet TensorFlow PaddlePaddle 深度学习实战(不定时更新)


请说一下你对迭代器和生成器的区别

答:(简单理解)

代器和生成器都是Python中特有的概念,迭代器可以看作是一个特殊的对象,每次调用该对象时会返回自身的下一个元素,从实现上来看,一个可迭代的对象必须是定义了__iter__()方法的对象,而一个迭代器必须是定义了__iter__()方法和next()方法的对象。生成器的概念要比迭代器稍显复杂,因为生成器是能够返回一个迭代器的函数,其最大的作用是将输入对象返回为一个迭代器。Python中使用了迭代的概念,是因为当需要循环遍历一个较大的对象时,传统的内存载入方式会消耗大量的内存,不如需要时读取一个元素的方式更为经济快捷。

(1)迭代器是一个抽象的概念,任何对象,如果它的类有next方法和iter方法返回自己本身都可以认为他是迭代器。对于string、list、dict、tuple等这类容器对象,使用for循环遍历是很方便的。在for语句对容器对象调用iter()函数,(iter()是python的内置函数),iter()会返回一个定义了next()方法的迭代器对象,它在容器中逐个访问容器内元素(next()也是python的内置函数),在没有后续元素时,next()会抛出一个StopIteration异常

(2)生成器(Generator)是创建迭代器的简单而强大的工具。它写起来就像是正规的函数,只是在需要返回数据的时候使用yield语句。每次next()被调用时,生成器会返回它脱离的位置(它记忆语句最后一次执行的位置和所有的数据值). 简单的说就是在函数的执行过程中,yield语句会把你需要的值返回给调用生成器的地方,然后退出函数,下一次调用生成器函数的时候又从上次中断的地方开始执行,而生成器内的所有变量参数都会被保存下来供下一次使用。

区别:生成器能做到迭代器能做的所有事,而且因为自动创建了__iter__()和next()方法,生成器显得特别简洁,而且生成器也是高效的,使用生成器表达式取代列表解析可以同时节省内存。除了创建和保存程序状态的自动方法,当发生器终结时,还会自动抛出StopIteration异常


什么是线程安全

答: 

如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。

但有的时候多线程运行时,会同时竞争同一个共享资源并对他进行修改,这个时候由于线程之间竞争的问题就会造成修改错误,会产生多线程运行的结果和单线程的运行结果不一致的问题.

解决方法:

当多个线程几乎同时修改某一个共享数据的时候,需要进行同步控制 线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制是引入互斥锁。 互斥锁为资源引入一个状态:锁定/非锁定。 某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。


socket 的 connect 方法工作原理是什么

答:

网络编程socket api存在一批核心接口,而这一批核心接口就是几个看似简单的函数,尽管实际上这些函数没有一个是简单。connect函数就是这些核心接口的一个函数,它完成主动连接的过程。connect函数的功能是完成一个有连接协议的连接过程,对于TCP来说就是那个三路握手过程.它的函数原型:connect(address) 连接到address处的套接字。一般address的格式为元组(hostname,port),如果连接出错,返回socket.error错误。

connect函数的功能:

connect函数的功能可以用一句话来概括,就是完成面向连接的协议的连接过程,它是主要用于连接的。面向连接的协议如TCP,在建立连接的时候总会有一方先发送数据,那么谁调用了connect谁就是先发送数据的一方。如此理解connect参数容易了,我必需指定数据发送的地址,这正好是connect的参数的作用.

参数元组(hostname,port)

指定数据发送的目的地,也就是服务器端的地址。这里服务器是针对connect说的,因为connect是主动连接的一方调用的,所以相应的要存在一个被连接的一方,被动连接的一方需要调用listen以接受connect的连接请求,如此被动连接的一方就是服务器了。与所有的socket网络接口一样,connect总会在某个时候可能失败,此时它会返socket.error,常见的错误有对方主机不可达或者超时错误,也可以是对方主机没有相应的进程在对应端口等待。


Python中怎么简单的实现列表去重

答:Python中的列表去重方法有很多,下面有两种常用的简单方式

方法一:转换为集合数据类型, set(列表)

解释:列表中的元素可以重复,在集合中元素是不可以重复的,这是集合的特性.利用这个特性,我们可以把列表强制转化成集合,使得原来列表中重复的元素去重.但是注意这种方法有可能使得原有的列表中的数据顺序发生改变.

方法二:不改变原有列表顺序的方法

解释:通过创建一个新的列表list2存贮去重后的数据,如以上代码显示,对list1中的数据依次进行for循环遍历.如果list1中的数据在list2中没有则添加到list2中,如果list2中有该数据则不添加,如此直到对list1中数据遍历完成就可以使得list2中存储保持原有顺序且不重复的数据.


python 中 yield 的用法

答:

通常的for…in…循环中,in后面是一个数组,这个数组就是一个可迭代对象,类似的还有表,字符串,文件。它可以是mylist = [1, 2, 3],也可以是mylist = [x*x for x in range(3)]。 它的缺陷是所有数据都在内存中,如果有海量数据的话将会非常耗内存。

而生成器就不是,他不需要把所有的数据都存放在内层中,而是当使用到的时候再去获取.这是由于生成器是可以迭代的,生成器(generator)能够迭代的关键是它有一个next()方法,工作原理就是通过重复调用next()方法,不断的获取下一数据,而不是一次性把所有的数据都存放到内存中,这样重复操作直到捕获一个异常才会停止。比如 mygenerator = (x*x for x in range(3)),注意这里用到了() 这里相当于一个函数,它就不是数组,而上面的例子 [ ] 列表

带有 yield 的函数就跟我们举的例子mygenerator = (x*x for x in range(3))一样,不再是一个普通函数,而是一个生成器generator,可用于迭代,工作原理同上。 yield 是一个类似 return 的关键字,迭代一次遇到yield时就返回yield后面的值。重点是:下一次迭代时,从上一次迭代遇到的yield后面的代码开始执行。

简要理解:yield就是 return 返回一个值,并且记住这个返回的位置,下次迭代就从这个位置后开始。


什么是面向对象编程

面向对象的编程产生的原因:由于面向过程编程在构造系统时,无法解决重用,维护,扩展的问题,而且逻辑过于复杂,代码晦涩难懂,因此,人们开始想能不能让计算机直接模拟现实的环境,以人类解决问题的方法,思路,习惯和步骤来设计相应的应用程序。于是,面向对象的编程思想就产生了。

面向对象的编程的主要思想是把构成问题的各个事物分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描述一个事物在解决问题的过程中经历的步骤和行为。对象作为程序的基本单位,将程序和数据封装其中,以提高程序的重用性,灵活性和可扩展性。

类是创建对象的模板,一个类可以创建多个对象。对象是类的实例化。 类是 抽象的,不占用存储空间,而对象具体的,占用存储空间。 面向对象有三大特性:封装,继承,多态。


谈谈你对GIL锁对python多线程的影响

答:

GIL的全称是Global Interpreter Lock(全局解释器锁),来源是python设计之初的考虑,为了数据安全所做的决定。每个CPU在同一时间只能执行一个线程(在单核CPU下的多线程其实都只是并发,不是并行,并发和并行从宏观上来讲都是同时处理多路请求的概念。但并发和并行又有区别,并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔内发生。)

在Python多线程下,每个线程的执行方式:

1、获取GIL

2、执行代码直到sleep或者是python虚拟机将其挂起。

3、释放GIL        

可见,某个线程想要执行,必须先拿到GIL,我们可以把GIL看作是“通行证”,并且在一个python进程中,GIL只有一个。拿不到通行证的线程,就不允许进入CPU执行。

GIL锁存在下多线程的使用场景:

1.密集型科学运算(CPU没有闲置)

由于GIL锁保证同一时刻只有一个线程可以使用CPU,这使得多线程之间会对GIL锁进行争夺,争夺的过程中谁获取了GIL锁谁就可以使用CPU,这种争夺状态会一直持续,这就造成了资源的浪费,浪费在了抢夺资源上,在这种情况下我们会发现如果只是用一个线程反而会避免这种资源的竞争,同时提高CPU的使用率,所以在科学计算这种没有CPU闲置的情况下我们倾向于使用单线程.

2.I/O操作(有CPU闲置)

像爬虫,网络请求这类操作往往受到网速等原因的限制,会造成一个线程占用CPU的同时等待网络数据的获取,这个时候GIL锁会自动判定如果线程使用的CPU闲置他就会自动切换到另外一个线程,由此可以节省线程占用CPU阻塞等待网络请求的时间.所以在像网络操作这种有CPU闲置的情况下我们倾向使用多线程


python是如何进行内存管理的

:(基本理解:足够面试使用)

一、垃圾回收:python不像C++,Java等语言一样,他们可以不用事先声明变量类型而直接对变量进行赋值。对Python语言来讲,对象的类型和内存都是在运行时确定的。这也是为什么我们称Python语言为动态类型的原因(这里我们把动态类型可以简单的归结为对变量内存地址的分配是在运行时自动判断变量类型并对变量进行赋值)。

二、引用计数:Python采用了类似Windows内核对象一样的方式来对内存进行管理。每一个对象,都维护这一个对指向该对对象的引用的计数。当变量被绑定在一个对象上的时候,该变量的引用计数就是1,(还有另外一些情况也会导致变量引用计数的增加),系统会自动维护这些标签,并定时扫描,当某标签的引用计数变为0的时候,该对就会被回收。

(深入理解:加深对内存机制的理解)

三、内存池机制Python的内存机制如同金字塔一般,

  第0层是C语言中的malloc(c语言中申请内存空间的方法),free(C语言中释放内存的方法)等内存分配和释放函数进行操作;

  第1层和第2层是内存池,有Python的接口函数PyMem_Malloc函数实现,当对象小于256K时有该层直接分配内存;

  第3层是最上层,也就是我们对Python对象的直接操作;

在 C 中如果频繁的调用 malloc 与 free 时,是会产生性能问题的.再加上频繁的分配与释放小块的内存会产生内存碎片. Python 在这里主要干的工作有:

  如果请求分配的内存在1~256字节之间就使用自己的内存管理系统,否则直接使用 malloc.

  这里还是会调用 malloc 分配内存,但每次会分配一块大小为256k的大块内存.

经由内存池登记的内存到最后还是会回收到内存池,并不会调用 C 的 free 释放掉.以便下次使用.对于简单的Python对象,例如数值、字符串,元组(tuple不允许被更改采用的是复制的方式,也就是说当将另一个变量B赋值给变量A时,虽然A和B的内存空间仍然相同,但当A的值发生变化时,会重新给A分配空间,A和B的地址变得不再相同


请描述线程、进程、协程的优缺点

进程、线程和协程是三个在多任务处理中常听到的概念,三者各有区别又相互联系。

(1)进程

进程是一个程序在一个数据集中的一次动态执行过程,可以简单理解为“正在执行的程序”,它是CPU资源分配和调度的独立单位。 进程一般由程序、数据集、进程控制块三部分组成。我们编写的程序用来描述进程要完成哪些功能以及如何完成;数据集则是程序在执行过程中所需要使用的资源;进程控制块用来记录进程的外部特征,描述进程的执行变化过程,系统可以利用它来控制和管理进程,它是系统感知进程存在的唯一标志。

进程的缺点:  创建、撤销和切换的开销比较大。

(2)线程

线程是在进程之后发展出来的概念。 线程也叫轻量级进程,它是一个基本的CPU执行单元,也是程序执行过程中的最小单元,由线程ID、程序计数器、寄存器集合和堆栈共同组成。一个进程可以包含多个线程。

线程的优点:   减小了程序并发执行时的开销,提高了操作系统的并发性能.

缺点的缺点:  线程没有自己的系统资源,只拥有在运行时必不可少的资源,但同一进程的各线程可以共享进程所拥有的系统资源,如果把进程比作一个车间,那么线程就好比是车间里面的工人。不过对于某些独占性资源存在锁机制,处理不当可能会产生“死锁”。

(3)协程

协程是一种用户态的轻量级线程,又称微线程,英文名Coroutine,协程的调度完全由用户控制。人们通常将协程和子程序(函数)比较着理解。 子程序调用总是一个入口,一次返回,一旦退出即完成了子程序的执行。 协程的起始处是第一个入口点,在协程里,返回点之后是接下来的入口点。在python中,协程可以通过yield来调用其它协程。通过yield方式转移执行权的协程之间不是调用者与被调用者的关系,而是彼此对称、平等的,通过相互协作共同完成任务。其运行的大致流程如下: 第一步,协程A开始执行。 第二步,协程A执行到一半,进入暂停,通过yield命令将执行权转移到协程B。 第三步,(一段时间后)协程B交还执行权。 第四步,协程A恢复执行。 1 2 3 4 协程的特点在于是一个线程执行,

与多线程相比,其优势体现在: 协程的执行效率非常高。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。 协程不需要多线程的锁机制。在协程中控制共享资源不加锁,只需要判断状态就好了。

提示:利用多核CPU最简单的方法是多进程+协程,既充分利用多核,又充分发挥协程的高效率,可获得极高的性能.


深拷贝和浅拷贝的区别

答: 通俗的说 浅拷贝只拷贝引用(也就是地址),深拷贝就是拷贝具体的值,重新开辟新的空间存储这些值。就像是浅拷贝就是你的影子,深拷贝是你的克隆人,你没了影子也就没了,但是克隆人还活着。

深拷贝和浅拷贝有很多不同的情况,不同情况下都有不同的特点

1 拷贝单一的可变数据类型的时候 如a = [1,2] 这种单一的列表

深拷贝和浅拷贝都是一样的,都会开辟新的一片新空间,存储数据,保证数据的独立性

2 拷贝复杂的有嵌套数据类型 如a = [1,2],b = [3,4],c = [a,b]拷贝c的时候

d = copy.copy(c)

浅拷贝:只会给d开辟一片空间 存贮c中的数据,也就是存储a,b的引用, 而并不是开辟多个空间去存储a中1,2数据,b中的3,4数据,这样以来a如果发生变化d也会跟着发生变化,没有办法保证数据的独立性.

d = copy.deepcopy(c)

深拷贝:会给d开辟多片空间,存储所有的数据,可以保证独立空间,保证数据的独立性

3 拷贝不可变数据类型 如元组这样的类型

深拷贝和浅拷贝都一样,因为不可变类型数据不会变化,所以不需要保证数据的独立性,直接引用这个数据就可以,而不会开辟新的空间.

提示:ptyhon中大多情况都是浅拷贝


Linux基础和数据结构与算法

find和grep的区别

  1. grep命令是一种强大的文本搜索工具,grep搜索内容串可以是正则表达式,允许对文本文件进行模式查找。如果找到匹配模式, grep打印包含模式的所有行。
  2. find通常用来在特定的目录下搜索符合条件的文件,也可以用来搜索特定用户属主的文件。

什么是阻塞?什么是非阻塞

阻塞调用是指调用结果返回之前,当前线程会被挂起。函数只有在得到结果之后才会返回。有人也许会把阻塞调用和同步调用等同起来,实际上他是不同的。对于同步调用来说,很多时候当前线程还是激活的,只是从逻辑上当前函数没有返回而已。例如,我们在CSocket中调用Receive函数,如果缓冲区中没有数据,这个函数就会一直等待,直到有数据才返回。而此时,当前线程还会继续处理各种各样的消息。如果主窗口和调用函数在同一个线程中,除非你在特殊的界面操作函数中调用,其实主界面还是应该可以刷新。socket接收数据的另外一个函数recv则是一个阻塞调用的例子。当socket工作在阻塞模式的时候,如果没有数据的情况下调用该函数,则当前线程就会被挂起,直到有数据为止。

非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。

描述数组、链表、队列、堆栈的区别

数组与链表是数据存储方式的概念,数组在连续的空间中存储数据,而链表可以在非连续的空间中存储数据;

队列和堆栈是描述数据存取方式的概念,队列是先进先出,而堆栈是后进先出;队列和堆栈可以用数组来实现,也可以用链表实现。

你知道几种排序,讲一讲你最熟悉的一种

如果有一个文本temp.txt中有且只有一个1.8,此时我们想将其更换为2.0,请在不适用编辑器的前提下实现该功能

sed –i s#1.8#2.0# temp.txt

如何使用两个栈结构实现一个队列

将数据压入一个栈,然后将其分别弹栈,并将弹栈数据压入另一个栈,然后另一个栈出栈

如何检查最近十分钟内变动过的文件

find / -mmin -10

在当前目录下查找大小小于10m的文件

find / -size -10M

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

あずにゃん

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值