目录
题目43:运行下面的代码是否会报错,如果报错请说明哪里有什么样的错,如果不报错请说出代码的执行结果。
class A:
def __init__(self, value):
self.__value = value
@property
def value(self):
return self.__value
obj = A(1)
obj.__value = 2
print(obj.value)
print(obj.__value)
点评:这道题有两个考察点,一个考察点是对
_
和__
开头的对象属性访问权限以及@property
装饰器的了解,另外一个考察的点是对动态语言的理解,不需要过多的解释。
1
2
扩展:如果不希望代码运行时动态的给对象添加新属性,可以在定义类时使用
__slots__
魔法。例如,我们可以在上面的A
中添加一行__slots__ = ('__value', )
,再次运行上面的代码,将会在原来的第10行处产生AttributeError
错误。
题目44:对下面给出的字典按值从大到小对键进行排序。
prices = {
'AAPL': 191.88,
'GOOG': 1186.96,
'IBM': 149.24,
'ORCL': 48.44,
'ACN': 166.89,
'FB': 208.09,
'SYMC': 21.29
}
点评:
sorted
函数的高阶用法在面试的时候经常出现,key
参数可以传入一个函数名或一个Lambda函数,该函数的返回值代表了在排序时比较元素的依据。
sorted(prices, key=lambda x: prices[x], reverse=True)
题目45:说一下namedtuple
的用法和作用。
点评:Python标准库的
collections
模块提供了很多有用的数据结构,这些内容并不是每个开发者都清楚,就比如题目问到的namedtuple
,在我参加过的面试中,90%的面试者都不能准确的说出它的作用和应用场景。此外,deque
也是一个非常有用但又经常被忽视的类,还有Counter
、OrderedDict
、defaultdict
、UserDict
等类,大家清楚它们的用法吗?
在使用面向对象编程语言的时候,定义类是最常见的一件事情,有的时候,我们会用到只有属性没有方法的类,这种类的对象通常只用于组织数据,并不能接收消息,所以我们把这种类称为数据类或者退化的类,就像C语言中的结构体那样。我们并不建议使用这种退化的类,在Python中可以用namedtuple
(命名元组)来替代这种类。
from collections import namedtuple
Card = namedtuple('Card', ('suite', 'face'))
card1 = Card('红桃', 13)
card2 = Card('草花', 5)
print(f'{card1.suite}{card1.face}')
print(f'{card2.suite}{card2.face}')
命名元组与普通元组一样是不可变容器,一旦将数据存储在namedtuple
的顶层属性中,数据就不能再修改了,也就意味着对象上的所有属性都遵循“一次写入,多次读取”的原则。和普通元组不同的是,命名元组中的数据有访问名称,可以通过名称而不是索引来获取保存的数据,不仅在操作上更加简单,代码的可读性也会更好。
命名元组的本质就是一个类,所以它还可以作为父类创建子类。除此之外,命名元组内置了一系列的方法,例如,可以通过_asdict
方法将命名元组处理成字典,也可以通过_replace
方法创建命名元组对象的浅拷贝。
class MyCard(Card):
def show(self):
faces = ['', 'A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K']
return f'{self.suite}{faces[self.face]}'
print(Card) # <class '__main__.Card'>
card3 = MyCard('方块', 12)
print(card3.show()) # 方块Q
print(dict(card1._asdict())) # {'suite': '红桃', 'face': 13}
print(card2._replace(suite='方块')) # Card(suite='方块', face=5)
总而言之,命名元组能更好的组织数据结构,让代码更加清晰和可读,在很多场景下是元组、字典和数据类的替代品。在需要创建占用空间更少的不可变类时,命名元组就是很好的选择。
题目46:按照题目要求写出对应的函数。
要求:写一个函数,传入一个有若干个整数的列表,该列表中某个元素出现的次数超过了50%,返回这个元素。
def more_than_half(items):
temp, times = None, 0
for item in items:
if times == 0:
temp = item
times += 1
else:
if item == temp:
times += 1
else:
times -= 1
return temp
点评:LeetCode上的题目,在Python面试中出现过,利用元素出现次数超过了50%这一特征,出现和
temp
相同的元素就将计数值加1,出现和temp
不同的元素就将计数值减1。如果计数值为0
,说明之前出现的元素已经对最终的结果没有影响,用temp
记下当前元素并将计数值置为1
。最终,出现次数超过了50%的这个元素一定会被赋值给变量temp
。
题目47:按照题目要求写出对应的函数。
要求:写一个函数,传入的参数是一个列表(列表中的元素可能也是一个列表),返回该列表最大的嵌套深度。例如:列表
[1, 2, 3]
的嵌套深度为1
,列表[[1], [2, [3]]]
的嵌套深度为3
。
def list_depth(items):
if isinstance(items, list):
max_depth = 1
for item in items:
max_depth = max(list_depth(item) + 1, max_depth)
return max_depth
return 0
点评:看到题目应该能够比较自然的想到使用递归的方式检查列表中的每个元素。
题目48:按照题目要求写出对应的装饰器。
要求:有一个通过网络获取数据的函数(可能会因为网络原因出现异常),写一个装饰器让这个函数在出现指定异常时可以重试指定的次数,并在每次重试之前随机延迟一段时间,最长延迟时间可以通过参数进行控制。
方法一:
from functools import wraps
from random import random
from time import sleep
def retry(*, retry_times=3, max_wait_secs=5, errors=(Exception, )):
def decorate(func):
@wraps(func)
def wrapper(*args, **kwargs):
for _ in range(retry_times):
try:
return func(*args, **kwargs)
except errors:
sleep(random() * max_wait_secs)
return None
return wrapper
return decorate
方法二:
from functools import wraps
from random import random
from time import sleep
class Retry(object):
def __init__(self, *, retry_times=3, max_wait_secs=5, errors=(Exception, )):
self.retry_times = retry_times
self.max_wait_secs = max_wait_secs
self.errors = errors
def __call__(self, func):
@wraps(func)
def wrapper(*args, **kwargs):
for _ in range(self.retry_times):
try:
return func(*args, **kwargs)
except self.errors:
sleep(random() * self.max_wait_secs)
return None
return wrapper
点评:我们不止一次强调过,装饰器几乎是Python面试必问内容,这个题目比之前的题目稍微复杂一些,它需要的是一个参数化的装饰器。
题目49:写一个函数实现字符串反转,尽可能写出你知道的所有方法。
点评:烂大街的题目,基本上算是送人头的题目。
方法一:反向切片
def reverse_string(content):
return content[::-1]
方法二:反转拼接
def reverse_string(content):
return ''.join(reversed(content))
方法三:递归调用
def reverse_string(content):
if len(content) <= 1:
return content
return reverse_string(content[1:]) + content[0]
方法四:双端队列
from collections import deque
def reverse_string(content):
q = deque()
q.extendleft(content)
return ''.join(q)
方法五:反向组装
from io import StringIO
def reverse_string(content):
buffer = StringIO()
for i in range(len(content) - 1, -1, -1):
buffer.write(content[i])
return buffer.getvalue()
方法六:反转拼接
def reverse_string(content):
return ''.join([content[i] for i in range(len(content) - 1, -1, -1)])
方法七:半截交换
def reverse_string(content):
length, content= len(content), list(content)
for i in range(length // 2):
content[i], content[length - 1 - i] = content[length - 1 - i], content[i]
return ''.join(content)
方法八:对位交换
def reverse_string(content):
length, content= len(content), list(content)
for i, j in zip(range(length // 2), range(length - 1, length // 2 - 1, -1)):
content[i], content[j] = content[j], content[i]
return ''.join(content)
扩展:这些方法其实都是大同小异的,面试的时候能够给出几种有代表性的就足够了。给大家留一个思考题,上面这些方法,哪些做法的性能较好呢?我们之前提到过剖析代码性能的方法,大家可以用这些方法来检验下你给出的答案是否正确。
题目50:按照题目要求写出对应的函数。
要求:列表中有
1000000
个元素,取值范围是[1000, 10000)
,设计一个函数找出列表中的重复元素。
def find_dup(items: list):
dups = [0] * 9000
for item in items:
dups[item - 1000] += 1
for idx, val in enumerate(dups):
if val > 1:
yield idx + 1000
点评:这道题的解法和计数排序的原理一致,虽然元素的数量非常多,但是取值范围
[1000, 10000)
并不是很大,只有9000个可能的取值,所以可以用一个能够保存9000个元素的dups
列表来记录每个元素出现的次数,dups
列表所有元素的初始值都是0
,通过对items
列表中元素的遍历,当出现某个元素时,将dups
列表对应位置的值加1,最后dups
列表中值大于1的元素对应的就是items
列表中重复出现过的元素。