Loading

『笔记』Python面试题

『笔记』Python面试题

Part 1

附答案 | 最强Python面试题之Python基础题(1)

赋值、浅拷贝和深拷贝的区别

(1) 赋值

在 Python 中,对象的赋值就是简单的对象引用,这点和 C++不同,如下所示:

a = [1,2,"hello",['python', 'C++']]
b = a

在上述情况下,a 和 b 是一样的,他们指向同一片内存,b 不过是 a 的别名,是引用。

我们可以使用 b is a 去判断,返回 True,表明他们地址相同,内容相同,也可以使用 id() 函数来查
看两个列表的地址是否相同。

赋值操作(包括对象作为参数、返回值)不会开辟新的内存空间,它只是复制了对象的引用。也就是说除了 b 这个名字之外,没有其他的内存开销。修改了 a,也就影响了 b,同理,修改了 b,也就影响了 a。

(2) 浅拷贝

浅拷贝会创建新对象,其内容非原对象本身的引用,而是原对象内第一层对象的引用。

浅拷贝有三种形式:切片操作、工厂函数、copy 模块中的 copy 函数。

  • 比如上述的列表 a,切片操作:b = a[:] 或者 b = [x for x in a];
  • 工厂函数:b = list(a);
  • copy 函数:b = copy.copy(a);

浅拷贝产生的列表 b 不再是列表 a 了,使用 is 判断可以发现他们不是同一个对象,使用 id 查看,他们也不指向同一片内存空间。但是当我们使用 id(x) for x in a 和 id(x) for x in b 来查看 a 和 b 中元素的地址时,可以看到二者包含的元素的地址是相同的。

在这种情况下,列表 a 和 b 是不同的对象,修改列表 b 理论上不会影响到列表 a。

但是要注意的是,浅拷贝之所以称之为浅拷贝,是它仅仅只拷贝了一层,在列表 a 中有一个嵌套的 list,如果我们修改了它,情况就不一样了。

比如:a[3].append('java'),查看列表 b,会发现列表 b 也发生了变化,这是因为,我们修改了嵌套的 list,修改外层元素,会修改它的引用,让它们指向别的位置,修改嵌套列表中的元素,列表的地址并未发生变化,指向的都是用一个位置。

(3) 深拷贝

深拷贝只有一种形式,copy 模块中的 deepcopy() 函数。

深拷贝和浅拷贝对应,深拷贝拷贝了对象的所有元素,包括多层嵌套的元素。因此,它的时间和空间开销要高。

同样的对列表 a,如果使用 b = copy.deepcopy(a),再修改列表 b 将不会影响到列表 a,即使嵌套的列表具有更深的层次,也不会产生任何影响,因为深拷贝拷贝出来的对象根本就是一个全新的对象,不再与原来的对象有任何的关联。

(4) 注意点

对于非容器类型,如数字、字符,以及其他的“原子”类型,没有拷贝一说,产生的都是原对象的引用。

如果元组变量值包含原子类型对象,即使采用了深拷贝,也只能得到浅拷贝。

__init__ 和__new__的区别

当我们使用「类名()」创建对象的时候,Python 解释器会帮我们做两件事情:第一件是为对象在内存分配空间,第二件是为对象进行初始化。「分配空间」是new 方法,初始化是init方法。

new 方法在内部其实做了两件时期:第一件事是为「对象分配空间」,第二件事是「把对象的引用返回给 Python 解释器」。当 Python 的解释器拿到了对象的引用之后,就会把对象的引用传递给 init 的第一个参数 self,init 拿到对象的引用之后,就可以在方法的内部,针对对象来定义实例属性。

之所以要学习 new 方法,就是因为需要对分配空间的方法进行改造,改造的目的就是为了当使用「类名()」创建对象的时候,无论执行多少次,在内存中永远只会创造出一个对象的实例,这样就可以达到单例设计模式的目的。

Python 的变量、对象以及引用

首先把结论抛出来

  • 变量是到内存空间的一个指针,也就是拥有指向对象连接的空间;

  • 对象是一块内存,表示它们所代表的值;

  • 引用就是自动形成的从变量到对象的指针。

以下是具体解释

在 Python 中使用变量的时候不需要提前声明变量及其类型,变量还是会正常工作。在 Python 中,这个是以一种非常流畅的方式完成,下面以 a = 1 为例我们来看一下它到底是个什么情况。

首先是怎么知道创建了变量:对于变量 a,或者说是变量名 a,当程序第一次给它赋值的时候就创建了它,其实真实情况是 Python 在代码运行之前就先去检测变量名,我们不去具体深究这些,你只需要当作是「最开始的赋值创建了变量」。

再者是怎么知道变量是什么类型:其实这个很多人都没有搞清楚,「类型」这个概念不是存在于变量中,而是存在于对象中。变量本身就是通用的,它只是恰巧在某个时间点上引用了当时的特定对象而已。就比如说在表达式中,我们用的那个变量会立马被它当时所引用的特定对象所替代。

上面这个是动态语言明显区别于静态语言的地方,其实对于刚开始来说,如果你适应将「变量」和「对象」分开,动态类型你也就可以很容易理解了。

我们还是以 a = 1 为例,其实从上面的讲述中,我们很容易的可以发现对于 a = 1 这个赋值语句 Python 是如何去执行它的:创建一个代表值 1 的对象 --> 创建一个变量 a --> 将变量 a 和对象 1 连接。 下面我用一个图来更清晰的表示一下:

image

由上图我们可以看出,变量 a 其实变成了对象 1 的一个引用。如果你学过指针的话,你就会发现在内部「变量其实就是到对象内存空间的一个指针」。

同样还是上图,我们还可以看出在 Python 中「引用」是从变量到对象的连接,它就是一种关系,在内存中以指针的形式实现。

Python 里面如何生成随机数

在 Python 中用于生成随机数的模块是 random,在使用前需要 import. 如下例子可以酌情列举:

  • random.random():生成一个 0-1 之间的随机浮点数

  • random.uniform(a, b):生成[a,b]之间的浮点数

  • random.randint(a, b):生成[a,b]之间的整数

  • random.randrange(a, b, step):在指定的集合[a,b)中,以 step 为基数随机取一个数

  • random.choice(sequence):从特定序列中随机取一个元素,这里的序列可以是字符串,列表,元组等。

Python 是强语言类型还是弱语言类型?

Python 是强类型的动态脚本语言。

  • 强类型:不允许不同类型相加。
  • 动态:不使用显示数据类型声明,且确定一个变量的类型是在第一次给它赋值的时候。
  • 脚本语言:一般也是解释型语言,运行代码只需要一个解释器,不需要编译。

谈一下什么是解释性语言,什么是编译性语言?

计算机不能直接理解高级语言,只能直接理解机器语言,所以必须要把高级语言翻译成机器语言,计算机才能执行高级语言编写的程序。

解释性语言在运行程序的时候才会进行翻译。

编译型语言写的程序在执行之前,需要一个专门的编译过程,把程序编译成机器语言(可执行文件)。

Python 怎么使用日志

Python 中有日志,Python 自带 logging 模块,调用 logging.basicConfig()方法,配置需要的日志等级和相应的参数,Python 解释器会按照配置的参数生成相应的日志。

Python 的标准日志模块

Python 标准库中提供了 logging 模块供我们使用。在最简单的使用中,默认情况下 logging 将日志打印到屏幕终端,我们可以直接导入 logging 模块,然后调用 debug,info,warn,error 和 critical 等函数来记录日志,默认日志的级别为 warning,级别比 warning 高的日志才会被显示(critical > error > warning > info > debug),「级别」是一个逻辑上的概念,用来区分日志的重要程度。

import logging

logging.debug('debug message')
logging.info("info message")
logging.warn('warn message')
logging.error("error message")
logging.critical('critical message')

上述代码的执行结果如下所示:

WARNING:root:warn message
ERROR:root:error message
CRITICAL:root:critical message

我在上面说过,用 print 的话会产生大量的信息,从而很难从中找到真正有用的信息。而 logging 中将日志分成不同的级别以后,我们在大多数时间只保存级别比较高的日志信息,从而提高了日志的性能和分析速度,这样我们就可以很快速的从一个很大的日志文件里找到错误的信息。

配置日志格式

我们在用 logging 来记录日志之前,先来进行一些简单的配置:

import logging

logging.basicConfig(filename= 'test.log', level= logging.INFO)

logging.debug('debug message')
logging.info("info message")
logging.warn('warn message')
logging.error("error message")
logging.critical('critical message')

运行上面的代码以后,会在当前的目录下新建一个 test.log 的文件,这个文件中存储 info 以及 info 以上级别的日志记录。运行一次的结果如下所示:

INFO:root:info message
WARNING:root:warn message
ERROR:root:error message
CRITICAL:root:critical message

上面的例子中,我是用 basicConfig 对日志进行了简单的配置,其实我们还可以进行更为复杂些的配置,在此之前,我们先来了解一下 logging 中的几个概念:

  • Logger:日志记录器,是应用程序中可以直接使用的接口。
  • Handler:日志处理器,用以表明将日志保存到什么地方以及保存多久。
  • Formatter:格式化,用以配置日志的输出格式。

