如何写出Pythonic的代码
前言
Python做为在近几年大火的编程语言,被越来越多的开发者所接受。Pythonic 就是很 Python 的 Python 代码,本文主要介绍python语言的特点以及如何使自己的python代码变得更简洁,更优雅。适合python初学者及有一定编程基础的同学阅读。
Python特点
一、简单,易于学习
上手快,语法简洁,类似伪代码,学习成本低,可以让开发者更聚焦于解决问题,而不是语言本身。参考教程:菜鸟教程、廖雪峰的Python教程、官方网站
二、自由开放,跨平台,应用广
完全开源,任何人都可以阅读源码,甚至进行改动。并且在UNIX,Windows上兼容很好。源码参考:python的c语言源码
三、库丰富,管理便捷
有丰富的开源库,并且可以使用pip、easy_install等包管理工具便捷管理。参考:python资源大全
四、动态语言,执行效率较低
变量动态的类型既是优点,也是缺点,虽然非常灵活,但是每次调用时均需要check一下type无疑牺牲了部分性能,而且也可能会增加维护成本。
Pythonic的代码
上文虽然说到Pythonic是以Python的方式写Python代码,但是其实并不是能用Python语言的特性必须用,我们的目标其实就是更简洁,更优雅的代码。下面给大家介绍一些python语言中常用的,便捷的,具有强烈Python风格的特性。
一、切片
切片算是Python中比较常用也比较便捷的特性了,Python里面的字符串、列表和元组都可以看作是序列,都是支持切片操作的,下面简单介绍一下:
切片用到了3个参数:[start_index: stop_index: step],
start_index是切片的起始位置,默认为0
stop_index是切片的结束位置(不包括),默认为序列长度
step可以不提供,默认值是1,步长值不能为0,不然会报错ValueError。
举几个例子:
1、后三个元素:arr[-3:]
2、复制:b=a[:]
3、倒序:arr[::-1]
4、100以内后10个被3整除的数:range(1,100)[2::3][-10:]
需要注意的是,切片很方便,但不要滥用,要有可读性,如果需要使用step为负数的情况最好加注释,如:arr[-2:2:-2],arr[:1:-1]
二、上下文管理
同样是python特有的写法,防止文件未关闭而导致的资源不释放,例子:
with open('\data\data.txt') as myfile:
for line in myfile:
...use line here...
等同于:
myfile=open("/data/data.txt")
try:
for line in myfile:
...use line here...
finally:
myfile.close()
明显第一种更简洁,而且更加pythonic。
三、装饰器
其他语言也有装饰器,但是在python应用的比较广泛。所谓装饰器,实际上就是我有一批函数在执行时均需要实现一个和函数本身关系不大的功能,例如登陆、打印日志、打印函数执行时间等。那么,我们本着同样的功能决不实现2次的原则,将装饰器加入函数中,这样使得代码更加清晰,更加pythonic,下面举2个例子,用法请参考注释:
1、打印函数执行时间
def set_fun(func):
def call_fun(*args, **kwargs):
start_time = time.time()
func(*args, **kwargs)#执行具体函数,这里用到可变参数*args和**kw
end_time = time.time()
print('程序用时:%s毫秒' % ((end_time - start_time)*1000))#在函数的前后均取时间戳,得到函数执行时间
return call_fun#返回内函数的引用,这里用到了闭包,感兴趣的同学可以自行查阅
@set_fun#装饰器
def out(a):
print a
out(123)
输出:
123
程序用时:0.0338554382324毫秒
2、django登陆
def login_required(function=None, redirect_field_name=REDIRECT_FIELD_NAME, login_url=None):
"""
Decorator for views that checks that the user is logged in, redirecting
to the log-in page if necessary.
"""
actual_decorator = user_passes_test(
lambda u: u.is_authenticated(),
login_url=login_url,
redirect_field_name=redirect_field_name
)#登陆具体的执行函数
if function:
return actual_decorator(function)#直接返回一个嵌套在登陆函数中的函数
return actual_decorator
@login_required(login_url='/login/')#装饰器
def index(request):
...
四、列表推导
列表推导是Python内置的非常简单却强大的可以用来创建list、set或dict的生成式。
请看下面的例子:
1、1-10所有数平方的列表
[x * x for x in range(1, 11)]
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
2、两层循环:数字1、2、3能组成的所有2位数
[x * 10 + y for x in range(1, 4) for y in range(1, 4)]
[11, 12, 13, 21, 22, 23, 31, 32, 33]
3、加入if判断:1-10所有奇数平方的列表
[x * x for x in range(1, 11) if x % 2 != 0]
[1, 9, 25, 49, 81]
4、使用其他迭代器:列出当前目录下的所有的隐藏文件及文件夹
[d for d in os.listdir('.') if d[0]=="."]
*5、使用map和lambda推导:1-10所有数的平方
map((lambda x: x*x), range(10))#使用到高阶函数map
五、生成器与迭代器
生成器、迭代器、可迭代对象之间的区别:参考链接
名词解释:
generator:生成器
iterator:迭代器
iterable:可迭代对象
简单来说,list、set、dict等数据结构均是可迭代对象,可以用for语句进行迭代,用iter包装后可以成为迭代器,可用next()函数进行迭代。而生成器是更高级的迭代器,特点为不是一次生成所有数据,而是一边循环一边计算,可以大大的节省内存和cpu,优化程序性能。生成器一般有生成器表达式,和使用yield的生成器函数。
为了方便理解,下面用经典的斐波那契数列举一些例子。
1、可迭代对象
def fib(n):
a=[0,1]
for i in range(1,n):
a.append(a[i-1]+a[i])
return a[1:]
输出:
print fib(10)
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
可以看到fib(10)就是一个list,一个可迭代对象。
2、迭代器
将上例中的fib(10)进行iter包装,生成一个迭代器,可用next函数取值:
fib10=iter(fib(10))
while True:
try:
x=fib10.next()#或者next(fib10)
print x
except StopIteration:
break
输出:
1
1
2
3
5
8
13
21
34
55
内存大小比较:
fib10000=iter(fib(10000))
print sys.getsizeof(fib(10000))
print sys.getsizeof(fib10000)
print fib10000
输出:
80072
64
可以看到一个输出斐波那契前10000的迭代器只占64字节,而list占80072字节。
3、生成器(表达式)
fib10=(i for i in fib(10))
while True:
try:
x=fib10.next()#或者next(fib10)
print x
except StopIteration:
break
输出的结果和上例一样
不知道大家发现没有,如果把列表推导最外面的中括号改成小括号,其实就变成了生成器表达式。
4、生成器(函数)
def fib(n):
a, b = 0, 1
for i in xrange(n):
yield b#使用yield输出每一个迭代的值
a, b = b, a + b
print fib(10)#这是一个生成器
print [i for i in fib(10)]
输出:
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
这个生成器每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。
以上的例子只是非常简单的例子,实际生成器在应用中可能会比较复杂,适合数据量非常大,需要节省内存时使用。
六、其他
1、交换值
a,b=b,a
等同于
temp = a
a = b
b = temp
2、链式比较
0
等同于
a>0 and b>a and b<10
3、真值测试
对于任意对象,python可以直接判断真假,参考下表:
真
假
True
False
任意非空字符串
空的字符串 ''
任意非0数字
数字0
任意非空容器
空的容器 [] () {} set()
其他任意非False
None
4、字符串列表连接
str = "-";
seq = ("a", "b", "c"); # 字符串序列
print str.join( seq );
输出:a-b-c
5、列表求和,最大值,最小值,乘积
numList = [1,2,3,4,5]
sum = sum(numList) #sum = 15
maxNum = max(numList) #maxNum = 5
minNum = min(numList) #minNum = 1
from operator import mul
prod = reduce(mul, numList) #prod = 120 #使用到高阶函数reduce
执行性能较直接循环求值要高,推荐使用
6、字典默认值
dict={}
dict['id'] = dict.get('id',0) + 1
等同于
dict={}
if dict.has_key('id'):
dict['id']+=1
else:
dict['id']=1
dict的get(key,default)方法用于获取字典中key的值,若不存在该key,则将key赋默认值default。
7、for else语句
for x in xrange(1,5):
if x == 5:
print 'find 5'
break
else:
print 'can not find 5!'
如果for语句是以break方式结束的,就不会执行else的语句内容
8、三元符的替代
a if a>b else b
等同于C语言的:
(a>b)?a:b
9、Enumerate
for key,value in enumerate(dict):
等同于
for key in range(len(dict)):
value = array[key]
使用enumerate可以一次性将索引和值取出,避免使用索引来取值
10、any/all的使用
result=any(number<10 for number in numbers)
等同于
for number in numbers:
if number<10:
result = True
break
else:
result=False
结语
Pythonic没有大家想象的那么神秘,了解Python语言的语法和特性后,写出简洁和优雅的代码是我们的目标,即是Pythonic的代码。这不仅仅使得代码可读性增强和维护成本降低,在写代码过程中,对开发者自身的成就感的提高,兴趣的建立也是很有帮助。希望大家都能够写出一手漂亮的Pythonic的代码。