Python 有哪些优雅的代码实现

Python014

Python 有哪些优雅的代码实现,第1张

列表切割

list[start:end:step]

如果从列表开头开始切割,那么忽略 start 位的 0,例如list[:4]

如果一直切到列表尾部,则忽略 end 位的 0,例如list[3:]

切割列表时,即便 start 或者 end 索引跨界也不会有问题

列表切片不会改变原列表。索引都留空时,会生成一份原列表的拷贝

b= a[:]

assertb== aandbisnota# true

列表推导式

使用列表推导式来取代map和filter

a= [1,2,3,4,5,6,7,8,9,10]

# use map

squares= map(lambdax: x ** 2,a)

# use list comprehension

squares= [x ** 2forxina]

# 一个很大的好处是,列表推导式可以对值进行判断,比如

squares= [x ** 2forxinaifx% 2== 0]

# 而如果这种情况要用 map 或者 filter 方法实现的话,则要多写一些函数

不要使用含有两个以上表达式的列表推导式

# 有一个嵌套的列表,现在要把它里面的所有元素扁平化输出

list= [[

[1,2,3],

[4,5,6]

]]

# 使用列表推导式

flat_list= [xforlist0 inlist forlist1 inlist0 forxinlist1]

# [1, 2, 3, 4, 5, 6]

# 可读性太差,易出错。这种时候更建议使用普通的循环

flat_list= []

forlist0 inlist:

forlist1 inlist0:

flat_list.extend(list1)

数据多时,列表推导式可能会消耗大量内存,此时建议使用生成器表达式

# 在列表推导式的推导过程中,对于输入序列的每个值来说,都可能要创建仅含一项元素的全新列表。因此数据量大时很耗性能。

# 使用生成器表达式

list= (x ** 2forxinrange(0,1000000000))

# 生成器表达式返回的迭代器,只有在每次调用时才生成值,从而避免了内存占用

实际上Python是没有所谓的 switch case 写法的,熟悉C艹和Java的同学可能已经习惯了用 switch case 结构去优雅的处理一些事情,比如这样:

但是在Python中,官方对 switch case 的需求是这样回复的:

" You can do this easily enough with a sequence of if... elif... elif... else. There have been some proposals for switch statement syntax, but there is no consensus (yet) on whether and how to do range tests. "

感觉有点low。

可用字典方法解决这个问题:

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

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

列表推导与循环

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

比较:

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来进行处理,你也不会喜欢抛出的异常的堆栈信息里面有一堆<lambda><genexpr>这样的意义不明的东西,而不是一个明确的行号。而且,很明显这是这段代码最重要的逻辑,这意味着这个逻辑以后可能会变更、会重构,你不会想要把一个列表推导变更得越来越复杂直到被迫推倒重来。

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

lambda表达式与具名函数

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

def my_func():

def subfunc():

...

subfunc()

...

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

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

抛出异常的时候,有函数名称明显要比显示为<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。