上述三者的关系是:一个 Logger 使用一个 Handler,一个 Handler 使用一个 Formatter。那么概念我们知道了,该如何去使用它们呢?我们的 logging 中有很多种方式来配置文件,简单的就用上面所说的 basicConfig,对于比较复杂的我们可以将日志的配置保存在一个配置文件中,然后在主程序中使用 fileConfig 读取配置文件。

基本的知识我们知道了,下面我们来做一个小的题目:日志文件保存所有 debug 及其以上级别的日志,每条日志中要有打印日志的时间,日志的级别和日志的内容。请先自己尝试着思考一下,如果你已经思考完毕请继续向下看:

import logging

logging.basicConfig(
   level= logging.DEBUG,
   format = '%(asctime)s : %(levelname)s : %(message)s',
   filename= "test.log"
)

logging.debug('debug message')
logging.info("info message")
logging.warn('warn message')
logging.error("error message")
logging.critical('critical message')

上述代码的一次运行结果如下:

2018-10-19 22:50:35,225 : DEBUG : debug message
2018-10-19 22:50:35,225 : INFO : info message
2018-10-19 22:50:35,225 : WARNING : warn message
2018-10-19 22:50:35,225 : ERROR : error message
2018-10-19 22:50:35,225 : CRITICAL : critical message

我刚刚在上面说过,对于比较复杂的我们可以将日志的配置保存在一个配置文件中,然后在主程序中使用 fileConfig 读取配置文件。下面我们就来看一个典型的日志配置文件(配置文件名为 logging.conf):[...]

Python 是如何进行类型转换的

内建函数封装了各种转换函数,可以使用目标类型关键字强制类型转换,进制之间的转换可以用 int('str',base='n')将特定进制的字符串转换为十进制,再用相应的进制转换函数将十进制转换为目标进制。

可以使用内置函数直接转换的有:

  • list---->tuple tuple(list)
  • tuple---->list list(tuple)

Part 2

附答案 | 最强Python面试题之Python基础题(2)

什么是全局变量/局部变量/作用域/命名空间

命名空间,又名 namesapce,是在很多的编程语言中都会出现的术语,趁着这个题顺便给大家仔细介绍一下。

全局变量 & 局部变量

全局变量和局部变量是我们理解命名空间的开始,我们先来看一段代码:

x = 2
def func():
   x = 3
   print('func x ---> ',x)

func()
print('out of func x ---> ',x)

这段代码输出的结果如下:

func x ---> 3
out of func x ---> 2

从上述的结果中可以看出,运行 func(),输出的是 func() 里面的变量 x 所引用的对象 3,之后执行的是代码中的最后一行。这里要区分清楚,前一个 x 输出的是函数内部的变量 x,后一个 x 输出的是函数外的变量 x,两个变量互相不影响,在各自的作用域中起作用。

那个只在函数内起作用的变量就叫 “局部变量”,有了 “局部” 就有相应的 “全部”,但是后者听起来有歧义,所以就叫了 “全局”。

x = 2
def func():
   global x = 3 #注意此处
   print('func x ---> ',x)

func()
print('out of func x ---> ',x)

这段代码中比上段代码多加了一个 global x,这句话的意思是在声明 x 是全局变量,通俗点说就是这个 x 和 函数外的 x 是同一个了,所以结果就成了下面这样:

func x ---> 3
out of func x ---> 3

这样乍一看好像全局变量好强,可以管着函数内外,但是我们还是要注意,全局变量还是谨慎使用的好,因为毕竟内外有别,不要带来混乱。

作用域

作用域,用比较直白的方式来说,就是程序中变量与对象存在关联的那段程序,比如我在上面说的, x = 2 和 x = 3 是在两个不同的作用域中。

通常的,作用域是被分为静态作用域和动态作用域,虽然我们说 Python 是动态语言,但是它的作用域属于静态作用域,即 Python 中的变量的作用域是由该变量所在程序中的位置所决定的。

在 Python 中作用域被划分成四个层级,分别是:local(局部作用域),enclosing(嵌套作用域),global(全局作用域)和 built - in(内建作用域)。对于一个变量,Python 也是按照之前四个层级依次在不用的作用域中查找,我们在上一段代码中,对于变量 x,首先搜索的是函数体内的局部作用域,然后是函数体外的全局作用域,至于这段话具体怎么来理解,请看下面的例子:

def out_func():
   x = 2
   def in_func():
       x = 3
       print('in_func x ---> ',x)
   in_func()
   print('out_func x ---> ',x)

x = 4
out_func()
print('x == ',x)

上述代码运行的结果是:

in_func x ---> 3
out_func x ---> 2
x == 4

仔细观察一下上面的代码和运行的结果,你就会发现变量在不同的范围内进行搜索的规律,是不是感觉这些都是以前被你忽略的呢?

命名空间

《维基百科》中说 “命名空间是对作用域的一种特殊的抽象”,在这里我用一个比方来具体说明一下:

比如张三在公司 A,他的工号是 111,李四在公司 B,他的工号也是 111,因为两个人在不同的公司,他们俩的工号可以相同但是不会引起混乱,这里的公司就表示一个独立的命名空间,如果两个人在一个公司的话,他们的工号就不能相同,否则光看工号也不知道到底是谁。

其实上面举的这个例子的特点就是我们使用命名空间的理由,在大型的计算机程序中,往往会出现成百上千的标识符,命名空间提供隐藏区域标识符的机制。通过将逻辑上相关的标识符构成响应的命名空间,可以使整个系统更加的模块化。

我在开头引用的《维基百科》的那句话说 “命名空间是对作用域的一种特殊的抽象”,它其实包含了处于该作用域内的标识符,且它本身也用一个标识符来表示。在 Python 中,命名空间本身的标识符也属于更外层的一个命名空间,所以命名空间也是可以嵌套的,它们共同生活在 “全局命名空间” 下。

简言之,不同的命名空间可以同时存在,但是彼此独立,互不干扰。当然了,命名空间因为其对象的不同也有所区别,可以分为以下几种:

  1. 本地命名空间:模块中有函数或者类的时候,每个函数或者类所定义的命名空间即是本地命名空间,当函数返回结果或者抛出异常的时候,本地命名空间也就结束了。
  2. 全局命名空间:每个模块创建了自己所拥有的全局命名空间,不同模块的全局命名空间彼此独立,不同模块中相同名称的命名空间也会因为模块的不同而不相互干扰。
  3. 内置命名空间:当 Python 运行起来的时候,它们就存在了,内置函数的命名空间都属于内置命名空间,所以我们可以在任何程序中直接运行它们。

程序查询命名空间的时候也有一套顺序,依次按照本地命名空间 ,全局命名空间,内置命名空间。

def fun(like):
   name = 'rocky'
   print(locals())

fun('python')

访问本地命名空间使用 locals 完成,我们来看一下结果:

{'name': 'rocky', 'like': 'python'}

从上面的结果中可以看出,命名空间中的数据存储的结构和字典是一样的。可能你已经猜到了,当我们要访问全局命名空间的时候,可以使用 globals。

关于命名空间还有一个生命周期的问题,就是一个命名空间什么时候出现,什么时候消失,这个很好理解,就是哪部分被读入内存,哪部分的命名空间就存在了,比如我们在上面说的,Python 启动,内置命名空间就建立。

PEP 8 编码风格是什么

PEP 8 编码风格

Python 代码从第一眼看上去,给人的感觉就是简洁优美,可读性强,也就是我们日常所说的「高颜值」。一方面是因为 Python 自身的优秀设计,比如统一的锁进,没有多余的符号从而让代码变的更加简洁;另一方面就是因为它有着一套较为统一的编码风格,当然它本身只是编码风格方面的建议而不是强制,相应的在编写 Python 代码的编辑器自动提供 PFP 8 检查,当你编写的代码违反了 PEP 8 规范的时候,会给出警告信息和修正的建议。与此同时,还有专门的检查工具对 Python 的代码风格进行检查。

由上,还是建议在编写 Python 代码的时候都遵循 PEP 8 编码规范,毕竟你以后不可能是只一个人写代码,未来不论是在公司或者某些开源项目中,作为其中的一份子,肯定还是要在风格上向大众看齐的。

PEP 8 编码规范详细的给出了 Python 编码的指导,包括什么对齐啦,包的导入顺序啦,空格和注释啦还有命名习惯等方方面面,并且还有详细的事例。

下面我以「包」的导入为例,看一下 PEP 8 给出的具体编程指导。在 Python 中, import 应该一次只导入一个模块,不同的模块应该独立一行。import 语句应该处于源码文件的顶部,位于模块注释和文档字符串之后,全局变量和常量之前。在导入不同的库的时候,应该按照以下的顺序分组,各个分组之间以空行分隔。

  • 导入标准库模块
  • 导入相关第三方库模块
  • 导入当前应用程序/库模块
import os
import time

import psutil

from test import u_test,my_test

Python 中还支持相对导入和绝对导入,在这里还是强推绝对导入。因为绝对导入的可读性更好一些,也不容易出错,即使出错了也会给出更加详细的错误信息。具体如下所示:

from sub_package import tools
from sub_package.tools  import msg

当然除了上述以外还有更多对于包的规范的描述,PEP 8 的编码风格指导比较长,并且写的非常详细,所以我就不在这一一介绍了,详细的可以参考 Python 官网上的资料。

dict 的 items() 方法与 iteritems()

items方法将所有的字典以列表方式返回,其中项在返回时没有特殊的顺序

iteritems方法有相似的作用,但是返回一个迭代器对象

os.path和sys.path的区别

os.path 主要是用于对系统路径文件的操作。os.path是module,包含了各种处理长文件名(路径名)的函数。

sys.path 主要是对 Python 解释器的系统环境参数的操作(动态的改变 Python 解释器搜索路径)。
sys.path是由目录名构成的列表,Python 从中查找扩展模块( Python 源模块, 编译模块,或者二进制扩展). 启动 Python 时,这个列表从根据内建规则,PYTHONPATH 环境变量的内容, 以及注册表( Windows 系统)等进行初始化。

输入某年某月某日,判断这一天是这一年的第几天

使用 Python 标准库 datetime

import datetime

def dayofyear():
    year = input("请输入年份:")
    month = input("请输入月份:")
    day = input("请输入天:")
    date1 = datetime.date(year=int(year),month=int(month),day=int(day))
    date2 = datetime.date(year=int(year),month=1,day=1)
    return (date1-date2+1).days

Part 3

附答案 | 最强Python面试题之Python基础题(3)

Python 中的 os 模块常见方法

os.remove() 删除文件
os.rename() 重命名文件
os.walk() 生成目录树下的所有文件
os.chdir() 改变目录
os.mkdir/makedirs 创建目录/多层目录
os.rmdir/removedirs 删除目录/多层目录
os.listdir() 列出指定目录的文件
os.getcwd() 取得当前工作目录
os.chmod() 改变目录权限
os.path.basename() 去掉目录路径,返回文件名
os.path.dirname() 去掉文件名,返回目录路径
os.path.join() 将分离的各部分组合成一个路径名
os.path.split() 返回(dirname(),basename())元组
os.path.splitext() 返回(filename,extension)元组
os.path.getatime\ctime\mtime 分别返回最近访问、创建、修改时间
os.path.getsize() 返回文件大小
os.path.exists() 是否存在
os.path.isabs() 是否为绝对路径
os.path.isdir() 是否为目录
os.path.isfile() 是否为文件

什么是可变、不可变类型

https://zhuanlan.zhihu.com/p/68577036

可变不可变指的是内存中的值是否可以被改变,不可变类型指的是对象所在内存块里面的值不可以改变,有数值字符串元组;可变类型则是可以改变,主要有列表字典

python中不可变数据类型的定义为:当该数据类型的对应变量的值发生了改变,那么它对应的内存地址也会发生改变,就称不可变数据类型。对于数值和字符串,给一个变量x赋值,然后改变这个x的值,输出前后两次的内存地址id和数据类型type,可以发现x的值发生改变的时候,变量的内存地址也发生了改变。所以整型是不可变的数据类型。对于元组,元组被称为只读列表,即数据可以被查询,但不能被修改。如果我们强行修改元组中的数据,会报错。

存入字典里的数据有没有先后排序?

存入的数据不会自动排序,可以使用 sort 函数对字典进行排序。

lambda 表达式格式以及应用场景

lambda 函数是一个可以接收任意多个参数(包括可选参数)并且返回单个表达式值的函数
1、lambda 函数比较轻便,即用即仍,很适合需要完成一项功能,但是此功能只在此一处使用,连名字都很随意的情况下
2、匿名函数,一般用来给 filter, map 这样的函数式编程服务
3、作为回调函数,传递给某些应用,比如消息处理

lambda 是一个可以只用一行就能解决问题的函数,让我们先看下面的例子:

>>> def add(x):
...     x += 1
...     return x
...
>>> numbers = range(5)
>>> list(numbers)
[0, 1, 2, 3, 4]
>>> new_numbers = []
>>> for i in numbers:
...     new_numbers.append(add(i))
...
>>> new_numbers
[1, 2, 3, 4, 5]

在上面的这个例子中,函数 add() 充当了一个中间角色,当然上面的例子也可以如下实现:

>>> new_numbers = [i+1 for i in numbers]
>>> new_numbers
[1, 2, 3, 4, 5]

首先我要说,上面的列表解析式其实是很好用的,但是我偏偏要用 lambda 这个函数代替 add(x) :

>>> lamb = lambda x: x+1
>>> new_numbers = []
>>> for i in numbers:
...     new_numbers.append(lamb(i))
...
>>> new_numbers
[1, 2, 3, 4, 5]

在这里的 lamb 就相当于 add(x) ,lamb = lambda x : x+1 就相当于 add(x) 里的代码块。下面再写几个应用 lambda 的小例子:

>>> lamb = lambda x,y : x + y
>>> lamb(1,2)
3
>>> lamb1 = lambda x : x ** 2
>>> lamb1(5)
25

由上面的例子我们可以总结一下 lambda 函数的具体使用方法:lambda 后面直接跟变量,变量后面是冒号,冒号后面是表达式,表达式的计算结果就是本函数的返回值。
在这里有一点需要提醒的是,虽然 lambda 函数可以接收任意多的参数并且返回单个表达式的值,但是 lambda 函数不能包含命令且包含的表达式不能超过一个。如果你需要更多复杂的东西,你应该去定义一个函数。
lambda 作为一个只有一行的函数,在你具体的编程实践中可以选择使用,虽然在性能上没什么提升,但是看着舒服呀。

如何理解 Python 中字符串中的\字符

1、转义字符
2、路径名中用来连接路径名
3、编写太长代码手动软换行

常用的 Python 标准库都有哪些

os 操作系统、time 时间、random 随机、pymysql 连接数据库、threading 线程、multiprocessing
进程、queue 队列

第三方库:
django、flask、requests、virtualenv、selenium、scrapy、xadmin、celery、re、hashlib、md5

常用的科学计算库:Numpy,Pandas、matplotlib

如何在Python中管理内存?

python中的内存管理由Python私有堆空间管理。所有Python对象和数据结构都位于私有堆中。程序员无权访问此私有堆。python解释器负责处理这个问题。

Python对象的堆空间分配由Python的内存管理器完成。核心API提供了一些程序员编写代码的工具。

Python还有一个内置的垃圾收集器,它可以回收所有未使用的内存,并使其可用于堆空间。

介绍一下 except 的作用和用法

except: 捕获所有异常
except:<异常名>: 捕获指定异常
except:<异常名 1, 异常名 2>: 捕获异常 1 或者异常 2
except: <异常名> ,< 数据>: 捕获指定异常及其附加的 数据
except: <异常名 1,异常名 2> :< 数据>: 捕获异常名 1 或者异常名 2,及附加的 数据

在 except 中 return 后还会不会执行 finally 中的代码?怎么抛出自定义异常?

会继续处理 finally 中的代码;
用 raise 方法可以抛出自定义异常。

Part 4

附答案 | 最强Python面试题之Python基础题(4)

read、readline 和 readlines 的区别

read:读取整个文件。

readline:读取下一行,使用生成器方法。

readlines:读取整个文件到一个迭代器以供我们遍历。

range 和 xrange 的区别

两者用法相同,不同的是 range 返回的结果是一个列表,而 xrange 的结果是一个生成器,前者是直接开辟一块内存空间来保存列表,后者是边循环边使用,只有使用时才会开辟内存空间,所以当列表
很长时,使用 xrange 性能要比 range 好。

请简述你对 input()函数的理解

在 Python3 中,input()获取用户输入,不论用户输入的是什么,获取到的都是字符串类型的。

在 Python2 中有 raw_input()和 input(), raw_input()和 Python3 中的 input()作用是一样的,
input()输入的是什么数据类型的,获取到的就是什么数据类型的。

代码中要修改不可变数据会出现什么问题?抛出什么异常

代码不会正常运行,抛出 TypeError 异常。

print 方法默认调用 sys.stdout.write 方法,即往控制台打印字符串。

Python 的 sys 模块常用方法

sys.argv 命令行参数 List,第一个元素是程序本身路径

sys.modules.keys() 返回所有已经导入的模块列表

sys.exc_info() 获取当前正在处理的异常类,exc_type、exc_value、exc_traceback 当前处理的
异常详细信息

sys.exit(n) 退出程序,正常退出时 exit(0)  sys.hexversion 获取 Python 解释程序的版本值,16 进制格式如:0x020403F0

sys.version 获取 Python 解释程序的版本信息

sys.maxint 最大的 Int 值

sys.maxunicode 最大的 Unicode 值

sys.modules 返回系统导入的模块字段,key 是模块名,value 是模块

sys.path 返回模块的搜索路径,初始化时使用 PYTHONPATH 环境变量的值

sys.platform 返回操作系统平台名称

sys.stdout 标准输出

sys.stdin 标准输入

sys.stderr 错误输出

sys.exc_clear() 用来清除当前线程所出现的当前的或最近的错误信息

sys.exec_prefix 返回平***立的 python 文件安装的位置

sys.byteorder 本地字节规则的指示器,big-endian 平台的值是'big',little-endian 平台的值是
'little'  sys.copyright 记录 python 版权相关的东西

sys.api_version 解释器的 C 的 API 版本

sys.version_info 元组则提供一个更简单的方法来使你的程序具备 Python 版本要求功能

模块和包是什么?

在 Python 中,模块是搭建程序的一种方式。每一个 Python 代码文件都是一个模块,并可以引用
其他的模块,比如对象和属性。

