99.python面试之常规题

Python是目前编程领域最受欢迎的语言。本系列中,我们将总结Python面试中最常见的各个维度的问题。每个问题都提供参考答案,这些面试题涉及Python基础知识、Python编程、数据分析以及Python函数库等多个方面。

python新式类和经典类的区别?

  1. 在python里凡是继承了object的类,都是新式类
  2. Python3里只有新式类
  3. Python2里面继承object的是新式类,没有写父类的是经典类
  4. 经典类目前在Python里基本没有应用
  5. 保持class与type的统一,对新式类的实例执行a.class与type(a)的结果是一致的,对于旧式类来说就不一样了。
    6.对于多重继承的属性搜索顺序不一样新式类是采用广度优先搜索,旧式类采用深度优先搜索。

python中内置的数据结构有几种?

  1. 整型 int、 长整型 long、浮点型 float、 复数 complex
  2. 字符串 str、 列表 list、 元组 tuple
  3. 字典 dict 、 集合 set
  4. Python3 中没有 long,只有无限精度的 int

python如何实现单例模式?请写出两种实现方式?

使用基类

New 是真正创建实例对象的方法,所以重写基类的new 方法,以此保证创建对象的时候只生成一个实

1
2
3
4
5
6
7
8
9
10
11
class Sington:
_instance = None
def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = super().__new__(cls, *args, **kwargs)
return cls._instance

a = Sington()
b = Sington()
print(a is b)

True

