PEP8 python编程规范
想要代码水平有所提高,总免不了code review,这个环节除了考察逻辑漏洞,我觉得最关键的一点就是代码的可读性,一个可读性高的代码,更加易读易维护.PEP8包含了大量的如何编写python代码的细节.下面列举几个重要的规则:
空格
在python中,空格是一个很重要的语法,空格的普适性建议:
- 使用空格而不是用tab键来缩进.
- 使用4个空格作每一层的缩进
- 每行代码应该尽量少于79个字符
- 在一个文件中,函数和类之间用两个空行隔开
- 方法与方法之间用一个空行隔开
- 列表索引,函数调用或关键字参数赋值两侧不要用空格
- 变量赋值的等号两边有且仅有一个空格
表达式和语句
- 推荐(if a is not b),不推荐(if not a is b)
- 推荐(if not somelist),不推荐(if len(somelist) == 0)
- if,while,for, try,except等分支关键字请不要放在一行
- 总是把import写在文件最顶部
*args和**kwargs
python的可变参数,比起java要讨喜的多,一个可变参数的方法,解决了java的n多方法重写,有这么好用的特性,不得安排起来吗?
使用场景是预先不知道调用者会传入多少参数的时候
注: *args 和 **kwargs 并不是唯一写法,如果你喜欢,可以写成 *vars , 以及 **vars
*args
*args用来接收一个有序的参数列表,在方法内部可以把args当做list来调用
比如,内置的sum只能求可迭代列表的和,我们想要自己定义一个求和方法:
def my_sum(*args):
res = 0
for n in args:
res+=n
return res
定义了上述方法,我们就可以用my_sum(1,4,5,6,7,8)
来求得不确定个数的和了.
**kwargs
*kwargs用来接收一个参数键值对,在方法内部可以把kwargs当做dict来调用:
比如我们要写一个方法保存不确定的键值对到非关系型数据库.
def save_kv(**kwargs):
for k,v in kwargs.items():
save_single_record(k,v)
save_kv可以用两种方式来调用:
-
save_kv(name=‘zhangsan’,age=18)
-
params = {‘name’:‘zhangsan’,‘age’:18}
save_kv(**params)
调试Debugging
不同于Java这种长在IDE上面的编程语言(不是说没有IDE就写不了Java,我是说大部分情况下写Java都在IDE中),python代码由于轻量,linux内置,很多时候没有IDE的debug环境,很多时候vim就在服务器上面写完了python,那如果出问题了如何debug呢?
答案是pdb(python debug),官网链接附上:
从命令行debug
$ python -m pdb short_script.py
这会触发debugger在脚本第一行指令处停止,在脚本很短的时候很有用;
从脚本内部运行
import pdb
def make_bread():
pdb.set_trace()
return "I don't have time"
print(make_bread())
pdb.set_trace()就是在这一行打了一个断点,与IDE里面的debug模式意思一样.
debugger模式怎么操作呢?
按键 | 操作 |
---|---|
c | 继续执行 |
w | 显示正在执行的代码的上下文信息 |
a | 打印当前函数的参数列表 |
s | 单步进入(IDE中的step into) |
n | 单步跳过(IDE中的step over) |
生成器 Generators 和迭代器 Iterators
可迭代对象(Iterable)
python中的任意对象,只要它定义了可以返回一个迭代器__iter__方法,或者定义了可以支持下标索引的__getitem__方法,那么它就是一个可迭代对象.
迭代器(Iterator)
任意对象,只要定义了next(python2),__next__(python3)方法,它就是一个迭代器.
生成器(Generators)
生成器也是一种迭代器,但是你只能对其迭代一次,因为它们并没有把所有的值保存在内存中,而是在运行时生成.大多数时候生成器是以函数来实现的.然而它们并不是返回一个值,而是用yield(“生出”)一个值.
当斐波那契数列传的n的值很大的时候,就可以用生成器来写:
def fib_gen(n):
a=b=1
for i in range(n):
yield a
a,b = b,a+b
它跟传统方式有什么区别?
传统return写法,如果n=100000(十万),这个方法调用需要占用大量的内存,因为它在内存中开辟了一个十万大小的list,然而用yield就完全不用担心.
三元运算符
同很多编程语言一样,python也有三元运算符.
python的三元运算符有两种写法:
human语言习惯法
为什么叫人类语言习惯法?看下面伪代码:
去打球 if 不下雨 else 宅家里
这,后置说法可能山东老哥们看起来倍儿熟悉吧.
machine的01法
0是False,1是True,数据库里面是不是很常见,这种思想同样可以被用到三元表达式中,对于上述伪代码,就变成了下面这样
(宅家里,去打球)[不下雨]
不下雨会被计算成0或1,0对应二元组的第一个也就是三元运算否的情况,1对应二元组第二个也就是三元运算是的情况.
装饰器 Decorator
相信学过spring的同学都听过AOP这么一个听起来很高大上的玩意,面向切面编程,用注解实现方法执行前后的代码,在python里面,装饰器就可以实现AOP.
要理解装饰器,首先要明确一个概念,方法也是对象,方法也可以当做方法参数
方法也是对象
方法也是对象怎么理解?为了方便理解这个重要概念,看下面代码
> def hi(name='zhangsan'):
> return "hi," + name
> print(hi())
#output: 'hi,zhangsan'
#重点来了
> greet = hi
> print(greet())
#output: 'hi,zhangsan'
> del hi
> print(hi())
#NameError:Nosuch method
> print(greet())
#output: 'hi,zhangsan'
def似乎是定义了一个对象模板,第六行就像对象赋值一样把hi赋值给了greet,由此看来方法也是对象.
方法可以当做方法的参数
正是AOP的核心思想,在python中,方法可以接收一个方法当做参数在其内部执行.
def myWrapper(func):
def wrapTheFunction():
print('look! this is pythonic @Before')
func()
print('and this is pythonic @After')
return wrapTheFunction
上面方法参数是一个方法,返回值也是一个方法,实现了AOP般的装饰Before,After是不是在Spring中见过?
对于一个其他方法来说,就可以用注解来装饰此方法了:
@myWrapper
def fun():
print('this is a function')
自定义的myWrapper,实现了对方法的装饰,但是有一个问题,对于被装饰的方法,print(fun.__name__)打印出来的就是myWrapper,就是说我们把方法包装起来之后,只能看到包装了,那么怎么在包装起来的同时,不改变原来的name呢?
只要用functools.wraps来修改一下我们的装饰器即可:
from functools import wraps
def myWrapper(func):
@wraps(func)
def wrapTheFunction():
print('look! this is pythonic @Before')
func()
print('and this is pythonic @After')
return wrapTheFunction
装饰器的应用场景
授权
授权是web服务面向切面编程的一个典型使用场景,python中的Django跟flask也有大量的使用场景,这里是一个装饰器授权的demo
from functools import wraps
def require(f):
@wraps(f)
def decorated(*args,**kwargs)
auth = request.authorization
if not auth:
authenticate()
return f(*args, **kwargs)
return decorated
日志封装
from functools import wraps
def logit(f):
@wraps(f)
def with_logging(*args,**kwargs):
print(f.__name__ + " was called ")
return f(*args,**kwargs)
return with_logging
@logit
def demo(x):
"""do something"""
return y
# output: demo was called
是不是有点Spring boot的意思了,但是你可能会说,Spring Boot的注解是可以加参数的,python 也可以!
因为@wraps也是装饰器,它接收一个参数,就像普通函数那样,记住,方法就是对象,那么解决方案就是_再!包!一!层!_
from functools import wraps
#不传参的时候默认往std.out里面打印
def logit(logfile='std.out')
def logging_decorator(f):
@wraps(f)
def wrap_func(*args, **kwargs):
log_str = f.__name__ + "was called"
with open(logfile,'a') as file:
file.write(log_str + '\n')
return f(*args, **kwargs)
return wrap_func
return logging_decorator
@logit()
def myfunc1():
"""do something"""
pass
#往std.out里面打印myfunc1 was called
@logit(logfile='demo.log')
def myfunc2():
"""dosome thing"""
pass
#往demo.log里面打印myfun2 was called
__slots__魔法
在python中,每个类都有实例属性,默认情况下python会用一个字典来保存这些变量的属性名跟属性值.
这通常很有用,它允许我们子啊运行时任意设置新的属性.
然而,如果这个类拥有很多已知属性的小类来说,由于字典的可扩展性我们可以猜到,为了保证在扩展时有内存可用,通常会"预留"较大的内存,如果小类的成员数量已知.我们怎么告诉python不要使用可扩展的字典,而是使用一种固定大小的数据结构呢?
答案是__slot__
不使用__slot__的传统方式
class MyClass(object):
def __init__(self,name,age)
self.name = name
self.age = age
self.set_up()
使用了__slot__:
class MyClass(object):
__slot__ = ['name','age']
self.name = name
self.age = age
self.set_up()
第二段代码会为内存减轻负担,通过这个技巧,有些人已经看到内存占用率几乎减少了40%-50%
集合框架 Collections
尽管python的字典元组列表等数据结构已经足够灵活,但是面对一些复杂的结构化数据处理时,还是有些费力,这个时候,就要用到内置的集合框架包collections了.
defaultdict
defaultdict与dict不同,我们不需要检查key是否存在,比如
from collections import defaultdict
colours = (
('Yasoob','Yellow'),
('Ali','Blue'),
('Arham','Green'),
('Ali','Black'),
('Yasoob','Red'),
('Ahmed','Silver'),
)
favorite_colours = defaultdict(list)
for name,color in colours:
favorite_colours[name].append(color)
print(favorite_colours)
#output
# defaultdict(<type 'list'>,
#{'Arham': ['Green'],
# 'Yasoob': ['Yellow', 'Red'],
# 'Ahmed': ['Silver'],
# 'Ali': ['Blue', 'Black']
# })
或者,可以直接嵌套赋值:
import collections
tree = lambda: collections.defaultdict(tree)
some_dict = tree()
some_dict['color']['favorite'] = 'blue'
# {"color":{'favorite':'blue'}}
Counter
Counter是一个计数器,可以针对某项数据进行计数
from collections import Counter
colors = ['blue','green','yellow','blue','blue','yellow']
cnts = Counter(colors)
print(cnts)
# {'blue':3,'yellow':2,'green':1}
deque
deque是一个限制了list部分方法的双端队列,可以从头尾两端添加或删除元素,而且在创建时指定maxlen,就可以限制大小.如果超过大小,数据会从队列另一段被挤出(pop)
推导式 Comprehension
推倒式在python 2和3中都有支持,是python独有且好用的特性,有了它,甚至不用知道map,filter,因为在python3里面 map的结果是一个map对象,filter的结果是一个filter对象,这些用推导式就可以搞定.
列表推导式
nums = [1,2,3,4,5,6]
#map推导式,对nums中的每个元素平方
nums2 = [i**2 for i in nums]
# 如果用map,需要这么写 num2 = list(map(lambda x:x**2, nums))
# filter,筛选偶数
num3 = [i for i in nums if i%2==0]
##如果用filter,需要写 num3 = list(filter(lambda x: x%2==0,nums) )
字典推导式
mcase = {'a':20,'b':15}
# kv互换
mcase_reverse = {v:k for k,v in mcase.items()}
集合推导式
集合推导式跟列表推导式相同,只是用的是大括号{},比如:
nums = [1,2,3,4,5,6]
nums2 = {x**2 for x in nums}