一个包含许多 Python 代码的文件夹是一个包。一个包可以包含模块和子文件夹。

什么是正则的贪婪匹配?

>>>re.search('ab*c', 'abcaxc')
<_sre.SRE_Match object; span=(0, 3), match='abc'>

>>>re.search('ab\D+c', 'abcaxc')
<_sre.SRE_Match object; span=(0, 6), match='abcaxc'>

贪婪匹配:正则表达式一般趋向于最大长度匹配,也就是所谓的贪婪匹配。

非贪婪匹配:就是匹配到结果就好,就少的匹配字符。

常用字符串格式化哪几种?

字符串格式化(format)

(1) 使用位置参数

位置参数不受顺序约束,且可以为{},参数索引从0开始,format里填写{}对应的参数值。

>>> msg = "my name is {}, and age is {}"
>>> msg.format("hqs",22)
'my name is hqs, and age is 22'

(2) 使用关键字参数

关键字参数值要对得上,可用字典当关键字参数传入值,字典前加**即可

>>> hash = {'name':'john' , 'age': 23}
>>> msg = 'my name is {name}, and age is {age}'
>>> msg.format(**hash)
'my name is john,and age is 23'

(3) 填充与格式化

:[填充字符][对齐方式 <^>][宽度]

>>> '{0:*<10}'.format(10)      # 左对齐
'10********'

“一行代码实现 xx”类题目

实现 1 - 100 的和:可以利用 sum() 函数。
实现数值交换:不用二话,直接换。
筛选列表中的奇偶数:使用列表推导式。
展开列表:使用列表推导式,稍微复杂一点,注意顺序。
打乱列表:用到 random 的 shuffle。
反转字符串:使用切片。
查看目录下所有文件:使用 os 的 listdir。
去除字符串间的空格:法 1 replace 函数。法 2 join & split 函数。
实现字符串整数列表变成整数列表:使用 list & map & lambda。
删除列表中重复的值:使用 list & set。
实现 9 * 9 乘法表:稍稍复杂的列表推导式,耐心点就行,一点点的搞...
找出两个列表中相同的元素:使用 set 和 &。
找出两个列表中不同的元素:使用 set 和 ^。
合并两个字典:使用 Update 函数。
字典按键值从小到大排序:使用 sort 函数。

Part 5

附答案 | 最强Python面试题之Python进阶题第一弹

Python 中类方法、类实例方法、静态方法有何区别

类方法:是类对象的方法,在定义时需要在上方使用“@ classmethod”进行装饰,形参为 cls,表示类对象,类对象和实例对象都可调用

类实例方法:是类实例化对象的方法,只有实例对象可以调用,形参为 self,指代对象本身

静态方法:是一个任意函数,在其上方使用“@ staticmethod”进行装饰,可以用对象直接调用,静态方法实际上跟该类没有太大关系

Python的内存管理机制及调优手段?

内存管理机制:引用计数垃圾回收内存池

引用计数

引用计数是一种非常高效的内存管理手段, 当一个 Python 对象被引用时其引用计数增加 1, 当其不再被一个变量引用时则计数减 1. 当引用计数等于 0 时对象被删除。

垃圾回收

(1)引用计数

引用计数也是一种垃圾收集机制,而且也是一种最直观,最简单的垃圾收集技术。当 Python 的某
个对象的引用计数降为 0 时,说明没有任何引用指向该对象,该对象就成为要被回收的垃圾了。比如
某个新建对象,它被分配给某个引用,对象的引用计数变为 1。如果引用被删除,对象的引用计数为 0,
那么该对象就可以被垃圾回收。不过如果出现循环引用的话,引用计数机制就不再起有效的作用了

(2)标记清除

如果两个对象的引用计数都为 1,但是仅仅存在他们之间的循环引用,那么这两个对象都是需要被
回收的,也就是说,它们的引用计数虽然表现为非 0,但实际上有效的引用计数为 0。所以先将循环引
用摘掉,就会得出这两个对象的有效计数。

(3) 分代回收

从前面“标记-清除”这样的垃圾收集机制来看,这种垃圾收集机制所带来的额外操作实际上与系统
中总的内存块的数量是相关的,当需要回收的内存块越多时,垃圾检测带来的额外操作就越多,而垃圾
回收带来的额外操作就越少;反之,当需回收的内存块越少时,垃圾检测就将比垃圾回收带来更少的额
外操作。

举个例子:

当某些内存块 M 经过了 3 次垃圾收集的清洗之后还存活时,我们就将内存块 M 划到一个集合 A 中去,而新分配的内存都划分到集合 B 中去。当垃圾收集开始工作时,大多数情况都只对集合 B 进行垃圾回收,而对集合 A 进行垃圾回收要隔相当长一段时间后才进行,这就使得垃圾收集机制需要处理的内存少了,效率自然就提高了。在这个过程中,集合 B 中的某些内存块由于存活时间长而会被转移到集合 A 中,当然,集合 A 中实际上也存在一些垃圾,这些垃圾的回收会因为这种分代的机制而被延迟。

内存池

(1) Python 的内存机制呈现金字塔形状,-1,-2 层主要有操作系统进行操作

(2) 第 0 层是 C 中的 malloc,free 等内存分配和释放函数进行操作

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

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

Python 在运行期间会大量地执行 malloc 和 free 的操作,频繁地在用户态和核心态之间进行切换,这将严重影响 Python 的执行效率。为了加速 Python 的执行效率,Python 引入了一个内存池机制,用于管理对小块内存的申请和释放。

Python 内部默认的小块内存与大块内存的分界点定在 256 个字节,当申请的内存小于 256 字节时,PyObject_Malloc 会在内存池中申请内存;当申请的内存大于 256 字节时,PyObject_Malloc 的行为将蜕化为 malloc 的行为。当然,通过修改 Python 源代码,我们可以改变这个默认值,从而改变 Python 的默认内存管理行为。

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

由于疏忽或错误造成程序未能释放已经不再使用的内存的情况。

内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,失去了对该段内存的控制,因而造成了内存的浪费。导致程序运行速度减慢甚至系统崩溃等严重后果。

del() 函数的对象间的循环引用是导致内存泄漏的主凶。

不使用一个对象时使用:del object 来删除一个对象的引用计数就可以有效防止内存泄漏问题。

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

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

Python 函数调用的时候参数的传递方式是值传递还是引用传递?

Python 的参数传递有:位置参数、默认参数、可变参数、关键字参数。函数的传值到底是值传递还是引用传递,要分情况:

不可变参数用值传递

像整数和字符串这样的不可变对象,是通过拷贝进行传递的,因为你无论如何都不可能在原处改变不可变对象

可变参数是引用传递的

比如像列表,字典这样的对象是通过引用传递、和 C 语言里面的用指针传递数组很相似,可变对象能在函数内部改变。

对缺省参数的理解?如何使用*args和**kwargs?

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

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

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

补充:[...]

为什么函数名字可以当做参数用?

Python 中一切皆对象,函数名是函数在内存中的空间,也是一个对象。

Python 中 pass 语句的作用是什么?

在编写代码时只写框架思路,具体实现还未编写就可以用 pass 进行占位,使程序不报错,不会进行任何操作。

面向对象中super的作用?

https://python3-cookbook.readthedocs.io/zh_CN/latest/c08/p07_calling_method_on_parent_class.html
https://zhuanlan.zhihu.com/p/45535784

super() 函数是用于调用父类(超类)的一个方法。super 是用来解决多重继承问题的,直接用类名调用父类方法在使用单继承的时候没问题,但是如果使用多继承,会涉及到查找顺序(MRO)、重复调用(钻石继承)等种种问题。

MRO 就是类的方法解析顺序表, 其实也就是继承父类方法时的顺序表。 对于你定义的每一个类,Python会计算出一个所谓的方法解析顺序(MRO)列表。 这个MRO列表就是一个简单的所有基类的线性顺序表。例如:

>>> C.__mro__
(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>,
<class '__main__.Base'>, <class 'object'>)
>>>

为了实现继承,Python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。而这个MRO列表的构造是通过一个C3线性化算法来实现的。 我们不去深究这个算法的数学原理,它实际上就是合并所有父类的MRO列表并遵循如下三条准则:

  • 子类会先于父类被检查
  • 多个父类会根据它们在列表中的顺序被检查
  • 如果对下一个类存在两个合法的选择,选择第一个父类

老实说,你所要知道的就是MRO列表中的类顺序会让你定义的任意类层级关系变得有意义。当你使用 super() 函数时,Python会在MRO列表上继续搜索下一个类。 只要每个重定义的方法统一使用 super() 并只调用它一次, 那么控制流最终会遍历完整个MRO列表,每个方法也只会被调用一次。(可以想象没有super,直接在类内硬写另一个类的函数,在继承比较复杂的时候就会被调用多次)

super本身其实就是一个类,super()其实就是这个类的实例化对象,它需要接收两个参数 super(class, obj),它返回的是obj的MRO中class类的父类

  • class:就是类,这里你可以是A,B,C或者D
  • obj:就是一个具体的实例对象,即a,b,c,d。我们经常在类的__init__函数里看到super的身影,而且一般都是写成这个样子的super(className, self).__init__(),self其实就是某个实例化的对象。

所以常用用法就是class xxx(...) + super(xxx, self)。缺省状态下,super()就表示前一个父类,这里就是C类,那么super().p()就会调用C的p函数

