Python3 | Pythoneer 必备品质

努力写出具有 pythonic 风格的代码

Posted by Haauleon on May 12, 2021

背景

  python 语言的最大特点就是整洁,但我老是按照其他语言的思路来写代码,经常导致代码繁琐复杂。所以,要努力学习,努力写出具有 pythonic 风格的代码,才有勇气在别人面前说我会写 python,而不是只会写 hello world 的菜瓜。



代码片段

变量值交换

  以前使用最多的方式就是通过中间变量来进行交换。如下:

1
2
3
4
5
6
7
8
a = 10
b = 20

tmp = a
a = b
b = tmp

print("a = %d, b = %d" %(a, b))

运行结果:

1
a = 20, b = 10



  而 python 中交换变量的方式。如下:

1
2
3
4
5
6
a = 10
b = 20

a, b = b, a

print("a = %d, b = %d" %(a, b))

运行结果:

1
a = 20, b = 10



列表推导式

  求 20 以内的整除 3 的数的平方的列表,不使用列表推导式的代码。如下:

1
2
3
4
5
6
numbers = []
for x in range(20):
    if x % 3 == 0:
        numbers.append(x*x)

print(numbers)

运行结果:

1
[0, 9, 36, 81, 144, 225, 324]



  使用列表推导式的代码,可以应用于列表、集合或者字典。如下:

1
2
3
4
5
6
7
numbers_list = [x*x for x in range(20) if x % 3 == 0]
numbers_set = {x*x for x in range(20) if x % 3 == 0}
numbers_dict = {x: x*x for x in range(20) if x % 3 == 0}

print(numbers_list)
print(numbers_set)
print(numbers_dict)

运行结果:

1
2
3
[0, 9, 36, 81, 144, 225, 324]
{0, 225, 36, 324, 9, 144, 81}
{0: 0, 3: 9, 6: 36, 9: 81, 12: 144, 15: 225, 18: 324}



字符串拼接

  以前字符串拼接会惯性地使用 + 作为连接符,由于像字符串这种不可变对象在内存中生成后无法修改,合并后的字符串会重新开辟出一块内存空间来存储。因此每合并一次就会单独开辟一块内存空间,这样会占用大量的内存空间,严重影响代码的效率。

1
2
3
4
5
6
7
words = ['I', ' ', 'love', ' ', 'Python', '.']
 
sentence = ''
for word in words:
    sentence += '' + word

print(sentence)

运行结果:

1
I love Python.



  使用 join 来进行字符串连接的 python 写法如下:

1
2
3
4
5
words = ['I', ' ', 'love', ' ', 'Python', '.']
 
sentence = ''.join(words)

print(sentence)

运行结果:

1
I love Python.



字符串翻转

  Java或者C++等语言的写法是新建一个字符串,从最后开始访问原字符串:

1
2
3
4
5
6
7
a = 'I love Python.'
 
reverse_a = ''
for i in range(0, len(a)):
    reverse_a += a[len(a) - i - 1]

print(reverse_a)

运行结果:

1
.nohtyP evol I



  python 则将字符串看作 list,而列表可以通过切片操作来实现反转:

1
2
3
4
5
a = 'I love Python.'

reverse_a = a[::-1]

print(reverse_a)

运行结果:

1
.nohtyP evol I



for-else 语句

  在 C 语言或 Java 语言中,寻找一个字符是否在一个 list 中,通常会设置一个布尔型变量表示是否找到:

1
2
3
4
5
6
7
8
9
10
11
cities = ['BeiJing', 'TianJin', 'JiNan', 'ShenZhen', 'WuHan']
tofind = 'Shanghai'
 
found = False
for city in cities:
    if tofind == city:
        print('Found!')
        found = True
        break
if not found:
    print('Not found!')

运行结果:

1
Not found!



  python 中的通过 for...else... 会使得代码很简洁。注意: else 中的代码块仅仅是在 for 循环中,在没有执行到 break 语句的时候才会执行:

1
2
3
4
5
6
7
8
9
10
cities = ['BeiJing', 'TianJin', 'JiNan', 'ShenZhen', 'WuHan']
tofind = 'Shanghai'
 
for city in cities:
    if tofind == city:
        print('Found!')
        break
else: 
    # 执行else中的语句意味着没有执行break
    print('Not found!')

运行结果:

1
Not found!



enumerate 迭代对象

  打印一个列表的索引及其内容,可以用如下代码实现:

1
2
3
4
5
6
cities = ['BeiJing', 'TianJin', 'JiNan', 'ShenZhen', 'WuHan']
 
index = 0
for city in cities:
    index = index + 1
    print(index, ':', city)

运行结果:

