社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  Python

3000 字教你学会最地道的 Python 编程风格

Python编程时光 • 4 年前 • 803 次点击  

点击上方“Python编程时光”,选择“加为星标
第一时间关注Python技术干货!



今天讨论 Python 编程风格,如何写出更加Pythonic的代码是本篇讨论的话题。

基本目录结构:

  • 1 基本编程习惯

    • 1.1 多余的空格

    • 1.2 是否为 None 判断

    • 1.3 lamda 表达式

    • 1.4 最小化受保护代码

    • 1.5 保持逻辑完整性

    • 1.6 使用语义更加明确的方法

  • 2 EAFP 防御编程风格

  • 3 LBYL 防御编程风格

    • 3.1 程序每次运行都要检查

    • 3.2 很难一次考虑所有可能异常

    • 3.3 代码的可读性下降

1 基本编程习惯

Python代码的编程习惯主要参考PEP8:

https://www.python.org/dev/peps/pep-0008/

里面主要包括如每行代码长度不超过80,函数间空一行等。在此我强烈建议大家都去读一下google编写的python规范。

同时,我们可以使用一些好用的小工具辅助我们写出更加符合习惯的Python代码,如flake8等小插件。

结合以上这些参考资料和工具,我们这篇专题总结就不会过多去讲语法相关的格式化。而是更多精力放在一些典型的、常用的对比分析上,告诉大家常用的代码书写习惯,哪些写法不够符合习惯等。

1.1 多余的空格

以下函数赋值符合习惯:

foo(a, b=0, {'a':1,'b':2}, (10,))

但是,下面出现的多余空格都不符合习惯:

# 这些空格都是多余的
foo ( a, b = 0, { 'a':1'b':2 }, (10, )) 

下面代码,有空格又更符合习惯:

i += 1
num = num**2 + 1
def  foo(nums: List)

尤其容易忽略的一个空格,增加函数元信息时要有一个空格:

def foo(nums: list): # 此处根据官方建议nums: list间要留有一个空格
    pass

1.2 是否为 None 判断

判断某个对象是否为None,下面符合习惯:

if arr is None:
    pass

if arr is not None:
    pass

下面写法不符合习惯,一般很少见:

if arr == None:
    pass

特别的,对于list,tuple,set,dict,str等对象,使用下面方法判断是否为None更加符合习惯:

if not arr: #为 None 时,满足条件
    pass
    
if arr: # 不为 None 时,满足条件
    pass

1.3 lamda 表达式

lambda 表达式适合一些key参数赋值等,一般不习惯这么写:

f = lambda i: i&1

下面写法更加符合习惯:

def is_odd(i): return i&1

1.4 最小化受保护代码

要想代码更健壮,我们一般都做防御性的工作,最小化受保护的代码更加符合习惯,如下为了防御键不存在问题,加一个try:

try:
    val = d['c']
except KeyError:
    print('c' not existence)

上面写法是合理的,但是下面代码在捕获KeyError时,又嵌套一个函数是不符合习惯的:

try:
    val = foo(d['c']) # 这样写也会捕获foo函数中的KeyError异常
except KeyError:
    print('c' not existence)

这样写也会捕获foo函数中的KeyError异常,不符合习惯。

1.5 保持逻辑完整性

根据官方指南,只有if逻辑return,而忽视可能的x为负时的else逻辑,不可取:

def foo(x):
    if x >= 0:
        return math.sqrt(x)

建议写法:

def foo(x):
    if x >= 0:
        return math.sqrt(x)
    else:
        return None

或者这样写:

def foo(x):
    if x 0:
        return None
    return math.sqrt(x)

所以,不要为了刻意追求代码行数最少,而忽视使用习惯。

1.6 使用语义更加明确的方法

判断字符串是否以ize结尾时,不建议这样写:

if s[-3:] == 'ize':
    print('ends ize')

使用字符串的endswith方法判断是否以什么字符串结尾,显然可读性更好:

if s.endswith('ize'):
    print('ends ize')

以上这些只要平时多加注意,理解起来不是问题。其实除了PEP8指定的这些代码编写习惯外,还有一种与代码健壮性息息相关的编程风格,今天重点介绍这方面的编程习惯。

2 EAFP 防御编程风格

为了提升代码的健壮性,我们要做防御性编程,Python中的try except就是主要用来做这个:

d = {'a'1'b': [123]}

try:
    val = d['c']
except KeyError:
    print('key not existence')

try块中代码是受保护的,如果键不存在,except捕获到KeyError异常,并处理这个异常信息。

而下面的代码,一旦从字典中获取不存在的键,如果没有任何try保护,则程序直接中断在这里,表现出来的现象就是app直接挂掉或闪退,这显然非常不友好。

