pythonic code_全栈之后端开发系列 - 将Python代码转化为美丽的、习惯用法的Pythonic代码...

Now in its 29th year (and still going strong in terms of popularity), Python stands out for its distinctive syntax compared to those programming languages based on C. Programmers appreciate how Python means not having types; and using four-space indents makes it much easier to identifying blocks. However, there’s much more to Python than just boosted readability.

The term “pythonic” is a bit vague; I like to think of it as describing Python code that does things the “Python way.” It’s analogous in many ways to carpentry: go against the grain, and you’ll have a much harder time getting the wood to do what you want. In essence, “pythonic” means simple, easy to read, and equally easy to comprehend. Pythonic means code that doesn’t just get the syntax right but that follows the conventions of the Python community and uses the language in the way it is intended to be used.

Python哲学,读者自行感悟:

import this

"""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!"""

笔者很希望所有的Python爱好者们积极响应回复,共同完善一个史无前例的Pythonic Tips。以下的每条总结都是笔者自我感觉很优雅的用法,望指正。(代码均在Python3.6环境中运行通过)

数据结构相关变量交换

a = 1

b = 2

a, b = b, a链式比较

a = 3

print( 2 < a < 8) # True

print(1 == a < 2) # False需要一个可迭代对象的索引和值

num_list = [1, 4, 9]

for i, val in enumerate(num_list):

print(i, '-->', val)文件打开与关闭

with open("a.txt", "r", encoding="utf-8") as f:

data = f.read()过滤掉列表List中的负数—列表解析式说到Python中的数据结构,那就离不开list,dictionary,set等存放数据的容器,怎么写出Pythonic的代码,就需要各种Comprehension Expressions;还有当年看到一篇文章,说是要挑战自己不在Python中写for嵌套循环。为什么要挑战自己在代码里不写for循环呢?因为这样可以迫使你去使用比较高级、地道的语法或库。大家尽量试试,Pythonic的本质也是在更少的代码行数、更好的代码阅读、只将缩进用于管理代码文本。

import random

List = random.sample(range(-100, 100), 10)

# [-79, 48, -7, -27, 92, 3, -51, 61, -3, 16]

new_List = [x for x in List if x >= 0]

# [48, 92, 3, 61, 16]筛选出Dict中 值高于90的项—字典解析式

Dict = {'LiLei': 79, 'Jim': 88, 'Lucy': 92}

new_Dict = {k: v for k, v in Dict.items() if v > 90}

# {'Lucy': 92}筛选出Set中能被3整除的元素—集合解析式

import random

List = random.sample(range(-100, 100), 10)

Set = set(List)

# {68, -21, -85, -83, 46, 78, 11, 53, -72, -97}

new_Set = {s for s in Set if s % 3 == 0}

# {-72, -21, 78}给元祖中的每个元素命名,提高程序的可读性实际应用场景这种方式就只针对元祖,因为元祖中的数值在定义好后不可修改,正好应用在存储固定格式且不会修改的数据,而且这样可以节省内存开销。

from collections import namedtuple

Student = namedtuple('Student', ['name', 'age', 'sex', 'email'])

one_student = Student('Rick', '18', 'male', 'loveweihaitong@foxmail.com')

# one_student.name --> Rick给可迭代对象中的每个元素命名(解构赋值)实际上,下面演示的解构赋值可以用在任何可迭代对象上面,而不仅仅是列表或者元组。包括字符串,文件对象,迭代器和生成器。

student = ['Tom', 18, 'male']

name, age, gender = student

print(name, age, gender)

# Tom 18 male

num_list = [100, 19, 20, 98]

first, *left_num_list, last = num_list

print(first, left_num_list, last)

# 100 [19, 20] 98

student = [['Tom', (98, 96, 100)], ['Jack', (98, 96, 100)]]

for name, (first, second, third) in student:

print(name, first, second, third)

# Tom 98 96 100

# Jack 98 96 100统计随机序列中出现频率最高的三个元素,并统计它们出现的次数这里的随机序列虽说全部是整数的列表,但是列表如果是字符串,依然可以统计,所以这个方法可以应用于NLP中的数据处理

from collections import Counter

from random import randint

random_sequence = [randint(0, 5) for _ in range(10)]

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

result = Counter(random_sequence)

# Counter({5: 3, 1: 2, 0: 2, 2: 1, 4: 1, 3: 1})