1
2
3
4
5
1 : BeiJing
2 : TianJin
3 : JiNan
4 : ShenZhen
5 : WuHan



  而通过使用 enumerate 则极大简化了代码,enumerate 类接收两个参数,其中一个是可以迭代的对象,另外一个是开始的索引。这里索引设置为从 1 开始(默认是从0开始):

1
2
3
cities = ['BeiJing', 'TianJin', 'JiNan', 'ShenZhen', 'WuHan']
for index, city in enumerate(cities, 1):
    print(index, ":", city)

运行结果:

1
2
3
4
5
1 : BeiJing
2 : TianJin
3 : JiNan
4 : ShenZhen
5 : WuHan



lambda 定义函数

  不使用 lambda 则需要单独定义一个函数:

1
2
3
4
def f(x):
    return x * x

print(f(20))

运行结果:

1
400



  lambda可以返回一个可以调用的函数对象,会使得代码更为简洁。

1
2
3
f = lambda x: x * x

print(f(20))

运行结果:

1
400



上下文管理

  在打开文件时,通常是通过捕获异常来进行实现的,并且在 finally 模块中对文件来进行关闭:

1
2
3
4
5
6
7
8
try:
    file = open('python.txt')
    for line in file:
        print(line)
except:
    print("File error!")
finally:
    file.close()



  而通过上下文管理中的 with 语句可以让代码非常简洁:

1
2
3
with open('python.txt') as file:
    for line in file:
        print(line)



装饰器

  装饰器在 python 中应用特别广泛,其特点是可以在具体函数执行之前或者之后做相关的操作,比如:执行前打印执行函数的相关信息,对函数的参数进行校验;执行后记录函数调用的相关流水日志等。使用装饰器最大的好处是 使得函数功能单一化,仅仅处理业务逻辑,而不附带其它功能
  在函数调用前打印时间函数名相关的信息,不使用装饰器可以用如下代码实现:

1
2
3
4
5
6
7
from time import ctime
 
def foo():
    print('[%s]  %s() is called' % (ctime(), foo.__name__))
    print('Hello, Python')

print(foo())

运行结果:

1
2
3
[Wed May 12 16:12:20 2021]  foo() is called
Hello, Python
None



  这样写的问题是业务逻辑中会夹杂参数检查,日志记录等信息,使得代码逻辑不够清晰。所以,这种场景需要使用装饰器:

1
2
3
4
5
6
7
8
9
10
11
12
13
from time import ctime
 
def deco(func):
    def decorator(*args, **kwargs):
        print('[%s]  %s() is called' % (ctime(), func.__name__))
        return func(*args, **kwargs)
    return decorator
 
@deco
def foo():
    print('Hello, Python')

print(foo())

运行结果:

1
2
3
[Wed May 12 16:11:20 2021]  foo() is called
Hello, Python
None



生成器函数

  以计算斐波那契数列为例,使用列表可以用如下代码来实现:

1
2
3
4
5
6
7
8
9
10
def fib(max):
    n, a, b = 0, 0, 1
    fibonacci = []
    while n < max:
        fibonacci.append(b)
        a, b = b, a + b
        n = n + 1
    return fibonacci

print(fib(10))

运行结果:

1
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]



  生成器是需要的时候生成的,基本不占用内存空间。是通过 yield 关键字来实现的:

1
2
3
4
5
6
7
8
9
10
def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        yield b
        a, b = b, a + b
        n = n + 1

res = fib(10)
for i in res:
    print(i)

运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
def fib(max):...
1
1
2
3
5
8
13
21
34
55



链式比较
1
2
3
4
5
6
>>> x = 79
>>> 80 < x < 90
False
>>> 70 < x < 80
True



函数返回多个值

  在 Java 语言中,当函数需要返回多个值时,通常的做法是生成一个 Response 对象,然后将要返回的值写入对象内部。而 python 不需要这样做,可以直接返回多个值:

1
2
3
4
def f():
    error_code = 0
    error_desc = "成功"
    return error_code, error_desc

使用的时候也会非常简单:

1
2
code, desc = f()
print(code, desc)

运行结果:

1
0 成功



* 运算符解包

  * 运算符和 ** 运算符完美的解决了将元组参数、字典参数进行 unpack,从而简化了函数定义的形式,如:

1
2
3
4
5
6
def fun(*args, **kwargs):
    for eacharg in args:
        print('tuple arg:', eacharg)
    print(kwargs)
 
fun('I', 'love', 'Python', a=1, b=2)

运行结果:

1
2
3
4
tuple arg: I
tuple arg: love
tuple arg: Python
{'a': 1, 'b': 2}



找出列表中出现最多的数
1
2
3
num = [1, 3, 3, 4, 5, 6, 3, 6, 6, 6]
 
print(max(set(num),key=num.count))

运行结果:

1
6