d = {'a'1'b': [123]}
val = d['c']

再举一个tryexcept使用的例子,如果目录已存在则触发OSError异常,并通过except捕获到然后在块里面做一些异常处理逻辑。

import os
try:
    os.makedirs(path)
except OSError as exception:
    if exception.errno != errno.EEXIST: 
        raise # PermissionError 等异常
    else:
        # path 目录已存在

以上这种使用tryexcept的防御性编程风格,在Python中有一个比较抽象的名字:EAFP

它的全称为:

Easier to Ask for Forgiveness than Permission.

没必要纠结上面这句话的哲学含义。

知道在编程方面的指代意义就行:首先相信程序会正确执行,然后如果出错了我们再处理错误

使用tryexcept这种防御风格,优点明显,try里只写我们的业务逻辑,except里写异常处理逻辑,几乎无多余代码,Python指南里也提倡使用这种风格。

但是任何事物都有两面性,这种写法也不例外。那么,EAFP防御风格有何问题呢?它主要会带来一些我们不想出现的副作用。

举一个例子,如下try块里的逻辑:出现某种情况修改磁盘的csv文件里的某个值,这些逻辑都顺利完成,但是走到下面这句代码时程序出现异常,进而被except捕获,然后做一些异常处理:

try:
    if condition:
        revise_csv()  # 已经污染csv文件

    do_something()  # 触发异常
except Exception:
    handle_exception()

由于try块里的逻辑分为两步执行,它们不是一个原子操作,所以首先修改了csv文件,但是do_something却出现异常,导致污染csv文件。

其实,除了以上EAFP防御性编程风格外,还有一种编程风格与它截然不同,它虽然能很好的解决EAFP的副作用,但是缺点更加明显,所以Python中不太提倡大量的使用此种风格。

3 LBYL 防御编程风格

再介绍另一种编程风格:LBYL

它的特点:指在执行正常的业务逻辑前做好各种可能出错检查,需要写一个又一个的ifelse逻辑。

EAFP风格的代码:

d = {'a'1'b': [123]}

try:
    val = d['c']
except KeyError:
    print('key not existence')

使用LBYL来写就是如下这样:

if 'c' in d:
    val = d['c']
else:
    print('key not existence')

EAFP风格的代码如下:

import os
try:
    os.makedirs(path)
except OSError as exception:
    if exception.errno != errno.EEXIST: 
        raise # PermissionError 等异常
    else:
        # path 目录已存在

使用LBYL来写就是如下这样:

import os

if not os.path.isdir(path):
    print('不是一个合法路径')

else:
    if not os.path.exists(path):
        os.makedirs(path)
    else:
        print('路径已存在')

通过以上两个例子,大家可以看出LBYL风格和EAFP风格迥异。

LBYL的代码if和else较多,这种风格会有以下缺点。

3.1 程序每次运行都要检查

程序每次运行都要检查,不管程序是不是真的会触发这些异常。

if 'c' in d: # 每次必做检查
    val = d['c']
    
if not os.path.isdir(path): # 每次必做检查
    print('不是一个合法路径')

else:
    if not os.path.exists(path): # 每次必做检查
        os.makedirs(path)
    else:
        print('路径已存在')

3.2 很难一次考虑所有可能异常

很难一次性考虑到所有可能的异常,更让人头疼的事情是,一旦遗漏某些异常情况,错误经常不在出现的地方,而在很外层的一个调用处。这就会导致我们花很多时间调试才能找到最终出错的地方。

def f1()
   if con1:

        # do1()
   if con2:
        # do2()
   # 但是遗漏了情况3,未在f1函数中报异常

3.3 代码的可读性下降

要写很多与主逻辑无关的if-else ,程序真正的逻辑就变得难以阅读。最后导致我们很难看出这个只是判断,还是程序逻辑/业务的判断。但是,如果用try-catch,那么try代码块里面可以只写程序的逻辑,在except里面处理所有的异常。

结论:就Python语言,推荐使用EAFP风格,个别受保护的块,若无法实现原子操作的地方可以使用LBYL风格。


- EOF -


推荐阅读  点击标题可跳转

最强Python自动化工具开源,不用写一行代码!

超干分享!如何提高Python的运行速度?

败家玩意儿!Redis 竟然浪费了这么多内存!

牛批了,1行python代码就可实现炫酷可视化

3 倍性能提升!升级 Flask 到 Quart

为什么 Python 多线程无法利用多核?

异步 Python 比同步 Python 快在哪里?




得本文对你有帮助?请分享给更多人

Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/99973
 
803 次点击