new_result = result.most_common(3)

# [(5, 3), (1, 2), (0, 2)]根据字典中值的大小,对字典中的项进行排序

from random import randint

d = {x: randint(60, 100) for x in 'xyzabc'}

# {'x': 82, 'y': 95, 'z': 69, 'a': 100, 'b': 63, 'c': 72}

# method 1:根据字典中的值得大小,使用key参数对字典中的项进行排序,如果key参数中改成的item[0],

# 其实就是对字典的键值进行排序,但键值一般为字符串,排序起来一般无意义,看具体情况而定。

new_d = sorted(d.items(), key=lambda item: item[1], reverse=True)

# [('a', 100), ('y', 95), ('x', 82), ('c', 72), ('z', 69), ('b', 63)]

# method 2:根据字典中的值得大小,使用zip函数对字典中的项进行排序

new_d = sorted(zip(d.values(), d.keys()), reverse=True)

# [(100, 'a'), (95, 'y'), (82, 'x'), (72, 'c'), (69, 'z'), (63, 'b')]快速找到字典中的公共键

from random import randint

from functools import reduce

d1 = {x: randint(10, 30) for x in ['库里', '汤普森', '杜兰特', '格林', '帕楚里亚', '欧文', '詹姆斯']}

d2 = {x: randint(10, 30) for x in ['汤普森', '杜兰特', '格林', '麦基', '香波特', '詹姆斯', '库里', '韦德']}

d3 = {x: randint(10, 30) for x in ['格林', '杜兰特', '利文斯顿', '库里', '香波特', '詹姆斯', '汤普森', '韦德']}

# d1 -> {'库里': 29, '汤普森': 14, '杜兰特': 25, '格林': 11, '帕楚里亚': 22, '欧文': 17, '詹姆斯': 28}

# d2 -> {'汤普森': 20, '杜兰特': 19, '格林': 23, '麦基': 13, '香波特': 21, '詹姆斯': 22, '库里': 25, '韦德': 26}

# d3 -> {'格林': 23, '杜兰特': 23, '利文斯顿': 29, '库里': 15, '香波特': 19, '詹姆斯': 29, '汤普森': 15, '韦德': 14}

# 使用map函数,得到所有字典的keys集合。

# 站在更高阶、更函数化的变成方式考虑一下,如果你想映射一个序列到另一个序列,直接调用map函数。

# map()的使用方法形如map(f(x),Itera).对,它有两个参数,第一个参数为某个函数,第二个为可迭代对象。

Set = map(dict.keys, [d1, d2, d3])

# 使用reduce函数,取所有函数的keys集合的交集, 并集用|,差集用-

reduce(lambda a, b: a & b, Set)

# {'库里', '汤普森', '杜兰特', '格林', '詹姆斯'}如何快速融合字典(合并字典)

query = {'id': 1, 'render_fast': True}

post = {'email': 'j@j.com', 'name': 'Joff'}

route = {'id': 271, 'title': 'Fast_apps'}

# 这里面如果有相同的键,后面键的值会覆盖前面的值,例如本例中的id。

merge_dic = {**query, **post, **route}

# {'id': 271, 'render_fast': True, 'email': 'j@j.com', 'name': 'Joff', 'title': 'Fast_apps'}使用双端循环队列

from collections import deque

# 应用场景可以实现用户历史记录功能

q = deque([], 5)

q.append(1)

q.append(2)

q.append(3)

q.append(4)

q.append(5)

q.append(6)

# q -> deque([2, 3, 4, 5, 6], maxlen=5)

# list也可以用pop(0)来删除第一个元素,但是list在内存中是顺序存储的,

# 删除第一个元素,会导致之后的所有元素都会前移,效率很低,

# 插入类似。开头如果有大量的删除和插入操作,避免使用list。

names = deque(['c', 'd', 'e'])

names.popleft()

names.appendleft('b')

names.append('f')

# deque(['b', 'd', 'e', 'f'])关键字属性

def connect(user, server, replicate, use_ssl):

pass

connect('Buddleha', 'dn_svr', True, False)

# 使用*作为第一个形参,当调用函数的时候,实参必须带着形参的名字,

# 这样会使函数调用更加语义化

def connect(*, user, server, replicate, use_ssl):

pass

connect(user='Buddleha', server='dn_svr', replicate=True, use_ssl=False)使用defaultdict(是dict的子类)允许我们通过工厂方法来动态创建不存在的属性