是否使用过functools中的函数?其作用是什么?

Python的functools模块用以为可调用对象(callable objects)定义高阶函数或操作。

简单地说,就是基于已有的函数定义新的函数。

所谓高阶函数,就是以函数作为输入参数,返回也是函数。

比较常见的有:reduce()partial()wraps()
https://zhuanlan.zhihu.com/p/356720970

Part 6

附答案 | 最强Python面试题之Python进阶题第二弹

什么是断言?应用场景?

info = {}
info['name'] = 'egon'
info['age'] = 18

# 用assert取代上述代码:
assert ('name' in info) and ('age' in info)

有用过with statement吗?它的好处是什么?

with语句的作用是通过某种方式简化异常处理,它是所谓的上下文管理器的一种
用法举例如下:

 with open('output.txt', 'w') as f:
        f.write('Hi there!')

当你要成对执行两个相关的操作的时候,这样就很方便,以上便是经典例子,with语句会在嵌套的代码执行之后,自动关闭文件。

这种做法的还有另一个优势就是,无论嵌套的代码是以何种方式结束的,它都关闭文件。

如果在嵌套的代码中发生异常,它能够在外部exception handler catch异常前关闭文件。

如果嵌套代码有return/continue/break语句,它同样能够关闭文件。

简述 Python 在异常处理中,else 和 finally 的作用分别是什么?

如果一个 Try - exception 中,没有发生异常,即 exception 没有执行,那么将会执行 else 语句的内容。反之,如果触发了 Try - exception(异常在 exception 中被定义),那么将会执行exception
中的内容,而不执行 else 中的内容。

如果 try 中的异常没有在 exception 中被指出,那么系统将会抛出 Traceback(默认错误代码),并且终止程序,接下来的所有代码都不会被执行,但如果有 Finally 关键字,则会在程序抛出 Traceback 之前(程序最后一口气的时候),执行 finally 中的语句。这个方法在某些必须要结束的操作中颇为有用,如释放文件句柄,或释放内存空间等。

map 函数和 reduce 函数,filter函数?

(1) 从参数方面来讲:

map()包含两个参数,第一个参数是一个函数,第二个是序列(列表 或元组)。其中,函数(即 map 的第一个参数位置的函数)可以接收一个或多个参数。

reduce()第一个参数是函数,第二个是序列(列表或元组)。但是,其函数必须接收两个参数。

(2) 从对传进去的数值作用来讲:

map()是将传入的函数依次作用到序列的每个元素,每个元素都是独自被函数“作用”一次 。

reduce()是将传人的函数作用在序列的第一个元素得到结果后,把这个结果继续与下一个元素作用(累积计算)。

map()

我们在上面讲 lambda 的时候用的例子,其实 map 也可以实现,请看下面的操作:

>>> numbers = [0,1,2,3,4]
>>> map(add,numbers)
[1, 2, 3, 4, 5]
>>> map(lambda x: x + 1,numbers)
[1, 2, 3, 4, 5]

map 是 Python 的一个内置函数,它的基本格式是:map(func, seq)。
func 是一个函数对象,seq 是一个序列对象,在执行的时候,seq 中的每个元素按照从左到右的顺序依次被取出来,塞到 func 函数里面,并将 func 的返回值依次存到一个列表里。
对于 map 要主要理解以下几个点就好了:
1.对可迭代的对象中的每一个元素,依次使用 fun 的方法(其实本质上就是一个 for 循环)。
2.将所有的结果返回一个 map 对象,这个对象是个迭代器。
我们接下来做一个简单的小题目:将两个列表中的对应项加起来,把结果返回在一个列表里,我们用 map 来做,如果你做完了,请往下看:

>>> list1 = [1,2,3,4]
>>> list2 = [5,6,7,8]
>>> list(map(lambda x,y: x + y,list1,list2))
[6, 8, 10, 12]

你看上面,是不是很简单?其实这个还看不出 map 的方便来,因为用 for 同样也不麻烦,要是你有这样的想法的话,那么请看下面:

>>> list1 = [1,2,3,4]
>>> list2 = [5,6,7,8]
>>> list3 = [9,10,11,12]
>>> list(map(lambda x,y,z : x + y + z,list1,list2,list3))
[15, 18, 21, 24]

你看三个呢?是不是用 for 的话就稍显麻烦了?那么我们在想如果是 四个,五个乃至更多呢?这就显示出 map 的简洁优雅了,并且 map 还不和 lambda 一样对性能没有什么提高,map 在性能上的优势也是杠杠的。

filter()

filter 翻译过来的意思是 “过滤器”,在 Python 中,它也确实是起到的是过滤器的作用。这个解释起来略微麻烦,还是直接上代码的好,在代码中体会用法是我在所有的文章里一直在体现的:

>>> numbers = range(-4,4)
>>> list(filter(lambda x: x > 0,numbers))
[1, 2, 3]

上面的例子其实和下面的代码是等价的:

>>> [x for x in numbers if x > 0]
[1, 2, 3]

然后我们再来写一个例子体会一下:

>>> list(filter(lambda x: x != 'o','Rocky0429'))
['R', 'c', 'k', 'y', '0', '4', '2', '9']

reduce()

reduce 函数。我在之前的文章中很多次都说过,我的代码都是用 Python3 版本的。在 Python3 中,reduce 函数被放到 functools 模块里,在 Python2 中还是在全局命名空间。
同样我先用一个例子来跑一下,我们来看看怎么用:

>>> reduce(lambda x,y: x+y,[1,2,3,4])
10

reduce 函数的第一个参数是一个函数,第二个参数是序列类型的对象,将函数按照从左到右的顺序作用在序列上。对比上面的两个例子,就知道两者的区别,map 相当于是上下运算的,而 reduce 是从左到右逐个元素进行运算。

递归函数停止的条件?

递归的终止条件一般定义在递归函数内部,在递归调用前要做一个条件判断,根据判断的结果选择是继续调用自身,还是 return;返回终止递归。
终止的条件:
(1) 判断递归的次数是否达到某一限定值
(2) 判断运算的结果是否达到某个范围等,根据设计的目的来选择

回调函数,如何通信的?

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

setattrgetattr,__delattr__函数使用详解?

1.setattr(self,name,value):如果想要给 name 赋值的话,就需要调用这个方法。
2.getattr(self,name):如果 name 被访问且它又不存在,那么这个方法将被调用。
3.delattr(self,name):如果要删除 name 的话,这个方法就要被调用了。
下面我们用例子来演示一下:

>>> class Sample:
...    def __getattr__(self,name):
...            print('hello getattr')
...    def __setattr__(self,name,value):
...            print('hello setattr')
...            self.__dict__[name] = value
...

上面的例子中类 Sample 只有两个方法,下面让我们实例化一下:

>>> s = Sample()
>>> s.x
hello getattr

s.x 这个实例属性本来是不存在的,但是由于类中有了 getattr(self,name) 方法,当发现属性 x 不存在于对象的 dict 中时,就调用了 getattr,也就是所谓的「拦截成员」。

>>> s.x = 7
hello setattr

同理,给对象的属性赋值的时候,调用了 setattr(self,name,value) 方法,这个方法中有 self.dict[name] = value,通过这个就将属性和数据存到了对象 dict 中。如果再调用这个属性的话,会成为下面这样:

>>> s.x
7

出现这种结果的原因是它已经存在于对象的 dict 中了。
看了上面的两个,你是不是觉得上面的方法很有魔力呢?这就是「黑魔法」,但是它具体有什么应用呢?我们接着来看:

class Rectangle:
   """
   the width and length of Rectangle
   """

   def __init__(self):
       self.width = 0
       self.length = 0

   def setSize(self,size):
       self.width, self.length = size

   def getSize(self):
       return self.width, self.length

if __name__ == "__main__":
   r = Rectangle()
   r.width = 3
   r.length = 4
   print(r.getSize())
   print(r.setSize((30,40)))
   print(r.width)
   print(r.length)

上面是我根据一个很有名的例子改编的,你可以先想一下结果,想完以后可以接着往下看:

(3, 4)
30
40

这段代码运行的结果如上面所示,作为一个强迫证的码农,对于这种可以改进的程序当然不能容忍。我们在上面介绍的特殊方法,我们一定要学以致用,虽然重新写的不一定比原来的好,但我们还是要尝试去用:

class NewRectangle:
   """
   the width and length of Rectangle
   """

   def __init__(self):
       self.width = 0
       self.length = 0

   def __setattr__(self, name, value):
       if name == 'size':
           self.width, self.length = value
       else:
           self.__dict__[name] = value

   def __getattr__(self, name):
       if name == 'size':
           return self.width, self.length
       else:
           return AttributeError

if __name__ == "__main__":
   r = NewRectangle()
   r.width = 3
   r.length = 4
   print(r.size)
   r.size = 30,40
   print(r.width)
   print(r.length)

我们来看一下运行的结果:

(3, 4)
30
40

我们可以看到,除了类的写法变了以外,调用的方式没有变,结果也没有变。

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

