python亲身经历面试题
好久没更新了,是面试去了,来说说近段时间遇到哪些python面试的问题。以下问题都是亲身经历。写python简单吗?其实都不是一句话能说到底的。引用我大学老师的一句话,做软件,能把实现功能出来是最简单的要求。因此要成为一个高手,必须懂得更多东西,更需要独立思考。以下内容都是我最近在面试中遇到的一些python基础问题,在此之前懂或不懂,或多或少都有点用吧。
1. python 里面 is 和 == 有什么区别?
is, == 通常出现在条件语句里面,作用是判断等式的两个值是否相等,返回一个布尔型,相等的时候返回True,不等的时候返回False
话不多说,来看python 交互模式代码。
>>> 1==1
True
>>> 1 is 1
True
>>> a = 1
>>> b = 1
>>> a == b
True
>>> a is b
True
>>> c = 123456789
>>> d = 123456789
>>> c ==d
True
>>> c is d
False
>>>
我们可以看出一点问题来了,在 a = 1 b=1时候 a is b 返回True ;在 c = 123456789 d = 123456789 等式 c is d False这里面 == is 肯定是有区别的。
必须先了解一下python 的内置函数 id()
id( [object] ) 函数用于获取对象的内存地址。务必提醒一下,python是一种面向对象的语言。你可以看:
python 的基本类型前面有一个 class !
>>> a = 1
>>> type(a)
<class 'int'>
>>>
同样的代码我们加上id() 查看内存地址再来看一下有什么区别:
>>> a = 1
>>> id(a)
140718485328928
>>> b = 1
>>> id(b)
140718485328928
>>> a is b
True
>>> c = 123456789
>>> id(c)
2446260967888
>>> d = 123456789
>>> id(d)
2446260967856
>>> c is d
False
>>>
你仔细观察的话可以发现其实已经懂了 is 和 == 的区别了。一句话概括:is 判断的是两边对象的id 是否相等也就是比较俩对象是否为同一个实例对象,是否指向同一个内存地址。。== 则是判断这两个对象的值是否相等。
由于 c = 123456789,d = 123456789,c 和d 的内存地址并不相等。所以 c is d False!
那么新的问题又来了,为什么 a = 1,b = 1时 id(a)和id(b)相等呢,而 c = 123456789,d = 123456789,c 和d 的内存地址并不相等呢?它们的id在什么条件下会相等呢?
官方解释:
出于对性能的考虑,Python内部做了很多的优化工作,对于整数对象,Python把一些频繁使用的整数对象缓存起来,保存到一个叫small_ints的链表中,在Python的整个生命周期内,任何需要引用这些整数对象的地方,都不再重新创建新的对象,而是直接引用缓存中的对象。
Python把这些可能频繁使用的整数对象规定在范围[-5, 256]之间的小对象放在small_ints中,但凡是需要用些小整数时,就从这里面取,不再去临时创建新的对象。
>>> a = 256
>>> id(a)
140718599304192
>>> b = 256
>>> id(b)
140718599304192
>>> c = 257
>>> id(c)
2305417615760
>>> d = 257
>>> id(d)
2305417615824
>>> a is b
True
>>> c is d
False
>>>
总结一下:== 是比较左右的值内容是否一致;is 比较的是左右两个对象的内存地址,获取内存地址用内置函数id(),而python为了提高性能 在[-5,256]范围的整数对象内存地址是固定的,哪个变量值是其中一样,变量就直接存这个整数对象的内存地址。因此 会出现 a is b 和 a == b 都能返True。但是本质上 is 就是 is , == 就是 ==
2. python里面的字典 dict 的键支持什么类型?为什么?什么是可哈希类型?
其实以上标题其实包含已经包含了几个问题了,面试的人可以问python的字典类型支持 列表作为键吗?python 索引最快的是那种类型?甚至包含了列表和元组有什么区别?等等问题。
字典
是一种映射 (mapping)关系的结构,不知道有没有学过java ,java有一种对象叫做hashMap,其实就类似于python里面的字典结构。当然在这里不介绍hashMap,引出一个词语 hash ,哈希
写在前面看不看得懂没关系:dict采用了哈希表,最低能在 O(1)时间内完成搜索。同样的java的HashMap也是采用了哈希表实现,不同是dict在发生哈希冲突的时候采用了开放寻址法,而HashMap采用了链接法。
下面我们逐个逐个来攻破这背后的逻辑。
什么是哈希?
在此之前多多少少会听到哈希表,哈希函数,哈希算法,哈希值…等等概念那么什么是哈希?如果你懂可以跳过…
哈希 就是一个 离 散 的东西!
我第一次遇见这个是严蔚敏《数据结构C语言版》第七章-查找,最后一节-离散表的查找 ,当时也是懵逼。虽然当时没怎么学,但是其实也是播下了一颗种子,当你需要它的时候你会马上想到可以在哪个地方找到这个知识。
在python的dict 字典结构里面应用的就是散列查找(hash search)的思想。离散查找的思想:
举个例子,现在有一串元素,请你找出其中特定的一个元素。
我们会首先想到的是利用目标元素和list 里面的每个元素进行比较,但是如果这个list 很长的情况下,查找时要大量的与无效节点的关键字进行比较,导致效率变慢,换句话说查找时间和长度有关 。
离散查找(hash search)
在元素的存储位置和关键字之间建立某种关系,在查找时候不需要做比较或少次比较,直接映射键的值地址出来。怎么样,再比如一下!
字典之所以叫字典是有原因的!
,想想你查新华字典是怎么样的,要想找一个目标的 字,你是从头第一页的第一个字开始去找直至到字典的最后一页吗?并不是!
- 你是首先去目录,找到相应的笔画或者拼音等规则(这些规则对应的就是哈希函数)
- 通过规则再找到这个字在第几页(对应哈希离散地址)
- 翻到这一页读取这个值,完成查找。
这就是哈希查找的思想。现在想想,字典这个词语翻译得是不是非常的贴切!
设 存储位置为p ,键为key ,哈希函数为H则
p = H(key)
ok,当你看到这里的时候剩下的都容易理解了。既然字典是一种哈希表结构,那么字典的键,key就必须是可哈希的!
大家别想那么深奥,就想想这个哈希函数的input 要求必须是合法的,它是哈希函数,所以输入的东西必须是可哈希的。
关于哈希函数的构造和处理冲突方法可以看看数据结构第七章离散表查找。
什么是可哈希?
可哈希结构就是一种不可变化的结构,这个对象的地址是唯一的,才能哈希。
python 可哈希类型有: 字符串,整数,实数,复数,元组等。
python不可哈希类型有: dict ,list ,set.
为什么呢?请看代码:
可哈希的对象的id 是唯一的,在它后面增加删除就是另一个对象了,注意元组存在后就只读,所以它也是可哈希的。
>>> a = "1"
>>> id(a)
2133739495976
>>> a = "11" #1就是1 ,11就是11 他们是唯一的!
>>> id(a)
2133771513000
>>> id(1)
140718713394208
>>> id(11)
140718713394528
>>> id([1,2])
2133778021064
不可哈希的列表结构:
可以看出列表存在,经过添加或删除里面的元素后,它的id是不会发生变化的!存在后id 就没有变化,那么它就是可以任意改变的,也就是不是唯一的,因此不可哈希。dict, set,和列表同理。
>>> listA = [1,2]
>>> id(listA)
2133778020488
>>> listA.append(3)#添加一个元素后
>>> listA
[1, 2, 3]
>>> id(listA)#id没有改变
2133778020488
>>>
其实python里面内置函数hash(),直接能判断这个对象是否是可哈希的,能就返回对应的哈希地址,不能就报错!
>>> hash(1) #哈希一个数字
1
>>> hash(257)
257
>>> hash("1")#哈希一个字符床
7090952801588601933
>>> hash((1,2,3))#哈希一个元组
2528502973977326415
>>> hash(1+2j)#哈希一个复数
2000007
>>> hash([1,2])#哈希列表会报错
Traceback (most recent call last):
File "<pyshell#5>", line 1, in <module>
hash([1,2])
TypeError: unhashable type: 'list'
>>> hash({"name":"半斤地瓜烧"})#哈希字典会报错
Traceback (most recent call last):
File "<pyshell#6>", line 1, in <module>
hash({"name":"半斤地瓜烧"})
TypeError: unhashable type: 'dict'
>>> hash({1,2})#哈希集合 set 会报错
Traceback (most recent call last):
File "<pyshell#7>", line 1, in <module>
hash({1,2})
TypeError: unhashable type: 'set'
>>>
总结:
很明显,字典的键,key是可哈希类型 包括:字符串,整数,实数,复数,元组等不包括,列表,字典,集合数据类型,并且是不可以重复的,值 value 是可以重复的。字典也是一种无序的序列,当查找元素时,不需要进行多余的比较,根据键 由哈希函数计算出哈希地址,取出地址的值就可以了,因此:索引字典的元素是最快的。
这也就可以解释为什么字典没有像列表,元组等结构的下标索引,解释为什么字典的添加和修改时字典用 dict[key] = value 就能完成。等等问题。
3. 列表和元组的异同,列表的存储?
列表和元组的相同点
- 两者皆支持存储异构元素
- 元组,列表都是支持索引,切片的。
- 元组也是序列,因此序列能使⽤的⽅法,如 max , min , len , sum ⽅法元组也能使⽤。
- 元组和列表之间是支持互相转换的 比如 list(tuple), tuple(list)。
列表和元组的不同点
- 元组是可哈希对象,列表是不可哈希对象
- 列表存在后可以增删改,复制等,元组只能读取,一旦存在就不能改变。
列表,元组为什么支持异构存储?
列表和元组非常灵活 都是可以存储多种结构的
如:
>>> a = [1,"1",{2:2},1+7j,(1,2),[1,2,4]]
>>> a
[1, '1', {2: 2}, (1+7j), (1, 2), [1, 2, 4]]
>>> b = (1,"1",{2:2},1+7j,(1,2),[1,2,4])
>>> b
(1, '1', {2: 2}, (1+7j), (1, 2), [1, 2, 4])
>>>
列表和元组之所以能支持异构元素是基于python值的内存管理模式,python变量不直接存储值,而是存储值的引用,id。请看:
>>> id(a[0])
140718713394208
>>> id(a[1])
2390987760168
>>> id(a[2])
2390997811848
>>> id(a[3])
2390997670800
>>> id(a[4])
2390997324488
>>>
因此列表或元组是如何实现索引呢?如a[2],注意列表是一个有序连续的存储空间,连续是指下标的连续。
- 由下标 “2” 找到存储a[2]的引用
- 根据引用,id,找到存储的值
好好体会上面这几句话,列表是连续的,因此写python代码尽可能的在列表尾部进行增加或删除,避免使用list.insert(),list.pop(),list.remove(),在列表的中间操作,会有涉及大量元素移动,降低效率。
4. python里面的函数支持多个参数输入是如何实现?
python编写函数时候,*arg和 * * kwarg 可以帮助我们处理上面这种情况,允许我们在调用函数的时候传入多个实参。
那么 *arg和 * * kwarg 是什么意思?和输入的参数有什么联系?
python里面定义一个函数定义输入的必要的参数,也可以输入一些不必要的参数。
例如:
*args 讲解:
#定义两个数的加法
def Add(x,y):
return x+y
#定义多个数的加法
def Add_1(*args):
print("args is {}".format(args))
print("type *args is {}".format(type(args)))
return sum(args)
'''结果:
3
args is (1, 2, 3, 4)
type *args is <class 'tuple'>
10
'''
说明了:
- *args ,是一个元组 tuple 类型的参数
- *args,是可以不输入的,如果输入,是除了必须输入的参数之外的元组。
** kwargs解释
kwargs 是将输入的参数格式为 “arg = value” 转化为字典,其中key 就是arg ,value 就是value。
#查看** kwargs 是什么
def func(**kwargs):
print("type **kwargs is {}".format(type(kwargs)))
return kwargs
#总体上理解 arg ,args **kwargs
def func1 (arg, *args ,**kwargs):
print("arg is {}".format(arg))
print("* args is {}".format(args))
print("**kwargs is {}".format(kwargs))
if __name__ == '__main__':
print(func(a=1,c = "1wrw"))
func1("半斤地瓜烧",1,2,3,apple = "苹果",flower = "花")
'''
arg is 半斤地瓜烧
* args is (1, 2, 3)
**kwargs is {'apple': '苹果', 'flower': '花'}
'''
那么问题又来了:下面两种代码创建字典有什么区别吗?
#-----方法1--------------------------
def dict(**kwargs):
return kwargs
d1 = dict(name = "半斤地瓜烧",age = 18)
#-----方法2---------------------
d2 = {"name": "半斤地瓜烧","age" : 18}
其实很容易看出,前面讲到,字典的格式除了字符串,还可以是整形,元组等可哈希类型。但是在方法1里面,必须遵守python的参数命名格式,将名字转化为字符串作为key,因此在方法 1 不能用(1,2)等非合法形式作为参数名字作为字典的 key; 显然方法2 是可以的。