from collections import defaultdict

my_dict = defaultdict(lambda: 'Default Value')

my_dict['a'] = 42

print(my_dict['a'])

print(my_dict['b'])

# 42 Default Value

在工作中我经常用defaultdict来构造一颗树形数据结构来满足我的常规需求,实例如下:

from collections import defaultdict

import json

def tree():

"""Factory that creates a defaultdict that also uses this factory"""

return defaultdict(tree)

root = tree()

root['Page']['Python']['defaultdict']['Title'] = 'Using defaultdict'

root['Page']['Python']['defaultdict']['Subtitle'] = 'Create a tree'

root['Page']['Java'] = None

print(json.dumps(root, indent=4))

“”“

{

"Page": {

"Python": {

"defaultdict": {

"Subtitle": "Create a tree",

"Title": "Using defaultdict"

}

},

"Java": null

}

}

”“”函数参数

def f(*args, **kwargs):

print("Arguments: ", args)

print("Keyword arguments: ", kwargs)

f(3, 4, 9, foo=42, bar=7)

# Arguments: (3, 4, 9)

# Keyword arguments: {'bar': 7, 'foo': 42}“Switch/Case“ 实现不同于我用过的其它编程语言,Python 没有 switch / case 语句。为了实现它,我们可以使用字典映射, 也是非常pythonic的写法,体现了python中函数为一等公民的思想。函数式编程,很好的解耦每个case中的业务逻辑。提高复用性和可维护性

# pythonic: 巧用字典的数据结构的代码代替通常switch语句,完成case的功能。

def get_sunday():

return 'sunday'

def get_monday():

return 'monday'

def get_tuesday():

return 'tuesday'

def get_default():

return 'unknown'

def switcher(arg):

switch = {

0: get_sunday,

1: get_monday,

2: get_tuesday,

}

return switch.get(arg, get_default)()

day = 9

day_name = switcher(day)

print(day_name)

# unknown链式函数调用以下方法可在一行中调用多个函数

def add(a, b):

return a + b

def subtract(a, b):

return a - b

a, b = 4, 5

print((subtract if a > b else add)(a, b)) # 9没有 if-else 语句的简单计算器

import operator

action = {

"+": operator.add,

"-": operator.sub,

"/": operator.truediv,

"*": operator.mul,

"**": pow

}

print(action["-"](50, 25)) # 25

迭代器与生成器相关

迭代器(生成器)在Python中是一种很常用也很好用的数据结构,比起列表(list)来说,迭代器最大的优势就是延迟计算,按需使用,从而提高开发体验和运行效率,以至于在Python 3中map,filter等操作返回的不再是列表而是迭代器。

序列是迭代器,但是不是所有的迭代器都是序列。当有人说“迭代器”这个词时,你只能假设他们的意思是“你可以迭代的东西”。不要假设迭代器可以被循环遍历两次、询问它们的长度或者索引。

迭代器是 Python 中最基本的可迭代形式。如果你想在代码中做一个惰性迭代,请考虑迭代器,并考虑使用生成器函数或生成器表达式。

最后,请记住,Python 中的每一种迭代都依赖于迭代器协议,因此理解迭代器协议是理解 Python 中的循环的关键。生成器函数实现可迭代对象

class PrimeNumbers(object):

def __init__(self, *, start, end):

self.start = start

self.end = end

def isPrimeNum(self, k):

if k < 2:

return False

for i in range(2, k):

if k % i == 0:

return False

return True

def __iter__(self):

for k in range(self.start, self.end + 1):

if self.isPrimeNum(k):

yield k

pn = PrimeNumbers(start=1, end=30)

print(list(pn))

# [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]使用itertools里的islice()对迭代器做切片操作

"""对迭代器做切片操作"""

from itertools import islice

# 由上一个浮点生成器的结果得出

print(list(islice(floatRange, 2, 6)))

# [2.0, 2.5, 3.0, 3.5]在一个for语句中迭代多个可迭代对象

from random import randint

from itertools import chain

chinese = [randint(60, 100) for _ in range(10)]

math = [randint(60, 100) for _ in range(10)]

english = [randint(60, 100) for _ in range(10)]

# chinese -> [98, 65, 99, 94, 97, 74, 95, 64, 90, 87]

# math -> [88, 98, 63, 73, 80, 73, 65, 79, 74, 64]

# english -> [95, 87, 61, 65, 62, 94, 91, 88, 72, 78]

# 并行

total = []

# zip()函数可将多个迭代对象合并,每次迭代返回一个元祖

for c, m, e in zip(chinese, math, english):

total.append(c + m + e)

# total -> [281, 250, 223, 232, 239, 241, 251, 231, 236, 229]

# 串行

total = []

# itertools中chain()可以进行多个迭代对象的连接

print(list(chain(chinese, math, english)))

# [98, 65, 99, 94, 97, 74, 95, 64, 90, 87, 88, 98, 63, 73, 80, 73, 65, 79, 74, 64, 95, 87, 61, 65, 62, 94, 91, 88, 72, 78]使用 itertools.accumulate 实现累加

import itertools

x = itertools.accumulate(range(10))

print(list(x))

# [0, 1, 3, 6, 10, 15, 21, 28, 36, 45]使用 itertools.chain 连接多个列表或者迭代器

import itertools

x = itertools.chain(range(3), range(4), [3,2,1])

print(list(x))

# [0, 1, 2, 0, 1, 2, 3, 3, 2, 1]使用 itertools.combinations 求列表或生成器中指定数目的元素不重复的所有组合数学中组合公式,则是指从给定n个数的元素中仅仅取出指定r个数的元素,不考虑排序

import itertools

x = itertools.combinations(range(4), 3)

print(list(x))

# [(0, 1, 2), (0, 1, 3), (0, 2, 3), (1, 2, 3)]使用itertools.combinations_with_replacement 求允许重复元素的组合

import itertools

x = itertools.combinations_with_replacement(range(4), 3)

print(list(x))

# [(0, 0, 0), (0, 0, 1), (0, 0, 2), (0, 0, 3), (0, 1, 1), (0, 1, 2), (0, 1, 3), (0, 2, 2), (0, 2, 3), (0, 3, 3), (1, 1, 1), (1, 1, 2), (1, 1, 3), (1, 2, 2), (1, 2, 3), (1, 3, 3), (2, 2, 2), (2, 2, 3), (2, 3, 3), (3, 3, 3)]使用itertools.compress 按照真值表筛选元素

import itertools

x = itertools.compress(range(5), (True, False, True, True, False))

print(list(x))

# [0, 2, 3]使用 itertools.cycle 循环指定的列表和迭代器

import itertools

x = itertools.cycle('ABC')

print(list(itertools.islice(x, 0, 10, 1)))

# ['A', 'B', 'C', 'A', 'B', 'C', 'A', 'B', 'C', 'A']使用 itertools.dropwhile 按照真值函数丢弃掉列表和迭代器前面的元素

import itertools

x = itertools.dropwhile(lambda e: e < 5, range(10))

print(list(x))

# [5, 6, 7, 8, 9]使用 itertools.filterfalse 保留对应真值为False的元素

import itertools

x = itertools.filterfalse(lambda e: e < 5, (1, 5, 3, 6, 9, 4))

print(list(x))

# [5, 6, 9]使用 itertools.groupby 按照分组函数的值对元素进行分组

import itertools

x = itertools.groupby(range(10), lambda x: x < 5 or x > 8)

for condition, numbers in x:

print(condition, list(numbers))

"""True [0, 1, 2, 3, 4]False [5, 6, 7, 8]True [9]"""使用 itertools.islice 上文使用过的函数,对迭代器进行切片

import itertools

x = itertools.islice(range(10), 0, 9, 2)

print(list(x))

# [0, 2, 4, 6, 8]itertools.permutations 产生指定数目的元素的所有排列(顺序有关)数学中排列公式,就是指从给定n个数的元素中取出指定r个数的元素,进行排序

import itertools

x = itertools.permutations(range(4), 3)

print(list(x))

# [(0, 1, 2), (0, 1, 3), (0, 2, 1), (0, 2, 3), (0, 3, 1), (0, 3, 2), (1, 0, 2), (1, 0, 3), (1, 2, 0), (1, 2, 3), (1, 3, 0), (1, 3, 2), (2, 0, 1), (2, 0,3), (2, 1, 0), (2, 1, 3), (2, 3, 0), (2, 3, 1), (3, 0, 1), (3, 0, 2), (3, 1, 0), (3, 1, 2), (3, 2, 0), (3, 2, 1)]使用 itertools.product 产生多个列表和迭代器的(积)

import itertools

x = itertools.product('ABC', range(3))

print(list(x))

# [('A', 0), ('A', 1), ('A', 2), ('B', 0), ('B', 1), ('B', 2), ('C', 0), ('C', 1), ('C', 2)]使用 itertools.repeat 简单的生成一个拥有指定数目元素的迭代器

import itertools

x = itertools.repeat(0, 5)

print(list(x))

# [0, 0, 0, 0, 0]使用 itertools.takewhile 保留元素直至真值函数值为假(与dropwhile相反)

import itertools

x = itertools.takewhile(lambda e: e < 5, range(10))

print(list(x))

# [0, 1, 2, 3, 4]使用 itertools.zip_longest类似于zip,不过已较长的列表和迭代器的长度为准

import itertools

x = itertools.zip_longest(range(3), range(5))

y = zip(range(3), range(5))

print(list(x))

# [(0, 0), (1, 1), (2, 2), (None, 3), (None, 4)]

print(list(y))

# [(0, 0), (1, 1), (2, 2)]使用 range() 将列表分块为指定大小的较小列表

from math import ceil

def chunk(lst, size):

return list(

map(lambda x: lst[x * size:x * size + size],

list(range(0, ceil(len(lst) / size))))

)

chunk([1, 2, 3, 4, 5], 2)

# [[1,2],[3,4],5]使用递归来展开潜在的深度列表

def deep_flatten(lst):

result = []

def spread(args):

ret = []

for arg in args:

if isinstance(arg, list):

ret.extend(arg)

else:

ret.append(arg)

return ret

result.extend(

spread(list(map(lambda x: deep_flatten(x) if isinstance(x, list) else x, lst)))

)

return result

deep_flatten([1, [2], [[3], 4], 5])

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

字符串处理相关使用format() / f"" 美化字符串打印格式

hello, python = 'Hello world', 'Python'

print('{0} from {1}.'.format('Hello world', 'Python'))

print('{greet} from {language}.'.format(greet='Hello world', language='Python'))

print(f'{hello} from {python}') # 推荐,时间开销最小拆分含有多种分隔符的字符串

import re

s = 'ab;gdgfdg|iuiu,sddsd|iweoiru\t8998;rst,qwq\tererer'

# 正则来对字符串进行选择

res = re.split('[,;\t|]+', s)

# ['ab', 'gdgfdg', 'iuiu', 'sddsd', 'iweoiru', '8998', 'rst', 'qwq', 'ererer']判断字符串a是否以字符串b开头或者结尾

a = '61duke'

a.startswith('b')

# False

a.endswith('e')

# True调整字符串中文本的格式使用正则表达式re.sub()函数做字符串替换,利用正则表达式的捕获组,捕获每个部分内容,在替换字符串中调整各个捕获组的顺序

import re

f = '2017-10-01 asd adassa;lkdasadaadads ' \

'2017-10-01 sfd gfl;;dgfdg dfgdfgdfgfd ' \

'2017-10-01 rtur tyurt yurtyu ' \

'2017-10-01 vbnvvcvc sd sdsd fsd f ' \

'2017-10-01 qwrwq y5445dfbh d '

print(re.sub('(?P\d{4})-(\d{2})-(\d{2})', r'\2/\3/\g', f))

# 10/01/2017 asd adassa;lkdasadaadads 10/01/2017 sfd gfl;;dgfdg dfgdfgdfgfd 10/01/2017 rtur tyurt yurtyu 10/01/2017 vbnvvcvc sd sdsd fsd f 10/01/2017 qwrwq y5445dfbh d将多个小字符串拼接成一个大字符串

a = ['61', 'duke', 2, 'love', 'python']

new_a = ''.join((str(x) for x in a))

print(new_a)

# 61duke2lovepython对字符串左、中、右、居中对齐

s = 'abc'

s.ljust(20, '=')

# abc=================

s.rjust(20, '=')

# =================abc

s.center(20, '=')

# ========abc=========去掉或者替换字符串中不需要的字符

s = 'abc 123+++++ '

s.replace('+', '')

# abc 123适用于多平台的文件路径写法

from pathlib import Path, PureWindowsPath

# 将文件路径以windows格式命名, 然后将路径转换成适合当前操作系统的路径

correct_path = Path(PureWindowsPath("../source_data/text_files/raw_data.txt"))

