9 - Python常见面试题
一、输入与输出
1、代码中如果修改不可变数据类型会出现什么问题,抛出什么异常?
代码不会正常运行,抛出TypeError异常
2、print调用Python底层的什么方法?
默认调用sys.stdout.write方法,即往控制台打印字符串
3、简述对input()函数的理解
在 Python2 中,有 raw_input()和 input(), raw_input()和 Python3 中的 input()作用是一样的,input()输入的是什么数据类型的,获取到的就是什么数据类型的
在 Python3 中,input()获取用户输入,不论用户输入的是什么,获取到的都是字符串类型的
二、条件与循环
1、range和xrange的区别?
1)用法相同
2)range 的结果是一个列表,直接开辟一块内存空间来保存列表 --浪费空间
xrange 的结果是一个生成器,边循环边使用,只有使用时才会开辟内存空间 --合理利用空间,用多少要多少
3)所以当列表很长时,使用 xrange 性能要比 range 好
三、字典
1、现有字典 d={‘a’:24,‘g’:52,‘i’:12,‘k’:33},请按字典中的 value 值进行排序?
sorted(d.items(),key = lambda x:x[1])
2、说一下JSON和字典的区别
字典是一种标准数据类型,它的key值只要是能hash的就行
JSON(JavaScript Object Notation)是一种数据的表现形式,它的key值必须是字符串 本质上还是字符串,只不过是按照key:value这种键值对的格式来的字符串
1) 是一种轻量级的数据交换格式,是一种独立于编程语言的文本格式来存储和表示数据
2)易于机器解析和生成,有效的提升网络传输效率
场景:自动化代码是python写的,但是后端的接口是Java等写的,不同的语言数据类型是不一样的,所以就导致测试提交的数据别的开发语言无法识别,此时就需要规范传输的数据,大家都遵循一个规范、按照一个标准的格式去传输,于是就有了JSON这种国际化规范的数据类型
3、什么是可变和不可变数据类型,包括哪些?
可变不可变指的是内存中的值是否可以被改变
不可变类型指的是对象所在内存块里面的值不可以改变,具体的数据类型有:数字、字符串、元组
可变类型则是可以改变,主要有列表、字典
4、存入字典里面的数据有没有先后顺序?
存入字典里面的数据不会自动排序,可以使用sort函数对字典进行排序
5、字典的推导式?
d = { key: value for (key, value) in iterable }
四、字符串
1、如何理解 Python 中字符串中的\字符?
有三种不同的含义:
1)转义字符
2)路径名中用来连接路径名
3)编写太长代码手动软换行
2、请按照alist中元素的age由大到小排序
alist [{'name':'a','age':20},{'name':'b','age':30},{'name':'c','age':25}]
def sort_by_age(list1):
return sorted(alist,key=lambda x:x['age'],reverse=True)
3、字符串反序输出
print(a_str[::-1])
4、判断回文
a_str[::-1] == a_str
五、列表
0、列表和数组的区别
列表:List,是Python的内置数据类型,6大基础数据类型之一
数组:Array ,Python本身是没有数组的
相同点:可以通过索引下标来取元素
不同点:
List:元素的数据类型可以不一样,整数、字符串等可以放在同一个列表中,不可以进行四则运算,比如+号只能将2个list进行拼接
列表的存储是分为单独存储数据和数据类型,数据类型保存的是数据的存放地址,也就是指针,并非数据,例如list1=[1,2,3,'a']需要4个数据和4个指针,所以增加了存储空间和消耗CPU
Array: Python本身并没有数组类型,但是它的Numpy库中有数组类型,元素的数据类型必须一样,可以进行四则运算 可以是列表、元组
numpy.array的储存、运行、数组间的计算等效率都要远高于列表
(0)列表是直接可以在python中使用的;数组是python中numpy库的,所以需要import numpy后,才能使用;
(1)列表中的元素数据类型可以不一样;数组中的元素数据类型必须一样;
(2)列表不可以进行四则运算;数组可以进行四则运算;
(3)列表可以使用更多的存储空间,数组使用空间则相对较少;
(4)由于Numpy专门针对数组的操作和运算进行了设计,所以数组的存储效率和输入输出性能都远优于python中的列表,且数据量越大,优势就越明显
1、从列表中取值时,如果超出索引的范围,程序会产生异常,报错 IndexError:list index out of range
2、列表的一些操作
1)列表的增加
列表名.insert(index, 数据):在指定位置插入数据(位置前有空元素会补位)
# 往列表 name_list 下标为 0 的地方插入数据
In [3]: name_list.insert(0, "Sasuke")
In [4]: name_list
Out[4]: ['Sasuke', 'zhangsan', 'lisi', 'wangwu', 'zhaoliu']
# 现有的列表下标是 0-4,如果我们要在下标是 6 的地方插入数据,那个会自动插入到下标为 5 的地方,也就是# 插入到最后
In [5]: name_list.insert(6, "Tom")
In [6]: name_list
Out[6]: ['Sasuke', 'zhangsan', 'lisi', 'wangwu', 'zhaoliu', 'Tom']
列表名.append(数据):在列表的末尾追加数据(最常用的方法)
In [7]: name_list.append("Python")
In [8]: name_list
Out[8]: ['Sasuke', 'zhangsan', 'lisi', 'wangwu', 'zhaoliu', 'Tom', 'Python']
列表.extend(Iterable):将可迭代对象中的元素追加到列表
# 有两个列表 a 和 b a.extend(b) 会将 b 的元素追加到列表
In [10]: a = [11, 22, 33]
In [11]: b = [44, 55, 66]
In [12]: a.extend(b)
In [13]: a
Out[13]: [11, 22, 33, 44, 55, 66]# 有列表 c 和 字符串c
In [14]: c = ['j', 'a', 'v', 'a']
In [15]: d = "python"
In [16]: c.extend(d)
In [17]: c
Out[17]: ['j', 'a', 'v', 'a', 'p', 'y', 't', 'h', 'o', 'n']
2)列表取值
列表名[index] :根据下标来取值
In [19]: name_list = ["zhangsan", "lisi", "wangwu", "zhaoliu"]
In [20]: name_list[0]
Out[20]: 'zhangsan'
In [21]: name_list[3]
Out[21]: 'zhaoliu'
修改:列表名[index] = 数据:修改指定索引的数据
In [22]: name_list[0] = "Sasuke"
In [23]: name_list
Out[23]: ['Sasuke', 'lisi', 'wangwu', 'zhaoliu']
3)删除列表
del 列表名[index]:删除指定索引的数据
In [25]: name_list = ["zhangsan", "lisi", "wangwu", "zhaoliu"]# 删除索引是 1 的数据
In [26]: del name_list[1]
In [27]: name_list
Out[27]: ['zhangsan', 'wangwu', 'zhaoliu']
4)删除第一个出现的指定数据
列表名.remove(数据):删除第一个出现的指定数据
In [30]: name_list = ["zhangsan", "lisi", "wangwu", "zhaoliu", "lisi"]# 删除 第一次出现的 lisi 的数据
In [31]: name_list.remove("lisi")
In [32]: name_list
Out[32]: ['zhangsan', 'wangwu', 'zhaoliu', 'lisi']
5) 删除列表末尾的数据
列表名.pop():删除末尾的数据,返回值: 返回被删除的元素
In [33]: name_list = ["zhangsan", "lisi", "wangwu", "zhaoliu"]# 删除最后一个元素
In [34]: name_list.pop()
Out[34]: 'zhaoliu'
In [35]: name_list
Out[35]: ['zhangsan', 'lisi', 'wangwu']
6)删除指定索引的数据,返回被删除的元素
列表名.pop(index):删除指定索引的数据,返回被删除的元素
In [36]: name_list = ["zhangsan", "lisi", "wangwu", "zhaoliu"]# 删除索引为 1 的数据 lisi
In [37]: name_list.pop(1)
Out[37]: 'lisi'
In [38]: name_list
Out[38]: ['zhangsan', 'wangwu', 'zhaoliu']
7)清空整个列表的元素
列表名.clear():清空整个列表的元素
In [40]: name_list = ["zhangsan", "lisi", "wangwu", "zhaoliu"]
In [41]: name_list.clear()
In [42]: name_list
Out[42]: []
8)对列表进行升序排序
排序列表名.sort():升序排序 从小到大
In [43]: a = [33, 44, 22, 66, 11]
In [44]: a.sort()
In [45]: a
Out[45]: [11, 22, 33, 44, 66]
9)对列表进行降序排序
列表名.sort(reverse=True):降序排序 从大到小
In [46]: a = [33, 44, 22, 66, 11]
In [47]: a.sort(reverse=True)
In [48]: a
Out[48]: [66, 44, 33, 22, 11]
10)列表的逆序或者反转
表名.reverse():列表逆序、反转
In [50]: a = [11, 22, 33, 44, 55]
In [51]: a.reverse()
In [52]: a
Out[52]: [55, 44, 33, 22, 11]
11)得到列表长度
len(列表名):得到列表的长度
In [53]: a = [11, 22, 33, 44, 55]
In [54]: len(a)
Out[54]: 5
12)数据在列表中出现的次数
列表名.count(数据):数据在列表中出现的次数
In [56]: a = [11, 22, 11, 33, 11]
In [57]: a.count(11)
Out[57]: 3
13)数据在列表中首次出现时的索引,没有查到会报错
列表名.index(数据):数据在列表中首次出现时的索引,没有查到会报错
In [59]: a = [11, 22, 33, 44, 22]
In [60]: a.index(22)
Out[60]: 1
14)判断列表中是否包含某元素
if 数据 in 列表: 判断列表中是否包含某元素
a = [11, 22, 33, 44 ,55]
if 33 in a:
print("找到了....")
15)列表的循环遍历
使用 while 循环:
a = [11, 22, 33, 44, 55]
i = 0
while i < len(a):
print(a[i])
i += 1
使用 for 循环:
a = [11, 22, 33, 44, 55]
for i in a:
print(i)
16)给定两个列表,找出他们相同的元素和不同的元素
list1 = [1,2,3]
list2 = [3,4,5]
set1 = set(list1)
set2 = set(list2)
print(set1&set2) # 交集:共有的部分
print(set1^set2) # 不同的元素
print(set2-set1). # 差集:另一个集合中没有的部分
print(set2|set1). # 并集:总共的部分
17)使用Python代码实现删除一个list里面的重复元素
方法一:用内置的sets:
l1 = ['b','c','d','b','c','a','a']
l2 = list(set(l1))
print l2
方法二:如果想要保持它们原来的排序,使用list类的sort方法:
l1 = ['b','c','d','b','c','a','a']
l2 = list(set(l1))
l2.sort(key=l1.index)
print l2
六、文件操作
1、read、readline和readlines的区别
read:读取整个文件
readline: 读取下一行,使用生成器方法
readlines:读取整个文件到一个迭代器供我们遍历
七、函数
1、Python 函数调用的时候参数的传递方式是值传递还是引用传递?
Python 的参数传递有:位置参数、默认参数、可变参数、关键字参数。
函数的传值到底是值传递还是引用传递,要分情况:
不可变参数用值传递:像整数和字符串这样的不可变对象,是通过拷贝进行传递的,因为你无论如何都不可能在原处改变不可变对象
可变参数是引用传递的:比如像列表,字典这样的对象是通过引用传递、和 C 语言里面的用指针传递数组很相似,可变对象能在函数内部改变。
2、对缺省参数的理解?
缺省参数指
1)在调用函数的时候没有传入参数的情况下,调用默认的参数
2)在调用函数的同时赋值时,所传入的参数会替代默认参数
*args 是不定长参数,他可以表示输入参数是不确定的,可以是任意多个。
**kwargs 是关键字参数,赋值的时候是以键 = 值的方式,参数是可以任意多对
在定义函数的时候不确定会有多少参数会传入时,就可以使用两个参数。
3、为什么函数名字可以当做参数用?
Python中一切皆对象,函数名是函数在内存中的空间,也是一个对象
4、Python中pass语句的作用是什么?
在编写代码时只写框架思路,具体实现还未编写就可以用 pass 进行占位,使程序不报错,不会进行任何操作
八、内建函数
1、map 函数和 reduce 函数?
①从参数方面来讲:
map()包含两个参数,第一个参数是一个函数,第二个是序列(列表 或元组)。其中,函数(即 map 的第一个参数位置的函数)可以接收一个或多个参数。
reduce()第一个参数是函数,第二个是序列(列表或元组)。但是,其函数必须接收两个参数。
②从对传进去的数值作用来讲:
map()是将传入的函数依次作用到序列的每个元素,每个元素都是独自被函数“作用”一次 。
reduce()是将传人的函数作用在序列的第一个元素得到结果后,把这个结果继续与下一个元素作用(累积计算)。
2、递归函数停止的条件?
递归的终止条件一般定义在递归函数内部,在递归调用前要做一个条件判断,根据判断的结果选择是继续调用自身,还是 return;返回终止递归。
终止的条件:
判断递归的次数是否达到某一限定值
判断运算的结果是否达到某个范围等,根据设计的目的来选择
3、回调函数如何通信的?
回调函数是把函数的指针(地址)作为参数传递给另一个函数,将整个函数当做一个对象,赋值给调用的函数
4、Python主要的内置数据类型有哪些?print dir('a')的输出?
内建类型:布尔类型、数字、字符串、列表、元组、字典、集合;输出字符串‘a’的内建方法;
5、print(list(map(lambda x: x * x, [y for y in range(3)])))的输出?
[0,1,4]
九、Lambda函数
1、什么是 lambda 函数? 有什么好处?
lambda 函数是一个可以接收任意多个参数(包括可选参数)并且返回单个表达式值的函数
1)lambda 函数比较轻便,即用即仍,很适合需要完成一项功能,但是此功能只在此一处使用,连名字都很随意的情况下;
2)匿名函数,一般用来给 filter, map 这样的函数式编程服务;
3)作为回调函数,传递给某些应用,比如消息处理
2、 写一个匿名函数求两个数的和?
lambda 函数是匿名函数;使用 lambda 函数能创建小型匿名函数。这种函数得名于省略了用 def 声明函数的标准步骤;
f = lambda x,y:x+y
print(f(2017,2018))
十、面向对象
1、Python中的可变对象和不可变对象?
不可变对象,该对象所指向的内存中的值不能被改变。当改变某个变量时候,由于其所指的值不能被改变,相当于把原来的值复制一份后再改变,这会开辟一个新的地址,变量再指向这个新的地址。
可变对象,该对象所指向的内存中的值可以被改变。变量(准确的说是引用)改变后,实际上是其所指的值直接发生改变,并没有发生复制行为,也没有开辟新的出地址,通俗点说就是原地改变。
Python 中,数值类型(int 和 float)、字符串 str、元组 tuple 都是不可变类型。而列表 list、字典 dict、集合 set 是可变类型。
2、Python 中is 和==的区别?
is 判断的是 a 对象是否就是 b 对象,是通过 id 来判断的。
==判断的是 a 对象的值是否和 b 对象的值相等,是通过 value 来判断的
3、Python中的魔方方法?
魔法方法就是可以给你的类增加魔力的特殊方法,如果你的对象实现 (重载)了这些方法中的某一个,那么这个方法就会在特殊的情况下被 Python 所调用,你可以定义自己想要的行为,而这一切都是自动发生的。 它们经常是两个下划线包围来命名的(比如 __init__,__lt__),Python 的魔法方法是非常强大的,所以了解其使用方法也变得尤为重要!__init__ 构造器,当一个实例被创建的时候初始化的方法。但是它并 不是实例化调用的第一个方法。
__new__才是实例化对象调用的第一个方法,它只取下 cls 参数,并把 其他参数传给 __init__。 __new__
很少使用,但是也有它适合的场景,尤其 是当类继承自一个像元组或者字符串这样不经常改变的类型的时候。
__call__ 允许一个类的实例像函数一样被调用
__getitem__ 定义获取容器中指定元素的行为,相当于 self[key] 。
__getattr__ 定义当用户试图访问一个不存在属性的时候的行为 。
__setattr__ 定义当一个属性被设置的时候的行为 。
__getattribute__ 定义当一个属性被访问的时候的行为 。
4、面向对象中怎么实现只读属性?
1)将对象私有化,通过共有方法提供一个读取数据的接口
class person:
def __init__(self,x):
self.__age = 10;
def age(self):
return self.__age;
t = person(22)
# t.__age = 100
print(t.age())
2)最好的方法
class MyCls(object): __weight = 50 @property #以访问属性的方式来访问 weight 方法 def weight(self): return self.__weight if __name__ == '__main__': obj = MyCls() print(obj.weight) obj.weight = 12 Traceback (most recent call last): File "C:/PythonTest/test.py", line 11, in <module> obj.weight = 12 AttributeError: can't set attribute
5、谈谈你对面向对象的理解?
面向对象是相对于面向过程而言的。面向过程语言是一种基于功能分析的、以算法为中心的程序设计方法;
而面向对象是一种基于结构分析的、以数据为中心的程序设计思想。在面向对象语言中有一个很重要东西,叫做类。面向对象有三大特性:封装、继承、多态。
6、Python中字符串的查找和替换?
re.findall(r’目的字符串’,’原有字符串’) #查询
re.findall(r'cast','itcast.cn')[0]
re.sub(r‘要替换原字符’,’要替换新字符’,’原始字符串’)
re.sub(r'cast','heima','itcast.cn')
十一、异常
1、在except 中return 后还会不会执行finally 中的代码?怎么抛出自定义异常?
会继续处理 finally 中的代码;用 raise 方法可以抛出自定义异常。
2、介绍一下 except 的作用和用法?
except: #捕获所有异常 except: <异常名>: #捕获指定异常
except:<异常名 1, 异常名 2> : 捕获异常 1 或者异常 2
except:<异常名>,<数据>:捕获指定异常及其附加的数据
except:<异常名 1,异常名 2>:<数据>:捕获异常名 1 或者异常名 2,及附加的数据
十二、模块与包
1、常用的 Python 标准库都有哪些?
os 操作系统,time 时间,random 随机,pymysql 连接数据库,threading 线程,multiprocessing进程,queue 队列。
第三方库:django 和 flask 也是第三方库,requests,virtualenv,selenium,scrapy,xadmin,celery,re,hashlib,md5。
常用的科学计算库(如 Numpy,Scipy,Pandas)。
2、赋值、浅拷贝和深拷贝的区别?
一、 赋值
在 Python 中,对象的赋值就是简单的对象引用,这点和 C++不同,如下所示:
a = [1,2,"hello",['python', 'C++']]
b = a
在上述情况下,a 和 b 是一样的,他们指向同一片内存,b 不过是 a 的别名,是引用。
我们可以使用 b is a 去判断,返回 True,表明他们地址相同,内容相同,也可以使用 id()函数来查看两个列表的地址是否相同。
赋值操作(包括对象作为参数、返回值)不会开辟新的内存空间,它只是复制了对象的引用。也就是说除了 b 这个名字之外,没有其他的内存开销。修改了 a,也就影响了 b,同理,修改了 b,也就影响了 a。
二、 浅拷贝(shallow copy)
浅拷贝会创建新对象,其内容非原对象本身的引用,而是原对象内第一层对象的引用。
浅拷贝有三种形式:切片操作、工厂函数、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,修改外层元素,会修改它的引用,让它们指向别的位置,修改嵌套列表中的元素,列表的地址并未发生变化,指向的都是用一个位置。
三、 深拷贝(deep copy)
深拷贝只有一种形式,copy 模块中的 deepcopy()函数。
深拷贝和浅拷贝对应,深拷贝拷贝了对象的所有元素,包括多层嵌套的元素。因此,它的时间和空间开销要高。
同样的对列表 a,如果使用 b = copy.deepcopy(a),再修改列表 b 将不会影响到列表 a,即使嵌套的列表具有更深的层次,也不会产生任何影响,因为深拷贝拷贝出来的对象根本就是一个全新的对象,不再与原来的对象有任何的关联。
四、 拷贝的注意点?
对于非容器类型,如数字、字符,以及其他的“原子”类型,没有拷贝一说,产生的都是原对象的引用。
如果元组变量值包含原子类型对象,即使采用了深拷贝,也只能得到浅拷贝。
3、__init__ 和__new__的区别?
init 在对象创建后,对对象进行初始化。
new 是在对象创建之前创建一个对象,并将该对象返回给 init。
4、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):从特定序列中随机取一个元素,这里的序列可以是字符串,列表,元组等。
5、输入某年某月某日,判断这一天是这一年的第几天?(可以用Python 标准库)
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
6、打乱一个排好序的 list 对象 alist?
import random
random.shuffle(alist)
7、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()是否为文件
8、模块和包是什么?
在 Python 中,模块是搭建程序的一种方式。每一个 Python 代码文件都是一个模块,并可以引用其他的模块,比如对象和属性。
一个包含许多 Python 代码的文件夹是一个包。一个包可以包含模块和子文件夹
十三、Python特性
1、Python 是强语言类型还是弱语言类型?
Python 是强类型的动态脚本语言。
强类型:不允许不同类型相加。
动态:不使用显示数据类型声明,且确定一个变量的类型是在第一次给它赋值的时候。
脚本语言:一般也是解释型语言,运行代码只需要一个解释器,不需要编译。
2、Python 中有日志吗?怎么使用?
有日志。
Python 自带 logging 模块,调用 logging.basicConfig()方法,配置需要的日志等级和相应的参数,Python 解释器会按照配置的参数生成相应的日志。
3、Python 是如何进行类型转换的?
内建函数封装了各种转换函数,可以使用目标类型关键字强制类型转换,进制之间的转换可以用int(‘str’,base=’n’)将特定进制的字符串转换为十进制,再用相应的进制转换函数将十进制转换为目标进制。
可以使用内置函数直接转换的有:
list---->tuple tuple(list)
tuple---->list list(tuple)
4、关于 Python 程序的运行方面,有什么手段能提升性能?
使用多进程,充分利用机器的多核性能
对于性能影响较大的部分代码,可以使用 C 或 C++编写
对于 IO 阻塞造成的性能影响,可以使用 IO 多路复用来解决
尽量使用 Python 的内建函数
尽量使用局部变量
5、Python 中的作用域?
Python 中,一个变量的作用域总是由在代码中被赋值的地方所决定。当 Python 遇到一个变量的话它会按照这的顺序进行搜索:
本地作用域(Local)--->当前作用域被嵌入的本地作用域(Enclosing locals)--->全局/模块作用域(Global)--->内置作用域(Built-in)
6、什么是 Python 的命名空间?
在 Python 中,所有的名字都存在于一个空间中,它们在该空间中存在和被操作——这就是命名空间。它就好像一个盒子,每一个变量名字都对应装着一个对象。当查询变量的时候,会从该盒子里面寻找相应的对象。
十四、遵循的Python代码规范
PEP8 规范
1. 变量
常量:大写加下划线 USER_CONSTANT。
私有变量 : 小写和一个前导下划线 _private_value。
Python 中不存在私有变量一说,若是遇到需要保护的变量,使用小写和一个前导下划线。但这只是程序员之间的一个约定,用于警告说明这是一个私有变量,外部类不要去访问它。但实际上,外部类还是可以访问到这个变量。
内置变量 : 小写,两个前导下划线和两个后置下划线 __class__ 两个前导下划线会导致变量在解释期
间被更名。这是为了避免内置变量和其他变量产生冲突。用户定义的变量要严格避免这种风格。以免导致混乱。
2. 函数和方法
总体而言应该使用,小写和下划线。但有些比较老的库使用的是混合大小写,即首单词小写,之后每个单词第一个字母大写,其余小写。但现在,小写和下划线已成为规范。
私有方法 :小写和一个前导下划线 这里和私有变量一样,并不是真正的私有访问权限。同时也应该注意一般函数不要使用两个前导下划线 (当遇到两个前导下划线时,Python 的名称改编特性将发挥作用)。
特殊方法 :小写和两个前导下划线,两个后置下划线这种风格只应用于特殊函数,比如操作符重载等。
函数参数 : 小写和下划线,缺省值等号两边无空格
3、类
类总是使用驼峰格式命名,即所有单词首字母大写其余字母小写。类名应该简明,精确,并足以从中理解类所完成的工作。常见的一个方法是使用表示其类型或者特性的后缀,例如:
SQLEngine,MimeTypes 对于基类而言,可以使用一个 Base 或者 Abstract 前缀 BaseCookie,
AbstractGroup
4、模块和包
除特殊模块 __init__ 之外,模块名称都使用不带下划线的小写字母。
若是它们实现一个协议,那么通常使用 lib 为后缀,例如:
import smtplib
import os
import sys
5、关于参数
不要用断言来实现静态类型检测。断言可以用于检查参数,但不应仅仅是进行静态类型检测。
Python 是动态类型语言,静态类型检测违背了其设计思想。断言应该用于避免函数不被毫无意义的调用
不要滥用 *args 和 **kwargs。*args 和 **kwargs 参数可能会破坏函数的健壮性。它们使签名变得模糊,而且代码常常开始在不应该的地方构建小的参数解析器。
6. 一些数字
一行列数 : PEP 8 规定为 79 列。根据自己的情况,比如不要超过满屏时编辑器的显示列数。
一个函数 : 不要超过 30 行代码, 即可显示在一个屏幕类,可以不使用垂直游标即可看到整个函数。
一个类 : 不要超过 200 行代码,不要有超过 10 个方法。一个模块 不要超过 500 行。
7. 验证脚本可以安装一个 pep8 脚本用于验证你的代码风格是否符合 PEP8。
十五、Python2与Python3的区别
1、核心类差异
1. Python3 对 Unicode 字符的原生支持。
Python2 中使用 ASCII 码作为默认编码方式导致 string 有两种类型 str 和 unicode,Python3 只支持unicode 的 string。
2. Python3 采用的是绝对路径的方式进行 import。Python2 中相对路径的 import 会导致标准库导入变得困难(想象一下,同一目录下有 file.py,如何同时导入这个文件和标准库 file)。Python3 中这一点将被修改,如果还需要导入同一目录的文件必须使用绝
对路径,否则只能使用相关导入的方式来进行导入。
3. Python2 中存在老式类和新式类的区别,Python3 统一采用新式类。新式类声明要求继承object,必须用新式类应用多重继承。
4. 缩进严格程度
Python3 使用更加严格的缩进。Python2 的缩进机制中,1 个 tab 和 8 个 space 是等价的,所以在缩进中可以同时允许 tab 和 space 在代码中共存。这种等价机制会导致部分 IDE 使用存在问题。
Python3 中 1 个 tab 只能找另外一个 tab 替代,因此 tab 和 space 共存会导致报错:TabError:inconsistent use of tabs and spaces in indentation.
2、废弃类差异
3、修改类差异
4、第三方工具包的差异
1、Python垃圾回收机制是什么?
Python中的垃圾回收主要以引用计数为主,再引入标记、清除,分代为辅来解决循环引用的问题
一个对象被引用时,引用计数加1,当对象被del时,引用计数减去1,为0时,对象就被清除
一般情况下用户不会去操作Python 的垃圾回收机制,但它留有API接口
2、GIL全局解释器锁?
GIL是python的全局解释器锁,
同一进程中假如有多个线程运行,一个线程在运行python程序的时候会霸占python解释器(加了一把锁即GIL),使该进程内的其他线程无法运行,等该线程运行完后其他线程才能运行。
如果线程运行过程中遇到耗时操作,则解释器锁解开,使其他线程运行。
所以在多线程中,线程的运行仍是有先后顺序的,并不是同时进行。
多进程中因为每个进程都能被系统分配资源,相当于每个进程有了一个python解释器,所以多进程可以实现多个进程的同时运行,缺点是进程系统资源开销大
3、Python中如何实现多线程?
一个线程就是一个轻量级进程,多线程能让我们一次执行多个线程。
我们都知道,Python是多线程语言,其内置有多线程工具包。
Python中的GIL确保一次执行单个线程。
一个线程保存GIL并在将其传递给下个线程之前执行一些操作,这会让我们产生并行运行的错觉。
但实际上,只是线程在CPU上轮流运行。当然,所有的传递会增加程序执行的内存压力
4、当退出Python时,是否释放全部内存?
No。循环引用其它对象或引用自全局命名空间的对象的模块,在Python退出时并非完全释放。另外,也不会释放C库保留的内存部分。
5、如何在Python中管理内存?
Python内存由Python的私有headspace管理。所有的Python对象和数据结构都位于一个私有堆中。私用维的分配由Python内存管理器负责
6、元组和列表的区别?
元组是不可变的,列表是可变的
7、元组是否可以作为字典的key?
首先一个对象能不能作为字典的key, 就取决于其有没有__hash__方法。 所以除了容器对象(list/dict/set)和内部包含容器对象的tuple 是不可作为字典的key, 其他的对象都可以
8、进程和线程的区别?
进程:
1、操作系统进行资源分配和调度的基本单位,多个进程之间相互独立
2、稳定性好,如果一个进程崩溃,不影响其他进程,但是进程消耗资源大,开启的进程数量有限制
线程:
1、CPU进行资源分配和调度的基本单位,线程是进程的一部分,是比进程更小的能独立运行的基本单位,一个进程下的多个线程可以共享该进程的所有资源
2、如果IO操作密集,则可以多线程运行效率高,缺点是如果一个线程崩溃,都会造成进程的崩溃
9、列表去重
先通过转换为集合去重,在转列表
10、with语句
with语句的使用,可以简化了代码,有效避免资源泄露的发生
打开文件在进行读写的时候可能会出现一些异常状况,如果按照常规的f.open
写法,我们需要try,except,finally,做异常判断,并且文件最终不管遇到什么情况,都要执行finally f.close()关闭文件,with方法帮我们实现了finally中f.close
11、实例方法 静态方法
实例方法只能被实例调用,静态方法(@由staticmethod装饰器的方法)、类方法(由@classmethod装饰器的方法),可以被类或类的实例对象调用。
1、实例方法,第一个参数必须要默认传递实例对象,一般使用self。
2、静态方法,参数没有必要。
3、类方法,第一个参数必须要默认传递,一般使用cls。
12、迭代器和生成器
迭代器:
迭代器是一个可以记住遍历的位置的对象。
迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。
迭代器有两个基本的方法:iter() 和 next()。
字符串,列表或元组对象都可用于创建迭代器
生成器:
使用了yield的函数被称为生成器
生成器是一个返回迭代器的函数,只能用于迭代操作,更简单点理解生成器就是一个迭代器
在调用生成器运行的过程中,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值, 并在下一次执行 next() 方法时从当前位置继续运行
13、不使用中间变量交换a和b的值?
1)相加相减法
2) 异或运算
3)a,b = b,a
14、异常处理?
try: 可能触发异常的语句 except 错误类型1 [as 变量1]: 处理语句1 except 错误类型2 [as 变量2]: 处理语句2 except Exception [as 变量3]: 不是以上错误类型的处理语句 else: 未发生异常的语句 finally: 无论是否发生异常的语句