(1) 抽象类
规定了一系列的方法,并规定了必须由继承类实现的方法。由于有抽象方法的存在,所以抽象类不能实例化。可以将抽象类理解为毛坯房,门窗、墙面的样式由你自己来定,所以抽象类与作为基类的普通类的区别在于约束性更强。
(2) 接口类
与抽象类很相似,表现在接口中定义的方法,必须由引用类实现,但他与抽象类的根本区别在于用途:与不同个体间沟通的规则(方法),你要进宿舍需要有钥匙,这个钥匙就是你与宿舍的接口,你的同室也有这个接口,所以他也能进入宿舍,你用手机通话,那么手机就是你与他人交流的接口。
(3) 区别和关联

  • 接口是抽象类的变体,接口中所有的方法都是抽象的。而抽象类中可以有非抽象方法。抽象类是声明方法的存在而不去实现它的类。
  • 接口可以继承,抽象类不行。
  • 接口定义方法,没有实现的代码,而抽象类可以实现部分方法。
  • 接口中基本数据类型为 static 而抽类象不是。
  • 接口可以继承,抽象类不行。
  • 可以在一个类中同时实现多个接口。
  • 接口的使用方式通过 implements 关键字进行,抽象类则是通过继承 extends 关键字进行。

请描述方法重载与方法重写?

(1)方法重载
是在一个类里面,方法名字相同,而参数不同。返回类型呢?可以相同也可以不同。重载是让类以统一的方式处理不同类型数据的一种手段。
(2) 方法重写
子类不想原封不动地继承父类的方法,而是想作一定的修改,这就需要采用方法的重写。方法重写又称方法覆盖。

Part 7

附答案 | 最强Python面试题之Python进阶题第三弹

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

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

(1)资源共享的情况下,避免由于资源操作时导致的性能或损耗等。如日志文件,应用配置。

(2)控制资源的情况下,方便资源之间的互相通信。如线程池等。 1.网站的计数器 2.应用配置 3.多线程池 4.数据库配置,数据库连接池 5.应用程序的日志应用....

01.单例设计模式

「单例设计模式」估计对很多人来说都是一个陌生的概念,其实它就环绕在你的身边。比如我们每天必用的听歌软件,同一时间只能播放一首歌曲,所以对于一个听歌的软件来说,负责音乐播放的对象只有一个;再比如打印机也是同样的道理,同一时间打印机也只能打印一份文件,同理负责打印的对象也只有一个。

结合说的听歌软件和打印机都只有唯一的一个对象,就很好理解「单例设计模式」。

单例设计模式确保一个类只有一个实例,并提供一个全局访问点。

「单例」就是单个实例,我们在定义完一个类的之后,一般使用「类名()」的方式创建一个对象,而单例设计模式解决的问题就是无论执行多少遍「类名()」,返回的对象内存地址永远是相同的。

02.new 方法

当我们使用「类名()」创建对象的时候,Python 解释器会帮我们做两件事情:第一件是为对象在内存分配空间,第二件是为对象进行初始化。初始化(init)我们已经学过了,那「分配空间」是哪一个方法呢?就是我们这一小节要介绍的 new 方法。

那这个 new 方法和单例设计模式有什么关系呢?单例设计模式返回的对象内存地址永远是相同的,这就意味着在内存中这个类的对象只能是唯一的一份,为达到这个效果,我们就要了解一下为对象分配空间的 new 方法。

明确了这个目的以后,接下来让我们看一下 new 方法。new 方法在内部其实做了两件时期:第一件事是为「对象分配空间」,第二件事是「把对象的引用返回给 Python 解释器」。当 Python 的解释器拿到了对象的引用之后,就会把对象的引用传递给 init 的第一个参数 self,init 拿到对象的引用之后,就可以在方法的内部,针对对象来定义实例属性。

这就是 new 方法和 init 方法的分工。

总结一下就是:之所以要学习 new 方法,就是因为需要对分配空间的方法进行改造,改造的目的就是为了当使用「类名()」创建对象的时候,无论执行多少次,在内存中永远只会创造出一个对象的实例,这样就可以达到单例设计模式的目的。

03.重写 new 方法

在这里我用一个 new 方法的重写来做一个演练:首先定义一个打印机的类,然后在类里重写一下 new 方法。通过对这个方法的重写来强化一下 new 方法要做的两件事情:在内存中分配内存空间 & 返回对象的引用。同时验证一下,当我们使用「类名()」创建对象的时候,Python 解释器会自动帮我们调用 new 方法。

首先我们先定义一个打印机类 Printer,并创建一个实例:

class Printer():
    def __init__(self):
        print("打印机初始化")
# 创建打印机对象
printer = Printer()

接下来就是重写 new 方法,在此之前,先说一下注意事项,只要注意了这几点,重写 new 就没什么难度:

重写 new 方法一定要返回对象的引用,否则 Python 的解释器得不到分配了空间的对象引用,就不会调用对象的初始化方法;

new 是一个静态方法,在调用时需要主动传递 cls 参数。

# 重写 __new__ 方法
class Printer():
    def __new__(cls, *args, **kwargs):
        # 可以接收三个参数
        # 三个参数从左到右依次是 class,多值元组参数,多值的字典参数
        print("this is rewrite new")
        instance = super().__new__(cls)
        return instance
    def __init__(self):
        print("打印机初始化")
# 创建打印机对象
player = Printer()
print(player)

上述代码对 new 方法进行了重写,我们先来看一下输出结果:

this is rewrite new
打印机初始化
<__main__.Printer object at 0x10fcd2ba8>

上述的结果打印出了 new 方法和 init 方法里的内容,同时还打印了类的内存地址,顺序正好是我们在之前说过的。new 方法里的三行代码正好做了在本小节开头所说的三件事:

  • print(this is rewrite new):证明了创建对象时,new 方被自动调用;
  • instance = super().new(cls):为对象分配内存空间(因为 new 本身就有为对象分配内存空间的能力,所以在这直接调用父类的方法即可);
  • return instance:返回对象的引用。

04.设计单例模式

说了这么多,接下来就让我们用单例模式来设计一个单例类。乍一看单例类看起来比一般的类更唬人,但其实就是差别在一点:单例类在创建对象的时候,无论我们调用多少次创建对象的方法,得到的结果都是内存中唯一的对象。

可能到这有人会有疑惑:怎么知道用这个类创建出来的对象是同一个对象呢?其实非常的简单,我们只需要多调用几次创建对象的方法,然后输出一下方法的返回结果,如果内存地址是相同的,说明多次调用方法返回的结果,本质上还是同一个对象。

class Printer():
    pass

printer1 = Printer()
print(printer1)
printer2 = Printer()
print(printer2)

上面是一个一般类的多次调用,打印的结果如下所示:

<__main__.Printer object at 0x10a940780>
<__main__.Printer object at 0x10a94d3c8>

可以看出,一般类中多次调用的内存地址不同(即 printer1 和 printer2 是两个完全不同的对象),而单例设计模式设计的单例类 Printer(),要求是无论调用多少次创建对象的方法,控制台打印出来的内存地址都是相同的。

那么我们该怎么实现呢?其实很简单,就是多加一个「类属性」,用这个类属性来记录「单例对象的引用」。

为什么要这样呢?其实我们一步一步的来想,当我们写完一个类,运行程序的时候,内存中其实是没有这个类创建的对象的,我们必须调用创建对象的方法,内存中才会有第一个对象。在重写 new 方法的时候,我们用 instance = super().new(cls) ,为对象分配内存空间,同时用 istance 类属性记录父类方法的返回结果,这就是第一个「对象在内存中的返回地址」。当我们再次调用创建对象的方法时,因为第一个对象已经存在了,我们直接把第一个对象的引用做一个返回,而不用再调用 super().new(cls) 分配空间这个方法,所以就不会在内存中为这个类的其它对象分配额外的内存空间,而只是把之前记录的第一个对象的引用做一个返回,这样就能做到无论调用多少次创建对象的方法,我们永远得到的是创建的第一个对象的引用。

这个就是使用单例设计模式解决在内存中只创建唯一一个实例的解决办法。下面我就根据上面所说的,来完成单例设计模式。

class Printer():
    instance = None
    def __new__(cls, *args, **kwargs):
        if cls.instance is None:
            cls.instance = super().__new__(cls)
    return cls.instance
printer1 = Printer()
print(printer1)
printer2 = Printer()
print(printer2)

上述代码很简短,首先给类属性复制为 None,在 new 方法内部,如果 instance 为 None,证明第一个对象还没有创建,那么就为第一个对象分配内存空间,如果 instance 不为 None,直接把类属性中保存的第一个对象的引用直接返回,这样在外界无论调用多少次创建对象的方法,得到的对象的内存地址都是相同的。
下面我们运行一下程序,来看一下结果是不是能印证我们的说法:

<__main__.Printer object at 0x10f3223c8>
<__main__.Printer object at 0x10f3223c8>

上述输出的两个结果可以看出地址完全一样,这说明 printer1 和 printer2 本质上是相同的一个对象。

什么是闭包?

我们都知道在数学中有闭包的概念,但此处我要说的闭包是计算机编程语言中的概念,它被广泛的使用于函数式编程。

关于闭包的概念,官方的定义颇为严格,也很难理解,在《Python语言及其应用》一书中关于闭包的解释我觉得比较好 -- 闭包是一个可以由另一个函数动态生成的函数,并且可以改变和存储函数外创建的变量的值。乍一看,好像还是比较很难懂,下面我用一个简单的例子来解释一下:

>>> a = 1
>>> def fun():
...     print(a)
...
>>> fun()
1
>>> def fun1():
...     b = 1
...
>>> print(b)
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
NameError: name 'b' is not defined

毋庸置疑,第一段程序是可以运行成功的,a = 1 定义的变量在函数里可以被调用,但是反过来,第二段程序则出现了报错。

在函数 fun() 里可以直接使用外面的 a = 1,但是在函数 fun1() 外面不能使用它里面所定义的 b = 1,如果我们根据作用域的关系来解释,是没有什么异议的,但是如果在某种特殊情况下,我们必须要在函数外面使用函数里面的变量,该怎么办呢?

我们先来看下面的例子:

>>> def fun():
...    a = 1
...    def fun1():
...            return a
...    return fun1
...
>>> f = fun()
>>> print(f())
1

如果你看过昨天的文章,你一定觉得的很眼熟,上述的本质就是我们昨天所讲的嵌套函数。

在函数 fun() 里面,有 a = 1 和 函数 fun1() ,它们两个都在函数 fun() 的环境里面,但是它们两个是互不干扰的,所以 a 相对于 fun1() 来说是自由变量,并且在函数 fun1() 中应用了这个自由变量 -- 这个 fun1() 就是我们所定义的闭包。

闭包实际上就是一个函数,但是这个函数要具有 1.定义在另外一个函数里面(嵌套函数);2.引用其所在环境的自由变量。

上述例子通过闭包在 fun() 执行完毕时,a = 1依然可以在 f() 中,即 fun1() 函数中存在,并没有被收回,所以 print(f()) 才得到了结果。

当我们在某些时候需要对事务做更高层次的抽象,用闭包会相当舒服。比如我们要写一个二元一次函数,如果不使用闭包的话相信你可以轻而易举的写出来,下面让我们来用闭包的方式完成这个一元二次方程:

>>> def fun(a,b,c):
...    def para(x):
...            return a*x**2 + b*x + c
...    return para
...
>>> f = fun(1,2,3)
>>> print(f(2))
11

上面的函数中,f = fun(1,2,3) 定义了一个一元二次函数的函数对象,x^2 + 2x + 3,如果要计算 x = 2 ,该一元二次函数的值,只需要计算 f(2) 即可,这种写法是不是看起来更简洁一些。

什么是装饰器?

「装饰器」作为 Python 高级语言特性中的重要部分,是修改函数的一种超级便捷的方式,适当使用能够有效提高代码的可读性和可维护性,非常的便利灵活。

「装饰器」本质上就是一个函数,这个函数的特点是可以接受其它的函数当作它的参数,并将其替换成一个新的函数(即返回给另一个函数)。

可能现在这么看的话有点懵,为了深入理解「装饰器」的原理,我们首先先要搞明白「什么是函数对象」,「什么是嵌套函数」,「什么是闭包」。关于这三个问题我在很久以前的文章中已经写过了,你只需要点击下面的链接去看就好了,这也是面试中常问的知识哦:

搞明白上面的三个问题,其实简单点来说就是告诉你:函数可以赋值给变量,函数可嵌套,函数对象可以作为另一个函数的参数。

首先我们来看一个例子,在这个例子中我们用到了前面列出来的所有知识:

def first(fun):
    def second():
        print('start')
        fun()
        print('end')
        print fun.__name__
    return second

def man():
    print('i am a man()')

f = first(man)
f()

上述代码的执行结果如下所示:

start
i am a man()
end
man

上面的程序中,这个就是 first 函数接收了 man 函数作为参数,并将 man 函数以一个新的函数进行替换。看到这你有没有发现,这个和我在文章刚开始时所说的「装饰器」的描述是一样的。既然这样的话,那我们就把上述的代码改造成符合 Python 装饰器的定义和用法的样子,具体如下所示:

def first(func):
    def second():
        print('start')
        func()
        print('end')
        print (func.__name__)
    return second

@first
def man():
    print('i am a man()')

man()

上面这段代码和之前的代码的作用一模一样。区别在于之前的代码直接“明目张胆”的使用 first 函数去封装 man 函数,而上面这个是用了「语法糖」来封装 man 函数。至于什么是语法糖,不用细去追究,你就知道是类似「@first」这种形式的东西就好了。

在上述代码中「@frist」在 man 函数的上面,表示对 man 函数使用 first 装饰器。「@」 是装饰器的语法,「first」是装饰器的名称。

下面我们再来看一个复杂点的例子,用这个例子我们来更好的理解一下「装饰器」的使用以及它作为 Python 语言高级特性被人津津乐道的部分:

def check_admin(username):
    if username != 'admin':
        raise Exception('This user do not have permission')

class Stack:
    def __init__(self):
        self.item = []

    def push(self,username,item):
        check_admin(username=username)
        self.item.append(item)

    def pop(self,username):
        check_admin(username=username)
        if not self.item:
            raise Exception('NO elem in stack')
        return self.item.pop()

上述实现了一个特殊的栈,特殊在多了检查当前用户是否为 admin 这步判断,如果当前用户不是 admin,则抛出异常。上面的代码中将检查当前用户的身份写成了一个独立的函数 check_admin,在 push 和 pop 中只需要调用这个函数即可。这种方式增强了代码的可读性,减少了代码冗余,希望大家在编程的时候可以具有这种意识。

下面我们来看看上述代码用装饰器来写成的效果:

def check_admin(func):
    def wrapper(*args, **kwargs):
        if kwargs.get('username') != 'admin':
            raise Exception('This user do not have permission')
        return func(*args, **kwargs)
    return wrapper

class Stack:
    def __init__(self):
        self.item = []

    @check_admin
    def push(self,username,item):
        self.item.append(item)

    @check_admin
    def pop(self,username):
        if not self.item:
            raise Exception('NO elem in stack')
        return self.item.pop()

对比一下使用「装饰器」和不使用装饰器的两种写法,乍一看,好像使用「装饰器」以后代码的行数更多了,但是你有没有发现代码看起来好像更容易理解了一些。在没有装饰器的时候,我们先看到的是 check_admin 这个函数,我们得先去想这个函数是干嘛的,然后看到的才是对栈的操作;而使用装饰器的时候,我们上来看到的就是对栈的操作语句,至于 check_admin 完全不会干扰到我们对当前函数的理解,所以使用了装饰器可读性更好了一些。

就和我在之前的文章中所讲的「生成器」那样,虽然 Python 的高级语言特性好用,但也不能乱用。装饰器的语法复杂,通过我们在上面缩写的装饰器就可以看出,它写完以后是很难调试的,并且使用「装饰器」的程序的速度会比不使用装饰器的程序更慢,所以还是要具体场景具体看待。

函数装饰器有什么作用?

装饰器本质上是一个 Python 函数,它可以在让其他函数在不需要做任何代码的变动的前提下增加额外的功能。装饰器的返回值也是一个函数的对象,它经常用于有切面需求的场景。 比如:插入日志、性能测试、事务处理、缓存、权限的校验等场景 有了装饰器就可以抽离出大量的与函数功能本身无关的雷同代码并发并继续使用。

生成器、迭代器的区别

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

生成器(Generator)是创建迭代器的简单而强大的工具。它们写起来就像是正规的函数,只是在需要返回数据的时候使用 yield 语句。每次 next()被调用时,生成器会返回它脱离的位置(它记忆语句最后一次执行的位置和所有的数据值)

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

多线程交互,访问数据,如果访问到了就不访问了,怎么避免重读?

创建一个已访问数据列表,用于存储已经访问过的数据,并加上互斥锁,在多线程访问数据的时候先查看数据是否已经在已访问的列表中,若已存在就直接跳过。

Python 中 yield 的用法?

yield 就是保存当前程序执行状态。你用 for 循环的时候,每次取一个元素的时候就会计算一次。用yield 的函数叫 generator,和 iterator 一样,它的好处是不用一次计算所有元素,而是用一次算一次,可以节省很多空间。generator每次计算需要上一次计算结果,所以用 yield,否则一 return,上次计算结果就没了。

在 Python 中,定义生成器必须要使用 yield 这个关键词,yield 翻译成中文有「生产」这方面的意思。在 Python 中,它作为一个关键词,是生成器的标志。接下来我们来看一个例子:

>>> def f():
...    yield 0
...    yield 1
...    yield 2
...
>>> f
<function f at 0x00000000004EC1E0>

上面是写了一个很简单的 f 函数,代码块是 3 个 yield 发起的语句,下面让我们来看看如何使用它:

>>> fa = f()
>>> fa
<generator object f at 0x0000000001DF1660>
>>> type(fa)
<class 'generator'>

上述操作可以看出,我们调用函数得到了一个生成器(generator)对象。

>>> dir(fa)
['__class__', '__del__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__lt__', '__name__', '__ne__',
'__new__', '__next__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'close', 'gi_code', 'gi_frame', 'gi_running', 'gi_yieldfrom', 'send', 'throw']

在上面我们看到了 iter() 和 next(),虽然我们在函数体内没有显示的写 iter() 和 next(),仅仅是写了 yield,但它就已经是「迭代器」了。既然如此,那我们就可以进行如下操作:

>>> fa = f()
>>> fa.__next__()
0
>>> fa.__next__()
1
>>> fa.__next__()
2
>>> fa.__next__()
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
StopIteration

从上面的简单操作可以看出:含有 yield 关键词的函数 f() 是一个生成器对象,这个生成器对象也是迭代器。所以就有了这样的定义:把含有 yield 语句的函数称为生成器,生成器是一种用普通函数语法定义的迭代器。
通过上面的例子可以看出,这个生成器(即迭代器)在定义的过程中并没有昨天讲的迭代器那样写 iter(),而是只用了 yield 语句,之后一个普普通通的函数就神奇的成了生成器,同样也具备了迭代器的特性。
yield 语句的作用,就是在调用的时候返回相应的值。下面我来逐行的解释一下上面例子的运行过程:
1.fa = f():fa 引用生成器对象。
2.fa.next():生成器开始执行,遇到了第一个 yield,然后返回后面的 0,并且挂起(即暂停执行)。
3.fa.next():从上次暂停的位置开始,继续向下执行,遇到第二个 yield,返回后面的值 1,再挂起。
4.fa.next():重复上面的操作。
5.fa.next():从上次暂停的位置开始,继续向下执行,但是后面已经没有 yield 了,所以 next() 发生异常。

想到了之前pytorch sampler的例子:

首先,每个sampler就是要提供iter方法,以生成迭代器,就可以定义遍历samples的方式

Every Sampler subclass has to provide an :meth:`__iter__` method, providing a
way to iterate over indices of dataset elements, and a :meth:`__len__` method
that returns the length of the returned iterators.

然后最简单的一个SequentialSampler就是这样的:

    def __iter__(self) -> Iterator[int]:
        return iter(range(len(self.data_source)))

进一步BatchSampler里面可以这样,其包装一个基础的sampler在类内,然后提供iter的时候iter出来batch size大小的一小组index,然后把它yield出来。这样的函数同样很神奇地“默认地”就返回了一个迭代器。而且很容易发现这个yield有记忆功能,总之就是每iter一次就“产生”一个iter的元素。以yield这样形式作为返回值的函数如果被建立成一个实例,就是generator实例了。

    def __iter__(self) -> Iterator[List[int]]:
        if self.drop_last:
            sampler_iter = iter(self.sampler)
            while True:
                try:
                    batch = [next(sampler_iter) for _ in range(self.batch_size)]
                    yield batch
                except StopIteration:
                    break
		else: ...

具体用法就可以:

>>> list(BatchSampler(SequentialSampler(range(10)), batch_size=3, drop_last=True))
[[0, 1, 2], [3, 4, 5], [6, 7, 8]]

谈下 python 的 GIL

GIL 是python的全局解释器锁,同一进程中假如有多个线程运行,一个线程在运行python程序的时候会霸占python解释器(加了一把锁即GIL),使该进程内的其他线程无法运行,等该线程运行完后其他线程才能运行。如果线程运行过程中遇到耗时操作,则解释器锁解开,使其他线程运行。所以在多线程中,线程的运行仍是有先后顺序的,并不是同时进行。
多进程中因为每个进程都能被系统分配资源,相当于每个进程有了一个python解释器,所以多进程可以实现多个进程的同时运行,缺点是进程系统资源开销大

Python 中的可变对象和不可变对象?

不可变对象,该对象所指向的内存中的值不能被改变。当改变某个变量时候,由于其所指的值不能被改变,相当于把原来的值复制一份后再改变,这会开辟一个新的地址,变量再指向这个新的地址。
可变对象,该对象所指向的内存中的值可以被改变。变量(准确的说是引用)改变后,实际上是其所指的值直接发生改变,并没有发生复制行为,也没有开辟新的出地址,通俗点说就是原地改变。
Python 中,数值类型(int 和 float)、字符串 str、元组 tuple 都是不可变类型。而列表 list、字典 dict、集合 set 是可变类型。

一句话解释什么样的语言能够用装饰器?

函数可以作为参数传递的语言,可以使用装饰器

Part 8

附答案 | 最强Python面试题之Python进阶题第四弹

Python 中 is 和==的区别?

is 判断的是 a 对象是否就是 b 对象,是通过 id 来判断的。

==判断的是 a 对象的值是否和 b 对象的值相等,是通过 value 来判断的。

谈谈你对面向对象的理解?

面向对象是相对于面向过程而言的。

面向过程语言是一种基于功能分析的、以算法为中心的程序设计方法

面向对象是一种基于结构分析的、以数据为中心的程序设计思想。在面向对象语言中有一个有很重要东西,叫做类。面向对象有三大特性:封装、继承、多态。

Python 里 match 与 search 的区别?

match()函数只检测 RE 是不是在 string 的开始位置匹配

search()会扫描整个 string 查找匹配

也就是说 match()只有在 0 位置匹配成功的话才有返回,如果不是开始位置匹配成功的话,match()就返回 none。

用 Python 匹配 HTML g tag 的时候,<.> 和 <.?> 有什么区别?

<.>是贪婪匹配,会从第一个“<”开始匹配,直到最后一个“>”中间所有的字符都会匹配到,中间可能会包含“<>”。

<.?>是非贪婪匹配,从第一个“<”开始往后,遇到第一个“>”结束匹配,这中间的字符串都会匹配到,但是
不会有“<>”。

Python 中的进程与线程的使用场景?

多进程适合在 CPU 密集型操作(cpu 操作指令比较多,如位数多的浮点运算)。

多线程适合在 IO 密集型操作(读写数据操作较多的,比如爬虫)。

解释一下并行(parallel)和并发(concurrency)的区别?

如何理解:程序、进程、线程、并发、并行、高并发? - 码农的荒岛求生的回答 - 知乎
https://www.zhihu.com/question/307100151/answer/1644084289
并发与并行的区别是什么? - invalid s的回答 - 知乎
https://www.zhihu.com/question/33515481/answer/1559913485
并发与并行的区别是什么? - 邱昊宇的回答 - 知乎
https://www.zhihu.com/question/33515481/answer/452128444

并发和并行并不是互斥的概念。并发强调的是可以一起出发,并行强调的是可以一起执行。前者关注的是任务的抽象调度,是从任务安排者视角看到的东西,后者关注的是任务的实际执行,是任务执行者视角下的东西

与并发(concurrent)相对的是顺序(sequential):

  • 顺序:不可以一起出发。上一个开始执行的任务完成后,当前任务才能开始执行
  • 并发:可以一起出发。无论上一个开始执行的人物是否完成,当前任务都可以开始执行

也就是说,A和B顺序执行的话,A一定比B先完成,而并发则不一定

与并行(parallel)相对的是串行(serial):

  • 串行:不可以一起执行。有一个任务执行单元,从物理上就只能一个任务一个任务地执行
  • 并行:可以一起执行。有多个任务执行单元,从物理上可以多个任务一起执行

也就是说,在任意时间点上,串行执行时肯定只能有一个任务在执行,而并行则可以多个

举例来说:

  • 单核cpu多任务:并发(不必等上一个任务完成才开始下一个任务),串行(只有一个实际执行任务的cpu核)
  • 多线程:并发,串行(所有线程都在同一个核上执行);并发,并行(不同线程在不同的核上执行)

如果一个程序需要进行大量的 IO 操作,应当使用并行还是并发?

并发

如果程序需要进行大量的逻辑运算操作,应当使用并行还是并发?

并行

在 Python 中可以实现并发的库有哪些?

  • 线程
  • 进程
  • 协程
  • threading

Python 如何处理上传文件?

Python 中使用 GET 方法实现上传文件,下面就是用 Get 上传文件的例子,client 用来发 Get 请求,server 用来收请求。
请求端代码

import requests #需要安装 requests

with open('test.txt', 'rb') as f:
requests.get('http://服务器 IP 地址:端口', data=f)

服务端代码

var http = require('http');
var fs = require('fs');
var server = http.createServer(function(req, res){

    var recData = "";
    req.on('data', function(data){
    recData += data;
    })
    req.on('end', function(data){
    recData += data;
    fs.writeFile('recData.txt', recData, function(err){
    console.log('file received');
        })
    })
    res.end('hello');
    })
server.listen(端口);

python 程序中文输出问题怎么解决?

方法一

用encode和decode,如:

import os.path
import xlrd,sys

Filename=/home/tom/Desktop/1234.xls’
if not os.path.isfile(Filename):
    raise NameError,%s is not a valid filename”%Filename

bk=xlrd.open_workbook(Filename)
shxrange=range(bk.nsheets)
print shxrange

for x in shxrange:
    p=bk.sheets()[x].name.encode(‘utf-8)
    print p.decode(‘utf-8)

方法二

在文件开头加上

reload(sys)

sys.setdefaultencoding(‘utf8′)

Python 如何 copy 一个文件?

shutil 模块有一个 copyfile 函数可以实现文件拷贝

如何用Python删除一个文件?

使用 os.remove(filename) 或者 os.unlink(filename)

当退出 Python 时,是否释放全部内存?

不是。

循环引用其它对象或引用自全局命名空间的对象的模块,在Python退出时并非完全释放。另外,也不会释放C库保留的内存部分。

什么是猴子补丁?

在运行期间动态修改一个类或模块。

>>> class A:
    def func(self):
        print("Hi")
>>> def monkey(self):
print "Hi, monkey"
>>> m.A.func = monkey
>>> a = m.A()
>>> a.func()

运行结果为:

Hi, Monkey
posted @   traviscui  阅读(14)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
点击右上角即可分享
微信分享提示