print(correct_path)检测两个字符串是否互为变位词(即互相颠倒字符顺序)

from collections import Counter

def anagram(first, second):

return Counter(first) == Counter(second)

anagram("abcd3", "3acdb") # True首字母大写

s = "programming is awesome"

print(s.title()) # Programming Is Awesome

类与对象相关使用__slots__()魔法方法创建大量实例,节省内存

class Player1(object):

def __init__(self, uid, name, status=0, level=1):

self.uid = uid

self.name = name

self.stat = status

self.level = level

class Player2(object):

__slots__ = ['uid', 'name', 'status', 'level']

def __init__(self, uid, name, status=0, level=1):

self.uid = uid

self.name = name

self.stat = status

self.level = level

# 减少实例中的__dict__属性让对象支持上下文管理要实现上下文管理器,必须实现两个方法 – 一个负责进入语句块的准备操作,另一个负责离开语句块的善后操作。

Python类包含两个特殊的方法,分别名为:__enter__以及__exit__(双下划线作为前缀及后缀)。

当一个对象被用作上下文管理器时:

(1)__enter__方法将在进入代码块前被调用。

(2)__exit__方法则在离开代码块之后被调用(即使在代码块中遇到了异常)。

class PypixContextManagerDemo(Object):

def __enter__(self):

print('Entering the block')

def __exit__(self, *unused):

print('Exiting the block')

with PypixContextManagerDemo():

print('In the block')

# Entering the block

# In the block

# Exiting the block

装饰器相关使用装饰器来缓存数据Python动态编程语言支持函数中套函数,以及函数返回函数。称之为闭包。装饰器可以理解为外层函数给内层函数提供支持的运行环境。

"""题目一: 斐波那契数列(Fibonacci Sequence),又称黄金分割数列,指的是这样一个数列:1,1,2,3,5,8,13,21,.....这个数列从第三项开始,每一项都等于前两项之和。求数列第N项"""

def memo(func):

cache = {}

def wrap(*args):

if args not in cache:

cache[args] = func(*args)

return cache[args]

return wrap

@memo

def fibonacci(n):

if n <= 1:

return 1

return fibonacci(n - 1) + fibonacci(n - 2)

import sys

sys.setrecursionlimit(1000000)

print(fibonacci(50))

# 20365011074

"""题目二: 一个共有10个台阶的楼梯,从下面走到上面,一次只能迈1-3个台阶,并且不能后退,走完这个楼梯共有多少种方法。"""

@memo

def climb(n, steps):

count = 0

if n == 0:

count = 1

elif n > 0:

for step in steps:

count += climb(n - step, steps)

return count

print(climb(10, (1, 2, 3)))

# 274属性可以修改的函数装饰器

from functools import wraps

import time

from random import randint

import logging

def warn(timeout):

timeout = [timeout]

def decorator(func):

def wrapper(*args, **kwargs):

start = time.time()

res = func(*args, **kwargs)

used = time.time() - start

if used > timeout[0]:

msg = '%s:%s>%s' % (func.__name__, used, timeout[0])

logging.warn(msg)

return res

def setTimeout(k):

nonlocal timeout # python 3

# timeout = k

timeout[0] = k

wrapper.setTimeout = setTimeout

return wrapper

return decorator

@warn(1.5)

def rand():

print('In test')

while randint(0, 1):

time.sleep(0.5)

for _ in range(30):

rand()

# In test

# In test

# WARNING:root:rand : 1.5010855197906494 > 1.5

# In test

# In test

# .........

写Python项目从菜鸟到老手通用进阶步骤

我们通常写一个程序的时候,都会经历从单->双->多,从硬编码到柔编码,基本上都会经历下面的这几步,其实代码是讲究柔术的。下面是总览图,我们接着来一 一解释。

写程序的蝶变先让程序跑起来

初步重构代码

增加注释

一定要考虑异常

添加配置文件

测试用例很重要

日志模块很重要

性能上的优化

再次重构,函数变类

代码检查

先让程序跑起来:当我们刚开始写一个程序的时候,无论是一个自动化脚本,还是一个小游戏,还是一个爬虫,还是一个模块,对于新手来说最简单的方法就是先work:用最直接的方法,让程序先能work.

比如直接用函数写,直接用假的变量,先把程序运行起来

运行正常的逻辑,让程序跑通

