首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

Python 让自己的代码更pythonic

写程序其实就跟写作文、遣词造句一样,会用成语会让你的文章显得老练,但是滥用成语就让人受不了了。程序也一样,重要的是要理解什么样的逻辑应当用什么样的语法来表达。其中关键的是让读代码的人是否容易理解,而不是执行起来是否正确。

以下是一些个人的见解。总的来说原则是:在代码中提供恰到好处的信息量,既不缺少,也不冗余

列表推导与循环

什么时候列表推导作为代码来说比较清晰?当读者能够清晰理解你这条语句是要生成一个列表,除此以外什么都没有做的时候。

比较:

new_list = [v[1] for v in old_list if v[2]]

new_list = []for v in old_list: if v[2]: new_list.append(v[1])

前者(对于熟练的程序员来说)明显更容易理解你的意图,即便不能一眼看清楚你所有的条件,他也能明白:这句语句只是将列表进行了一个格式的调整。

再比较

result = [v for v in iter(self.read_socket, '') if not v.startswith('+')]

result = []while True: v = self.read_socket() if v == '': break if not v.startswith('+'): result.append(v)

前者明显就很糟糕,read_socket可能是个非常复杂、有明显副作用的过程,读代码的人不会期待你在这么短的语句当中完成个非常重要的逻辑。read_socket可能在里面抛出异常,你将没有机会给它加上try...except来进行处理,你也不会喜欢抛出的异常的堆栈信息里面有一堆 这样的意义不明的东西,而不是一个明确的行号。而且,很明显这是这段代码最重要的逻辑,这意味着这个逻辑以后可能会变更、会重构,你不会想要把一个列表推导变更得越来越复杂直到被迫推倒重来。

另外,用到map和filter的都要尽量重写成列表推导。

lambda表达式与具名函数

Python是支持函数嵌套定义的,在已有的函数中可以嵌套定义新的函数:

def my_func(): def subfunc(): ... subfunc() ...

嵌套的具名函数可以完全替代lambda表达式,而且有许多优点:

一个函数名可以迅速告诉读代码的人这个函数在做什么

抛出异常的时候,有函数名称明显要比显示为强

可以添加比较复杂的逻辑

可以使用decorator

具名函数可以用yield(也就是说可以定义嵌套的具名的generator,但不能定义lambda的generator)

需要作为返回值的一部分的时候,在repr表达式当中能显示函数名,方便调试

一般来说lambda表达式的使用一定要严格限定为(与关系):

非常简单的逻辑,尤其最好不要在lambda当中再嵌套列表推演或者生成器表达式或者其他lambda表达式,非常不清晰

没有副作用,或者只包装一个有副作用的表达式

一次性使用(绝对不要用f = lambda x: ...这样的语句,虽然只有一行,但读代码的时候会很难找到f的定义)

单返回值——使用tuple或者list的多返回值会让括号嵌套变得复杂难读懂。

例如:

return lambda x: lambda y: x + y

def add_closure(x): def add_func(y): return x + y return add_funcreturn add_closure

同样是嵌套的闭包,明显后一种写法要清晰得多,以后调试起来也要容易。

可以在列表推导当中嵌套lambda,但最好不要在lambda当中嵌套列表推导。在列表推导中使用lambda的时候,首先确定这个逻辑是必要的;其次,给这个列表起一个非常明确的变量名,说明这个列表中的函数起什么作用;第三,给lambda表达式加上括号,让人能比较清楚地看到lambda表达式的开始和结束;最后,一定要警惕闭包封闭循环变量的方式,非常容易出意料之外的bug。

multipliers = [(lambda x, i = i: x * i) for i in range(0, 20)]

修饰符/注解(decorator)

修饰符是让代码变得优雅易读的非常重要的工具,正确运用能有非常多的好处。但也一定要注意:

1. decorator中只做定义和初始化的工作,不要用decorator来执行某个操作。或者说,decorator不要有除了定义以外的副作用

例如,严格杜绝下面的用法:

def execute_once(f): f('test') @execute_once def my_func(param): ...

没有人会从代码中判断这个函数会在import的时候自动执行。而且,没有人会懂为什么my_func的值是None。

2. 用decorator修饰一个函数得到另一个函数的时候,原函数的逻辑仍然是新函数的中心,而decorator增加的是相对无关紧要的或者外围的功能;尤其不要改变原函数的执行逻辑。

严格杜绝下面的例子:

def revert(f): @wraps(f) def newf(*args, **kwargs): return not f(*args, **kwargs) # No, please DON'T, really return newf@revertdef is_ok(my_str): return my_str == 'OK'

3. 即使去掉修饰符,整个函数的逻辑仍然是完整、清晰、可读的。

严格杜绝下面的例子:

def repeat(f): @wraps(f) def newf(*args, **kwargs): data, num = f(*args, **kwargs) return [data] * num return newf@repeatdef zeros(n): return (0,n) # What is it???

with语句

with比起try...finally...来说,更强调“作用域”的概念。with接口一般有两种,一种是整个对象作为with表达式,一种是某个方法的返回值作为with表达式。应当遵循以下的规则:

with的__enter__和__exit__过程中不应当执行过于复杂的操作。尤其应当避免在__exit__中抛出异常——在__exit__中抛出新的异常可能会覆盖旧的异常。

with过程应当有明确的语义,一般来说代表某种生命周期(比如file,在with退出的时候会自动close),或者某种互斥过程(比如锁);如果要表达其他含义,最好用一个方法来返回一个with对象,方便读代码

对于可能重入的过程(即:每次with是独立的,可以在多线程中同时进行多个),应当使用函数返回with对象的形式,这样实现起来不容易出bug

能运用contextlib的情况下运用contextlib而不是手工实现__enter__和__exit__(不过这意味着要实现成函数返回with对象)

with应当事先比较次要的逻辑。即使去掉with,with内的逻辑看上去仍然比较清晰、完整。

能返回一个有意义的对象的时候,要返回一个有意义的对象,方便使用with ... as ...的语法

比如,比起

client = MyClient()with client: client.send(...)

应当用

client = MyClient()with client.transact() as trans_obj: client.send(...) self._logger.info('Transact %r succeeded.', trans_obj.id)

__getattr__, __getattribute__, property(descriptor), __setattr__,__delattr__

Python可以以很多种方式重新定义对象属性的特性。应当注意:

实施之前请再次思考是否真的必要。尤其是__setattr__和__getattribute__,它可能严重拖慢性能,因为每一个属性读写都会调用。

优先使用property

get过程不要修改对象的状态——增加cache之类的是可以的。

考虑实现__dir__来让对象可以正确列出属性。

如果重写了__getattr__和__setattr__,请至少考虑让下划线开头的属性遵循原始的规则,对于许多实现来说会提供一些便利。

metaclass

如果你是新手,尤其如果你不知道什么是metaclass,不要去强行用它。能使用其他方案比如decorator代替的要用decorator

不要修改基类和类名

总是从type派生出metaclass,你不会希望isinstance(myclass, type)返回False

有清晰的文档说明这个metaclass的特性

  • 发表于:
  • 原文链接http://kuaibao.qq.com/s/20180114A05YHG00?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券