区分Python开发高级和初级工程师的五个技巧汇总

时间:2023-01-29 21:56:12

1. 引言

在本文中,我们将以高级方式而不是初级方式来研究五种解决常见编码问题的方法。每一个编码问题都源于某个常见的实际问题抽象,许多问题在日常工作中反复出现多次,熟练掌握相关技巧,可以很方便的区分高级与初级开发人员。

闲话少说,我们直接开始吧!

2. 更加高效地读取文件

我们遇到的第一个问题是,需要进行几个数字块的读取,其中每个块用空行符​​\n​​进行分隔。
输入输出的样例如下:

# INPUT
10
20
30

50
60
70

# DESIRED OUTPUT
[[10, 20, 30], [50, 60 70]]

初级开发工程师的一般写法是采用​​if-else​​语句,代码如下:

numbers = []
with open("file.txt") as f:
group = []
for line in f:
if line == "\n":
numbers.append(group)
group = []
else:
group.append(int(line.rstrip()))
# append the last group because if line == "\n"
# will not be True for the last group
numbers.append(group)

高级工程师一般会采用列表生成式和字符串中的​​split​​方法,代码如下:

with open("file.txt") as f:
nums = [list(map(int, (line.split()))) for line in f.read().rstrip().split("\n\n")]

使用列表生成式,我们可以方便地将前面的九行代码合并为一行,而不损失代码层面的可读性,同时可以提升代码的性能(列表生成式通常比常规循环执行速度更快)。同时​​map​​函数将其第一个参数映射到第二个参数中的可迭代函数。在上述代码中,它将​​int()​​应用于列表中的每个元素,使每个元素都转化为整数。

3. 使用枚举代替if-else

接着我们考虑剪刀石头布的游戏,其中不同形状代表不同的点数,即使用X,Y,Z来分别表示剪刀石头布,同时剪刀石头布的点数依次为1,2,3。

将其抽象,该问题的输入和输出样例如下:

# INPUT
X
Y
Z

# DESIRED OUTPUT
1
2
3

一般初级开发人员倾向于使用​​if-else​​语句,代码如下:

def points_per_shape(shape: str) -> int:
if shape == 'X':
return 1
elif shape == 'Y':
return 2
elif shape == 'Z':
return 3
else:
raise ValueError('Invalid shape')

而高级开发人员往往将其抽象为枚举类型,代码如下:

from enum import Enum

class ShapePoints(Enum):
X = 1
Y = 2
Z = 3

def points_per_shape(shape: str) -> int:
return ShapePoints[shape].value

当然,在本例中,使用​​if-else​​方法并没有那么可怕,但使用​​Enum​​会导致代码更简短,增加可读性。特别是当对其进行扩展时,采用​​if-else​​的方法会变得越来越糟糕,而​​Enum​​则相对容易保持可读性。

4. 使用查找表代替字典

假设不同字母表示不同的取值。比如小写字母​​a-z​​的值为1到26,大写​​A-Z​​的数值为27到52。由于有许多不同的情况,使用上面这样的Enum将导致许多行代码。这里更为实用的方法是使用查找表。
此时,我们的输入输出样例如下:

# INPUT
c
Z
a
...

# DESIRED OUPUT
3
52
1
...

普通开发人员可能会选择字典作为相应的数据结构,代码如下:

letters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
letter_dict = dict()
for value, letter in enumerate(letters, start=1):
letter_dict[letter] = value

def letter_value(ltr: str) -> int:
return letter_dict[ltr]

而高级开发人员往往将其抽象为查找表的方式实现该功能,代码如下:

def letter_value(ltr: str) -> int
return 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.index(ltr) + 1

使用字符串中的函数​​.index()​​方法,我们可以获得相应的索引,因此采用​​letters.index('c')+1​​将得到预期值3。不需要将值存储在字典中,因为索引可以直接获取相应的取值。为了你觉得出现​​+1​​不够优雅,此时可以在字符串的开头添加一个空白字符,以便a的索引直接从1开始。

当然,此时我们也可以使用查找表来解决剪刀石头布的任务,代码如下:

def points_per_shape(shape: str) -> int:
return 'XYZ'.index(shape) + 1

5. 高级切片

假设我们需要读取行中的字母(见下面的输入)。其中每个字母都从索引1开始,间隔四个字母。现在,几乎每个​​Python​​程序员都会熟悉使用例如​​list[10:20]​​的方式来进行字符串的切片。但许多人不知道的是,我们可以使用例如​​list[10:20:2]​​来定义步长为​​2​​。在下面的样例中,这可以为我们节省大量不必要的代码逻辑。
样例输入输出如下:

# INPUT
[D]
[N] [C]
[Z] [M] [P]

# DESIRED OUTPUT
[' D ', 'NC', 'ZMP']

初级程序员往往使用两重循环来实现该功能,代码如下:

letters = []
with open('input.txt') as f:
for line in f:
row = ''
for index in range(1, len(line), 4):
row += line[index]
letters.append(row)

而高级程序员直接使用切片的高级用法,代码如下:

with open('input.txt') as f:
letters = [line[1::4] for line in f]

6. 使用类属性存储类实例

假设我们遇到了猴子之间互相传递物体。为了简化,我们假装只有两个猴子在互相传递香蕉。每个猴子都可以表示为​​Python​​类的一个​​class​​实例,其​​id​​和香蕉数量作为相应实例的属性。然而,有很多猴子,它们需要能够相互交流。存储所有猴子​​Monkey ​​并使它们能够相互交互的一个技巧是将包含所有猴子​​Monkey ​​实例的字典定义为猴子类的类属性。通过访问​​monkeys.Monkey[id]​​,我们可以访问所有的​​monkies​​,而无需通过​​monkeys​​类之外的字典,样例代码如下:

class Monkey:
monkeys: dict = dict()

def __init__(self, id: int):
self.id = id
self.bananas = 3
Monkey.monkeys[id] = self

def pass_banana(self, to_id: int):
Monkey.monkeys[to_id].bananas += 1
self.bananas -= 1

Monkey(1)
Monkey(2)
Monkey.monkeys[1].pass_banana(to_id=2)

print(Monkey.monkeys[1].bananas)
2

print(Monkey.monkeys[2].bananas)
4

7. 使用f-string

这个技巧实际上在每次编写​​Python​​程序时都适用。不是在​​f​​字符串中定义要打印的内容(例如。
​print(f"x = {x}")​​而是可以使用​​print(f"{x = }”)​​打印相应变量的值,并指定要打印的内容。

样例输出如下:

# INPUT
x = 10 * 2
y = 3 * 7

max(x,y)

# DESIRED OUTPUT
x = 20
y = 21

max(x,y) = 21

初级程序员的实现如下:

print(f"x = {x}")
print(f"y = {y}")

print(f"max(x,y) = {max(x,y)}")

高级程序员的方法如下:

print(f"{x = }")
print(f"{y = }")

print(f"{max(x,y) = }")

8. 总结

本文重点研究了5种用以区分高级开发人员和初级开发人员的Python技巧。当然,仅应用这些技巧不会突然将某人提升为高级开发人员。然而,通过分析两者在风格和模式上的差异,大家可以了解高级开发人员与初级开发人员处理编码问题时方式的差异,并且我们可以开始尽快掌握这些方法,从而提升自己的职业素养。