初步重构代码:当你的代码已经初步run起来之后,接着我们要对代码进行简单的梳理和整形,会从下面几个地方修剪修剪:变量名,函数名字的重构

函数的状态要不要有返回值

if else这样的嵌套太多,考虑提取

函数进行提取和重构,每个函数的功能单一原则

函数的参数有没有考虑缺省值

全局变量,有没有大写,有没有写在开头

增加注释:代码修剪过之后,发现利索多了,这个时候要趁热打铁把注释写上去!很多同学不太爱写注释,觉得麻烦。如果这个代码就你一人用,或者说这个代码很短,确实可以不写!

如果这个代码有几千行,并且会多个人合作开放,后续还要扩展功能,那么你花5分钟来写注释,未来会帮你节省5个小时的维护时间!

代码首先是给人看的,然后才是给机器运行的!

一定要考虑异常:代码跑的好好的,看起来没有问题,是不是很有成就感!如果是爬虫,你把网络断了,看看有没有异常处理

如果是文件,你把句柄改成None看看有没有问题

如果取列表里面的item,当你获得列表为空,会发生什么

所有的这一切,你有没有考虑到异常,有没有考虑到程序的健壮性。要考虑异常分支,if 里面有没有else的情况

for里面如果出现了错误,有没有break

判断list[],需要看一下这个列表是否为空

文件读写,有没有try/except

拿到一个句柄,比如SSH,SQL,这样有没有考虑到句柄的有效性

添加配置文件:我们刚从把局部变量,提到了全局变量。现在我们需要把这个全局变量放到一个配置文件里面,把实现和接口分离,降低耦合度。对于用户来说只要改配置文件就行了。比如可以把整个的全局变量放到一个config.py里面,然后在主程序里面用from config import * 这样的话,对后续的修改方便很多。

测试用例很重要:程序虽然写好了,不管你的程序是几十行的小程序小脚本,还是几千上万行的项目,测试用例是一定要设计。简单的程序可以设一些断言assert,看一些有无异常,对于复杂的逻辑,一定要针对性的设计多个分支回路来测一下代码。

日志模块很重要:有同学说上面6步之后,我感觉代码已经很不错了,这么还有进化!Python的代码很多都是在服务区上运行的,你总不能一直都是print吧,尤其是对大型的程序,没有日志怎么行,建议用logging模块进行日志的记录。

性能上的优化:如果你处理的任务仅仅是几百上千,对性能要求不高,对实时性要求不高那还好。如果你要处理几十万条数据呢!我记得我有一次爬stackoverflow的数据,有96万的数据,你不用并发,估计等程序运行完,你已经睡着啦!这个时候一定要考虑并发的处理,到底是用多进程,还是多线程,线程池,还是用协程,需要思考!当然性能上的优化并不单是单线程变多线程,还有数据结构的优化,比如什么时候该用列表,什么时候用元组,哪一种对内存消耗少,查询快。

再次重构,函数变类:为了让我们的代码更加易于扩展,适应变化!我们需要用类把变量和函数进行封装,设计一些接口,那些是对外开发的,那些是对外封闭的。哪些用静态函数包裹,哪些用实例方法。是不是需要用一些装饰器来简化代码。相同类别的函数,进行整合,合并要一个类里面。

多个功能用多个类来表示,方便维护和扩展。

类与类之间,考虑他们的内在关系。用组合还是继承,用一些简单的设计模式,根据程序的特性用比如工厂模式,观察者。

代码检查:代码到这里应该是比较优美了,等一下是不是忘记了一个很重要的东西,我们有没有遵循PEP8的代码风格。比如命名规范,每一行的长度,看似是细节,但是很多时候细节决定成败。为啥不用这个神器检查一下Pylint库,它能快速帮你查缺补漏。

Learning to write code in a pythonic manner is something to aspire to. If you haven’t already, check out the style guide for Python, PEP8, written by Guido van Rossum and others. It features a lot of good advice, including one mention of pythonic code guidelines. (One note of caution: I do think it’s possible to go overboard, however, and write some horribly incomprehensible code in very few lines; make sure your code is truly elegant, not just short.)

欢迎留言增加新的Pythonic代码技巧,完善该教程…

本文为2018/5/13的《Pythonic Code ----- Transforming Code into Beautiful, Idiomatic Python》和2019/9/29的《即学即用的30段Python实用代码》博客迁移,并进行更新,日后在此继续更新...

赞助感谢支持 感谢赞助

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值