使用装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def singleton(cls):
instances = {}
def wrapper(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return wrapper

@singleton
class Foo(object):
pass
foo1 = Foo()
foo2 = Foo()
print(foo1 is foo2) # True
True

使用元类

元类是用于创建类对象的类,类对象创建实例对象时一定要调用call方法,因此在
调用call时候保证始终只创建一个实例即可,type是python的元类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Singleton(type):
def __call__(cls, *args, **kwargs):
if not hasattr(cls, '_instance'):
cls._instance = super().__call__(*args, **kwargs)
return cls._instance
# Python2
# class Foo(object):
# __metaclass__ = Singleton
# Python3
class Foo(metaclass=Singleton):
pass
foo1 = Foo()
foo2 = Foo()
print(foo1 is foo2) # True
True

反转一个整数,例如-123 –> -321

1
2
3
4
5
6
7
8
9
10
11
num = -123
first_char = ''
if not str(num)[0].isdigit():
first_char = str(num)[0]
other = str(num)[1:][::-1]
else:
other = str(num)[::-1]
results = int(first_char+other)
print(results, type(results))


-321 <class 'int'>

设计实现遍历目录与子目录,抓取.pyc文件

使用os.walk遍历目录

1
2
3
4
5
6
7
8
9
10
import os
# 存放pyc
pyc = []
# 要遍历的目录
path = 'F:/projects/flask_spider/app'
for dirpath, dirnames, filenames in os.walk(path):
for file in filenames:
if file.endswith('.pyc'):
pyc.append(os.path.join(dirpath, file))
pyc
['F:/projects/flask_spider/app\\views\\__pycache__\\task.cpython-37.pyc',
 'F:/projects/flask_spider/app\\views\\__pycache__\\__init__.cpython-37.pyc',
 'F:/projects/flask_spider/app\\__pycache__\\models.cpython-37.pyc',
 'F:/projects/flask_spider/app\\__pycache__\\__init__.cpython-37.pyc']

使用递归遍历目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 存放pyc
pyc = []
path = 'F:/projects/flask_spider/app'
def get_pyc(path):
for file in os.listdir(path):
# 完成文件目录
file_full_path = os.path.join(path, file)
# 判断是否是文件
if os.path.isfile(file_full_path):
# 判断是否是pyc文件
if file_full_path.endswith('.pyc'):
pyc.append(file_full_path)
else:
get_pyc(file_full_path)

get_pyc(path)
print(pyc)
['F:/projects/flask_spider/app\\views\\__pycache__\\task.cpython-37.pyc', 'F:/projects/flask_spider/app\\views\\__pycache__\\__init__.cpython-37.pyc', 'F:/projects/flask_spider/app\\__pycache__\\models.cpython-37.pyc', 'F:/projects/flask_spider/app\\__pycache__\\__init__.cpython-37.pyc']

使用glob模块

1
2
3
4
5
6
7
from glob import iglob
def func(fp, postfix):
for i in iglob(f"{fp}/**/*{postfix}", recursive=True):
print(i)

postfix = ".pyc"
func('F:/projects/flask_spider/app', postfix)
F:/projects/flask_spider/app\views\__pycache__\task.cpython-37.pyc
F:/projects/flask_spider/app\views\__pycache__\__init__.cpython-37.pyc
F:/projects/flask_spider/app\__pycache__\models.cpython-37.pyc
F:/projects/flask_spider/app\__pycache__\__init__.cpython-37.pyc

一行代码实现1-100之和

1
sum(range(101))
5050

Python-遍历列表时删除元素的正确做法

遍历在新在列表操作,删除时在原来的列表操作

1
2
3
4
5
lis = [1, 2, 3, 4, 5, 6, 7, 8, 9]
for i in lis[:]:
lis.remove(i)
print(lis)

[]

使用filter进行过滤删除

1
2
3
lis = [1, 2, 3, 4, 5, 6, 7, 8, 9]
results = filter(lambda item:item>5, lis)
print(list(results))
[6, 7, 8, 9]

使用列表推导式过滤删除

1
2
3
4
lis = [1, 2, 3, 4, 5, 6, 7, 8, 9]
results = [item for item in lis if item >5]
print(results)

[6, 7, 8, 9]

倒序解决

因为列表总是‘向前移’,所以可以倒序遍历,即使后面的元素被修改了,还没有被遍历的元素和其坐标
还是保持不变的

1
2
3
4
lis = [1, 2, 3, 4, 5, 6, 7, 8, 9]
for i in lis[::-1]:
lis.remove(i)
print(lis)
[]

字符串的操作题目

全字母短句 PANGRAM 是包含所有英文字母的句子,比如:A QUICK BROWN FOX JUMPS OVER THE LAZY DOG. 定义并实现一个方法 get_missing_letter, 传入一个字符串,返回参数字符串变成一个 PANGRAM 中所缺失的字符。应该忽略传入字符串参数中的大小写,返回应该都是小写字符并按字母顺序排序(请忽略所有非 ACSII 字符)

下面示例是用来解释,双引号不需要考虑:

(0)输入: “A quick brown fox jumps over the lazy dog”

返回: “”

(1)输入: “A slow yellow fox crawls under the proactive dog”

返回: “bjkmqz”

(2)输入: “Lions, and tigers, and bears, oh my!”

返回: “cfjkpquvwxz”

(3)输入: “”

返回:”abcdefghijklmnopqrstuvwxyz”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import string
# text = "A quick brown fox jumps over the lazy dog"
# text = "A slow yellow fox crawls under the proactive dog"
# text="Lions, and tigers, and bears, oh my!"
text = ""
def get_missing_letter(text):
# 取出a-z 26个小写字母
# pangram = string.ascii_lowercase
# pangram = "".join(map(chr, range(ord('a'), ord('z')+1)))
pangram = 'abcdefghijklmnopqrstuvwxyz'
# 所有字母转小写,并移除中间的空格
text_format = text.lower().replace(' ', '')
# 取两个差集组成的集合转为列表
results = list(set(pangram) ^ set(text_format))
# 列表排序
results.sort()
# 返回排序后的列表
return results

"".join(get_missing_letter(text))
'abcdefghijklmnopqrstuvwxyz'

可变类型和不可变类型

  1. 可变类型有list,dict.不可变类型有string,number,tuple.
  2. 当进行修改操作时,可变类型传递的是内存中的地址,也就是说,直接修改内存中的值,并没有开辟
    新的内存。
  3. 不可变类型被改变时,并没有改变原内存地址中的值,而是开辟一块新的内存,将原地址中的值复制
    过去,对这块新开辟的内存中的值进行操作。

is和==有什么区别?

  • is:比较的是两个对象的id值是否相等,也就是比较俩对象是否为同一个实例对象。是否指向同一个内
    存地址
  • == : 比较的两个对象的内容/值是否相等,默认会调用对象的eq()方法

求出列表所有奇数并构造新列表

使用列表推导式

1
2
3
a = [1,2,3,4,5,6,7,8,9,10]
results = [i for i in a if i%2==1]
print(results)
[1, 3, 5, 7, 9]

使用filter

1
2
3
4
a = [1,2,3,4,5,6,7,8,9,10]
results = list(filter(lambda item : item%2==1, a))
print(results)

[1, 3, 5, 7, 9]

用一行python代码写出1+2+3+10248

使用列表sum求和

1
print(sum([1, 2, 3, 1048]))
1054

使用reduce函数

1
2
3
from functools import reduce
results = reduce(lambda x, y:x+y, [1, 2, 3, 1048])
print(results)
1054

Python中变量的作用域?(变量查找顺序)

函数作用域的LEGB顺序

1.什么是LEGB?

  • L: local 函数内部作用域
  • E: enclosing 函数内部与内嵌函数之间
  • G: global 全局作用域
  • B: build-in 内置作用

python在函数里面的查找分为4种,称之为LEGB,也正是按照这是顺序来查找的

字符串 “123” 转换成 123 ,不使用内置api,例如 int()

字典键值对应,循环计算法

1
2
3
4
5
6
7
8
9
10
11
12
# 待转换的字符串
num_str = '123'
# 数字和字符串对应关系
numbers = {"0":0, "1":1, "2":2, "3":3, "4":4, "5":5, "6":6, "7":7, "8":8, "9":9}
# 最终转换结果初始为0
results = 0
# 倒序循环数字字符串(从个位数开始计算)
for index, num in enumerate(num_str[::-1]):
# 当前位对应数字乘以10对应的索引次方即该位数字的值
results += 10**index*numbers[num]
print(results, type(results))

123 <class 'int'>

str字符串计算法

1
2
3
4
5
6
7
8
9
10
11
12
# 待转换的字符串
num_str = '123'
# 最终转换结果初始为0
results = 0
# 遍历数字字符串
for num in num_str:
# 遍历0-9数字
for i in range(10):
# 比较当前数字,结果x10,加上当前数字即当前已循环的数字
if num == str(i):
results = results * 10 +i
print(results, type(results))
123 <class 'int'>

利用 ord 函数

1
2
3
4
5
6
7
# 待转换的字符串
num_str = '123'
# 最终转换结果初始为0
results = 0
for num in num_str:
results = results * 10 + ord(num)-ord('0')
print(results, type(results))
123 <class 'int'>

利用 eval 函数

1
2
3
4
5
6
7
8
9
# 待转换的字符串
num_str = '123'
# 不让用int,直接用eval就直接转成int了,哈哈哈,面试官要气死
# results = eval(num_str)
# 最终转换结果初始为0
results = 0
for num in num_str:
results = results *10 + eval(num)
print(results, type(results))
123 <class 'int'>

使用 reduce ,一行解决

1
2
3
4
5
from functools import reduce
# 待转换的字符串
num_str = '123'
results = reduce(lambda num, v: num*10+ord(v)-ord('0'), num_str, 0)
print(results, type(results))
123 <class 'int'>

Given an array of integers

给定一个整数数组和一个目标值,找出数组中和为目标值的两个数。你可以假设每个输入只对应一种答
案,且同样的元素不能被重复利用。示例:给定nums = [2,7,11,15],target=9 因为 nums[0]+nums[1] =
2+7 =9,所以返回[0,1]

1
2
3
4
5
6
7
8
9
10
11
data = 26
nums = [2,7,11,15]
for i in range(len(nums)):
for j in range(i+1, len(nums)):
if nums[i]+nums[j]==data:
print([i, j])
break
else:
continue
break

[2, 3]

统计一个文本中单词频次最高的10个单词?

使用字典记录:key为单词,val为出现次数

1
2
3
4
5
6
7
8
9
10
11
str = 'Hello world Hello China Tony China Shanghai Beijing China Shanghai Shenzhen'
str_list = str.split(" ")
print(str_list)
dic = dict.fromkeys(str_list, 0)
for word in str_list:
if not dic.get(word):
dic[word] = 1
else:
dic[word] +=1
lis = list(sorted(dic.items(), key=lambda item:item[1], reverse=True))
print(lis)
['Hello', 'world', 'Hello', 'China', 'Tony', 'China', 'Shanghai', 'Beijing', 'China', 'Shanghai', 'Shenzhen']
[('China', 3), ('Hello', 2), ('Shanghai', 2), ('world', 1), ('Tony', 1), ('Beijing', 1), ('Shenzhen', 1)]

使用counter模块

1
2
3
4
5
6
7
8
from collections import Counter
str = 'Hello world Hello China Tony China Shanghai Beijing China Shanghai Shenzhen'
str_list = str.split(" ")
# 获取每个次的排名,出现次数默认从高到低
rank_str = Counter(str_list)
print(rank_str)
# 取出前三名
print(rank_str.most_common(3))
Counter({'China': 3, 'Hello': 2, 'Shanghai': 2, 'world': 1, 'Tony': 1, 'Beijing': 1, 'Shenzhen': 1})
[('China', 3), ('Hello', 2), ('Shanghai', 2)]

请写出一个函数满足以下条件

该函数的输入是一个仅包含数字的list,输出一个新的list,其中每一个元素要满足以下条件:

  1. 该元素是偶数
  2. 该元素在原list中是在偶数的位置(index是偶数)
1
2
3
lis = [1, 3, 5, 6, 4, 2, 8, 12, 17, 19, 24]
results = [item for item in lis if item%2==0 and lis.index(item)%2 == 0]
print(results)
[4, 8, 24]

使用单一的列表生成式来产生一个新的列表

该列表只包含满足以下条件的值,元素为原始列表中偶数切片

1
2
3
list_data = [1,2,5,8,10,3,18,6,20]
results = [item for item in list_data if list_data.index(item)%2==0]
print(results)
[1, 5, 10, 18, 20]

用一行代码生成[1,4,9,16,25,36,49,64,81,100]

1
2
results = [item**2 for item in range(1, 11)]
print(results)
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

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

1
2
3
4
5
6
7
8
9
10
11
import datetime
# 模拟输入的年月日
year = 2022
month = 2
day = 2
# 转为datetime
now_datetime = datetime.datetime(year=year, month=month, day=day)
# 转为时间元祖
now_datetime_tuple = now_datetime.timetuple()
# 直接取这一天是当年的第几天
print(now_datetime_tuple.tm_yday)
33

两个有序列表,l1,l2,对这两个列表进行合并不可使用extend

1
2
3
4
5
6
7
8
l1 = [2, 4, 6, 7, 8]
l2 = [11, 33, 55, 66, 77]
# 不使用extend可以用+不,又被面试官撵出去
# print(l1+l2)
# 可以使用append吧?
while l2:
l1.append(l2.pop())
print(l1)
[2, 4, 6, 7, 8, 77, 66, 55, 33, 11]

给定一个任意长度数组,实现一个函数

让所有奇数都在偶数前面,而且奇数升序排列,偶数降序排序,如字符串’1982376455’,变成’1355798642’

1
2
3
4
5
6
7
8
9
10
11
s = list('1982376455')
print(s)
# 奇数
odd = sorted([i for i in s if int(i)%2==1])
# 偶数
even = sorted([i for i in s if int(i)%2==0], reverse=True)

print(odd)
print(even)
results = "".join(odd+even)
print(results)
['1', '9', '8', '2', '3', '7', '6', '4', '5', '5']
['1', '3', '5', '5', '7', '9']
['8', '6', '4', '2']
1355798642

写一个函数找出一个整数数组中,第二大的数

使用数组排序

1
2
3
4
5
6
7
8
lis = [2, 3, 6, 7, 8, 9, 3, 9]
def get_max_second(l):
l = list(set(l))
l.sort()
return l[-2]

print(get_max_second(lis))

8

部分使用冒泡排序

1
2
3
4
5
6
7
8
9
10
11
12
# 如果最大值有两个,则该种方法取出来还是最大的
lis = [2, 3, 6, 7, 8, 11, 3, 10]
def get_max_second(l):
top1 = l[0]
top2 = l[0]
for i in range(len(l)):
if l[i]>top1:
top1, top2=l[i], top1
elif l[i]>top2:
top2 = l[i]
return top2
print(get_max_second(lis))
10

阅读一下代码他们的输出结果是什么?

1
2
3
def multi():
return [lambda x : i*x for i in range(4)]
print([m(3) for m in multi()])

网友回答:正确答案是[9,9,9,9],而不是[0,3,6,9]产生的原因是Python的闭包的后期绑定导致的,这意味着在闭包
中的变量是在内部函数被调用的时候被查找的,因为,最后函数被调用的时候,for循环已经完成, i 的
值最后是3,因此每一个返回值的i都是3,所以最后的结果是[9,9,9,9]

个人理解:这道题最大的陷阱就是在函数内使用了匿名函数,形成了一个闭包,我们将multi方法进行拆解,不用匿名函数的方式来实现下:

1
2
3
4
5
6
7
def multi():
lis = []
def inner(x):
return i * x
for i in range(4):
lis.append(inner)
return lis

拆解后我们再来分析其实就清晰多了,可以看到for循环根本没有直接影响到inner函数,只是影响到了inner函数里的i,for循环到最后,i=3了,
所以inner的返回值就是参数x 3,而后面使用m(3)中的3其实传到了inner中,被x接收了,所以3x3=9,循环了4次,每次都是3*3, 所以返回了[9, 9, 9, 9]

统计一段字符串中字符出现的次数

使用传统的字典存储每个字符出现的次数

1
2
3
4
5
6
7
8
s = 'absdlfjkasdjfasdfasdfoewirweonmxlfvmljowerjmzxdkdbkshiweruy'
results = {}
for i in s:
if results.get(i):
results[i] +=1
else:
results[i] = 1
print(results)
{'a': 4, 'b': 2, 's': 5, 'd': 6, 'l': 3, 'f': 5, 'j': 4, 'k': 3, 'o': 3, 'e': 4, 'w': 4, 'i': 2, 'r': 3, 'n': 1, 'm': 3, 'x': 2, 'v': 1, 'z': 1, 'h': 1, 'u': 1, 'y': 1}

使用Counter模块统计

使用Counter不光统计出了每个字符出现的次数,而且按出现次数高低进行了排序

1
2
3
4
from collections import Counter
results = Counter(s)
print(results)
print(results.most_common(5))
Counter({'d': 6, 's': 5, 'f': 5, 'a': 4, 'j': 4, 'e': 4, 'w': 4, 'l': 3, 'k': 3, 'o': 3, 'r': 3, 'm': 3, 'b': 2, 'i': 2, 'x': 2, 'n': 1, 'v': 1, 'z': 1, 'h': 1, 'u': 1, 'y': 1})
[('d', 6), ('s', 5), ('f', 5), ('a', 4), ('j', 4)]

super函数的具体用法和场景

为了调用父类(超类)的一个方法,可以使用 super() 函数,super() 函数的一个常见用法是在 init() 方法中确保父类被正确的初始化了