导航

Python语言之那些年遇到过的坑

Posted on 2022-05-11 16:15  蟪蛄|大梦春秋  阅读(30)  评论(0)    收藏  举报
 
Python语言避坑指南
 
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
 

一、 Pythonic

所谓Pythonic即,以python的方式编写代码,在大部分时候,python是程序员的第二门甚至第n门语言,包括我自己(我个人是Java,JavaScript,C++/C,python是我的第四门语言)之前的语言使用习惯会带到新的语言中,但是有时候其实是不合适的。想学好python,以python的风格编写代码是一件非常重要的事情。
更简便的方法是遵循PEP08:
如果使用pycharm进行编程,该IDE会自动提示风格不正确的地方。我们的编程规范要求是,所有IDE提示有风格问题的地方都需要修复。做到这样pythonic差不多达到70%-80%的样子。
当然,遵守pythonic有时会带来一些问题。比如在使用递推表达式的时候,如果逻辑是清晰的,会很简洁。但是有一些复杂逻辑可能出错的情况。
 

二、条件表达式中的陷阱

data = dict()
# Some other data
if data:
do_someting(data)
第一条提到了pythonic,第二条我在这里就要提一个反例了。上面是python中推荐的判断方式,但是这里有一个陷阱。如上面这段代码所描述,这个if表达式,在data为None,0,“”,空list,空dict的情况下,都会返回False。而实际上,在这里我们需要判断的是data为不None。因此在这边正确的使用方法是:
data = dict()
# Some other data
if data is not None:
do_someting(data)

三、异常处理

try:
print("Hello")
except Exception1:
print("Exception1 happen") # Exception1 发生时
except:
print("Something went wrong") # 其余Exception 发生时
else:
print("Nothing went wrong") # 如果没有Exception 发生
finally:
print("The 'try except' is finished") # 无论有没有Exception 发生
上面是python异常处理的语法,具体内容的讲解,在这边就不赘述。大家可以参考:Python中的异常处理 写得很详细。
我今天要讲的是在编程过程中,大家需要注意控制异常捕获的范围。一个真实的例子是当年我接受AE的代码时,它的后台代码的最外面被人用一个try ... Except ...给包围起来的。这是一个已经发布的产品,因此就存在一个问题:当任何后台故障发生时,没有任何日志,在维护时,非常麻烦,根本没法在第一时间知道问题出在哪里。我猜测,这是因为当时版本发布时,后台有很多故障(事实上也证明了这一点),相关的研发为了能让测试通过,使用的绕过行为。这件事情也证明了一件事件,那就是掩盖问题并不会使问题消失,而只会在后面让问题以更猛烈的方式爆发出来。
 

四、装饰器

正常的文章介绍的装饰器有函数装饰器类装饰器,具体的内容,可以参考上面链接的文章进行学习。不过大部分文章并没有提过类成员函数装饰器怎么实现。
def retry_connect(fn):
def do_connection(*args, **kwargs):
count = 0
while count < 3:
try:
self = args[0]
self.conn = self.get_connection()
if self.conn:
return fn(*args, **kwargs)
except RedisError:
logging.error("Redis Connection is Not Ready", exc_info=True)
 
count += 1
time.sleep(5)
 
return do_connection
上面的实现是我使用的一个类成员函数装饰器。可以看到其定义和普通的函数装饰器没有太多区别。这里的问题关键在于,我们希望在装饰器里访问到类的一些成员,调用类的成员方法。
在上面的例子中,我是用了不定参数传递,想了解详情的可以参考:Python函数的非固定参数
其中,类实例本身,固定的就是args的第一个参数,因此,我们直接取args[0]就能获得对象本身了。
 

五、使用单例 

class Singleton(object):
def __init__(self, cls):
self._cls = cls
self._instance = {}
 
def __call__(self):
if self._cls not in self._instance:
self._instance[self._cls] = self._cls()
return self._instance[self._cls]
 
@Singleton
class LogStats:
def __init__(self):
self.fp = 0
self.t0 = 0
self.t1 = 0
self.t2 = 0
self.t3 = 0
self.drop = 0
self.output = 0
self.output_recv = 0
self.log_send = 0
从上面的实现来看,单例使用了类装饰器。
很多情况下,单例是用来取代全局变量的,事实上,由于python是单进程模型,其线程只是逻辑上的,在编程过程中很多同步问题不需要考虑,在单例的使用上来说更加方便。尤其是很多需要全局唯一的数据。
 

六、使用工厂模式

工厂模式是非常常见的设计模式,不过很多时候大家都是背八股文,知道是怎么用,但是不理解为什么要这样用。毕竟,大部分人是没有从头开始写过一个框架的。
在这里,我要讲一下我对这个的理解。在常规的编程中,如果我们按照需求直接编码,会觉得直接创建和使用工厂模式没有太多区别,甚至使用工厂模式还要更麻烦一些。但是我认为,实际上工厂模式并不是让研判在日常业务代码中直接使用的。工厂模式是让框架来调用的。基础的业务流程,通过框架来定义好。如果需要新增功能,我直接按照功能模块的接口给他实现了。然后注册给工厂。后面框架直接就可以通过配置来使用了。
上面是咱们研判模块的配置,或者可以认为是pattern。其中每一个功能,都是一个算子。实现了其功能后,都是框架通过工厂模式进行实例化的。
 

七、字符串

字符串是编程中使用最多的数据结构,具体怎么用,上面的图讲解的还是比较清楚的。这里要介绍一下python字符串的格式化,在python中,字符串格式有几种方式:

1.c语言格式化方法

%s 字符串 (采用str()的显示)
%r 字符串 (采用repr()的显示)
%c 单个字符
%b 二进制整数
%d 十进制整数
%i 十进制整数
%o 八进制整数
%x 十六进制整数
%e 指数 (基底写为e)
%E 指数 (基底写为E)
%f 浮点数
%F 浮点数,与上相同
%g 指数(e)或浮点数 (根据显示长度)
%G 指数(E)或浮点数 (根据显示长度)
%% 字符"%",显示百分号%
使用方法和C语言里面的一致,这也是最常见的一种方式:
print("%s %d" % (str(a), b_int))

2.str.format

str.format() 方法通过字符串中的花括号 {} 来识别替换字段 ,从而完成字符串的格式化。
"{} {}".format("hello","world")#设置指定位置,按默认顺序
输出为:'hello world'
"{1} {0}".format("world","hello") # 设置指定位置
输出为:'hello world' "{1} {0} {1}".format("hello", "world") # 设置指定位置
输出为:'world hello world'
input=["hello", "world"] #传入位置参数列表可用*列表
"{} {}".format(*input)
输出为:'hello world'
关于这种用法可以参考: