Python 高频面试题解析:字符串处理、传参机制与闭包陷阱
本文汇总了 Python 常见笔试面试题,涵盖字符串中数字翻倍替换、参数传递机制(值与引用)、浅拷贝与深拷贝的区别,以及一行代码实现奇数、水仙花数等技巧。重点解析了列表推导式中闭包延迟绑定导致的常见错误,并提供了生成器与默认参数两种解决方案。
字符串处理:数字翻倍替换实现
字符串处理
题目要求将字符串中的数字替换为其两倍的值,例如输入 "AS7G123m (d)F77k",期望输出 "AS14G246m (d)F154k"。
修改前:"AS7G123m (d)F77k"
修改后:"AS14G246m (d)F154k"
解题思路:首先利用正则表达式提取字符串中的所有数字,将每个数字乘以 2,随后以原数字为分隔符对字符串进行切割,得到字符列表。最后将处理后的数字与原字符列表拼接,形成最终结果。若读者有更优方案,欢迎交流分享。
import re
text = "AS7G123m (d)F77k"
nums = re.findall(r'(\d+)', text) # 取出字符串中数字
double_nums = [2 * int(i) for i in nums] # 乘以2
the_str = [] # 字符列表
for i in nums:
the_str.append(text.split(i, 1)[0])
text = text.split(i, 1)[1]
result = "" # 结果
for i in range(len(double_nums)):
result += the_str[i] + str(double_nums[i])
result += text
print(result)
Python 参数传递机制:引用传递解析
Python 传参是值传递还是引用传递?
结论是 Python 采用引用传递方式。可通过打印对象地址来验证:比较原变量 a 与函数参数 x 的内存地址,若两者一致,则证明传递的是引用。
def f(x):
print(id(x))
a = 1
print(id(a))
f(a)
# 140716760159264
# 140716760159264
需要注意的是,对于不可变类型(如整数、字符串),在函数内部对其操作会创建新对象,不影响原对象;而对于可变类型(如列表),在函数内部的操作可能会直接修改原对象的值。
1)传入的参数是不可变类型:
def f(x):
x += "666" # 这里会创建一个新的对象
print(x)
s = "777" # 字符串不可变
print(s)
f(s)
print(s)
# 777
# 777666
# 777
2)传入的参数是可变类型:
def f(x):
x.append(4) # 修改原对象的值
print(x)
s = [1, 2, 3] # 列表可变
print(s)
f(s)
print(s)
# [1, 2, 3]
# [1, 2, 3, 4]
# [1, 2, 3, 4]
浅拷贝与深拷贝的区别
3.浅拷贝与深拷贝的那些事
在 Python 中,必须明确区分浅拷贝与深拷贝。
1)浅拷贝:创建一个新的对象,但该对象内部包含的是原对象中元素的引用。如果通过引用修改了嵌套对象,原对象也会随之改变。
2)深拷贝:创建一个新的对象,并递归复制原对象中包含的所有子对象。此时,即使修改深拷贝后的数据,也不会影响原对象。
代码示例展示了赋值、浅拷贝和深拷贝的行为。赋值操作(c = a)意味着 c 与 a 引用同一对象,对 c 的操作即是对 a 的操作。对于浅拷贝对象(b 和 d),修改其嵌套元素会影响原对象 a;而对深拷贝对象 e 的操作则与原对象 a 完全独立。
import copy
a = [1, [2], 3]
b = a[:] # 使用切片操作,浅拷贝
c = a # 赋值操作,即引用
d = a.copy() # 浅拷贝
e = copy.deepcopy(a) # 深拷贝
b.append(4)
c.append(5)
d.append(6)
d[1].append(2)
e.append(7)
e[1].append(3)
print(a) # [1, [2, 2], 3, 5]
print(b) # [1, [2, 2], 3, 4]
print(c) # [1, [2, 2], 3, 5]
print(d) # [1, [2, 2], 3, 6]
print(e) # [1, [2, 3], 3, 7]
Python 一行代码技巧示例
4.Python 一行式能干嘛?
以下示例展示了 Python 一行代码的强大与简洁性。
1)一行代码输出一百以内的奇数:
print([x for x in range(100) if x % 2])
2)一行代码求水仙花数:
print([x for x in range(100, 1000) if int(str(x)[0])**3 + int(str(x)[1])**3 + int(str(x)[2])**3 == x])
3)一行代码打印九九乘法表:
print("".join(["{} * {} = {}\t".format(x, y, x * y) if x != y else "{} * {} = {}\n".format(x, y, x * y) for x in range(1, 10) for y in range(1, x + 1)]))
4)一行代码实现 IP 地址转换,如将 192.168.12.1 转换成二进制格式 0b11000000 10101000 00001100 00000001:
print("0b"+" ".join(("00000000" + bin(int(i)).replace("0b", ""))[-8:] for i in ip.split(".")))
5)一行代码求 1 到 10 的和:
from functools import reduce; print(reduce(lambda x, y: x + y, [i for i in range(1, 11)]))
闭包延迟绑定陷阱及解决方案
5.下面这段代码的结果是什么?
def mul():
return [lambda x: x * i for i in range(4)]
print([m(2) for m in mul()])
上述代码的输出结果是 [6, 6, 6, 6],而非预期的 [0, 2, 4, 6]。
该现象源于 Python 闭包的延迟绑定机制。内部函数在调用时,才会去查找其所在作用域中变量的当前值。由于 for 循环结束后变量 i 的值为 3,因此每次调用返回的函数时,i 均被解析为 3,导致最终结果为 2 * 3 = 6。
解决此问题可参考以下两种方法:
1)使用 Python 生成器。
def mul():
for i in range(4):
yield lambda x: x * i
print([m(2) for m in mul()])
2)利用默认参数在定义时绑定变量值,创造闭包。
def mul():
return [lambda x, i=i: x * i for i in range(4)]
print([m(2) for m in mul()])
本文先介绍至此,后续将分享更多技术内容。
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













