Pythonic Code ----- Transforming Code into Beautiful, Idiomatic Python

Pythonic代码—将代码转化为美丽的、习惯用法的Python

其实是不怎么好解释Pythonic这个稍带点中文特有意蕴的英文词。但是我的标题就得必须用英语解释,因为这样真的很优雅。这篇博客就是总结很Pythonic的代码用法,从不同角度方向总结(偏向‘代码实现’方向,如果是优雅地代码风格,遵守PEP8就好)。笔者很希望Python爱好者们积极回复,共同完善一个史无前例的Pythonic tips。以下的每条总结都是笔者自我感觉很优雅地用法。望指正…(代码全部在python3.6.3版上运行通过了。总结也只针对Python3的语法,没有理由,未来趋势而已。)

Now in its 26th 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.

The Zen of Python, by Tim Peters

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哲学,慢慢感悟。

数据结构相关的话题

1. 变量交换

最pythonic代码:

a = 1
b = 2
a, b = b, a
2. 当需要一个可迭代对象的索引和值

最pythonic代码:

num_list = [1, 4, 9]
for i, val in enumerate(num_list):
    print(i, '-->', val)
3. 文件打开与关闭

最pythonic代码:

with open('a.txt') as f:
    data = f.read()
4. 过滤掉列表List中的负数—列表解析式

说到python中的数据结构,那就离不开list,dictionary,set等存放数据的容器,怎么写出pythonic的容器代码,就需要各种Comprehension Expressions;

还有当年看到一篇文章,说是要挑战自己不在Python中写for嵌套循环。为什么要挑战自己在代码里不写for循环呢?因为这样可以迫使你去使用比较高级、地道的语法或库。大家尽量试试,Pythonic的本质也是在更少的代码行数、更好的代码阅读、只将缩进用于管理代码文本。

最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]
5. 筛选出Dict中 值高于90的项—字典解析式

最pythonic代码:

Dict = {'LiLei': 79, 'Jim': 88, 'Lucy': 92}
new_Dict = {k: v for k, v in Dict.items() if v > 90}
# {'Lucy': 92}
6. 筛选出Set中能被3整除的元素—集合解析式

最pythonic代码:

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}
7. 给元祖中的每个元素命名,提高程序的可读性

实际应用场景这种方式就只针对元祖,因为元祖中的数值在定义好后不可修改,正好应用在存储固定格式且不会修改的数据,而且这样可以节省内存开销。
最pythonic代码:

from collections import namedtuple

Student = namedtuple('Student', ['name', 'age', 'sex', 'email'])
one_student = Student('Rick', '18', 'male', '[email protected]')
# one_student.name --> Rick
8. 给列表中的每个元素命名(解构赋值)

最pythonic代码:

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
9. 统计随机序列中出现频率最高的三个元素,并统计它们出现的次数

这里的随机序列虽说全部是整数的列表,但是列表如果是字符串,依然可以统计,所以这个方法可以应用于NLP中的数据处理
最pythonic代码:

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)]
10. 根据字典中值的大小,对字典中的项进行排序

最pythonic代码:

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')]
11. 快速找到字典中的公共键

最pythonic代码:

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)
# {'库里', '汤普森', '杜兰特', '格林', '詹姆斯'}
12. 如何快速融合字典(合并字典)

最pythonic代码:

query = {'id': 1, 'render_fast': True}
post = {'email': '[email protected]', 'name': 'Joff'}
route = {'id': 271, 'title': 'Fast_apps'}

# 这里面如果有相同的键,后面键的值会覆盖前面的值,例如本例中的id。
merge_dic = {**query, **post, **route}
# {'id': 271, 'render_fast': True, 'email': '[email protected]', 'name': 'Joff', 'title': 'Fast_apps'}
13. 使用双端循环队列

最pythonic代码:

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'])
14. 关键字属性

最pythonic代码:

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)

迭代器与生成器相关话题

1. 生成器函数实现可迭代对象

生成器函数,自我理解就是小return的感觉
最pythonic代码:

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]
2. 如何进行反向迭代以及如何实现反向迭代

最pythonic代码:

class FloatRange(object):
    def __init__(self, start, end, step=0.1):
        self.start = start
        self.end = end
        self.step = step

    # 正向迭代器
    def __iter__(self):
        t = self.start
        while t <= self.end:
            yield t
            t += self.step

    # 反向迭代器
    def __reversed__(self):
        t = self.end
        while t >= self.start:
            yield t
            t -= self.step


floatRange = FloatRange(1.0, 4.0, 0.5)
# 正向迭代器实例
print(list(floatRange))
# [1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0]
# 反向迭代器实例
print(list(reversed(floatRange)))
# [4.0, 3.5, 3.0, 2.5, 2.0, 1.5, 1.0]
3. 使用itertools里的islice()对迭代器做切片操作

最pythonic代码:

"""对迭代器做切片操作"""
from itertools import islice

# 由上一个浮点生成器的结果得出
print(list(islice(floatRange, 2, 6)))
# [2.0, 2.5, 3.0, 3.5]
4. 在一个for语句中迭代多个可迭代对象

最pythonic代码:

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]

字符串处理相关话题

1. 使用format()美化字符串打印格式

最pythonic代码:

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

print('{greet} from {language}.'.format(greet='Hello world', language='Python'))
2. 拆分含有多种分隔符的字符串

最pythonic代码:

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']
3. 判断字符串a是否以字符串b开头或者结尾

最pythonic代码:

a = 'weihaitong'
a.startswith('b')
# False
a.endswith('g')
# True
4. 调整字符串中文本的格式

使用正则表达式re.sub()函数做字符串替换,利用正则表达式的捕获组,捕获每个部分内容,在替换字符串中调整各个捕获组的顺序
最pythonic代码:

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<year>\d{4})-(\d{2})-(\d{2})', r'\2/\3/\g<year>', 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
5. 将多个小字符串拼接成一个大字符串
a = ['yang', 'tong', 2, 'love', 'wei', 'hai', 'tong']
new_a = ''.join((str(x) for x in a))
# yangtong2loveweihaitong
6. 对字符串左、中、右、居中对齐

最pythonic代码:

s = 'abc'
s.ljust(20, '=')
# abc=================
s.rjust(20, '=')
# =================abc
s.center(20, '=')
# ========abc=========
7. 去掉或者替换字符串中不需要的字符

最pythonic代码:

s = 'abc   123+++++  '
s.replace('+', '')
# abc   123

类与对象相关话题

1. 使用__slots__()魔法方法创建大量实例,节省内存

最pythonic代码:

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__属性
2. 让对象支持上下文管理

要实现上下文管理器,必须实现两个方法 – 一个负责进入语句块的准备操作,另一个负责离开语句块的善后操作。
Python类包含两个特殊的方法,分别名为:__enter__以及__exit__(双下划线作为前缀及后缀)。
当一个对象被用作上下文管理器时:
(1)__enter__方法将在进入代码块前被调用。
(2)__exit__ 方法则在离开代码块之后被调用(即使在代码块中遇到了异常)。

最pythonic代码:

class PypixContextManagerDemo:
    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

装饰器相关话题

1. 使用装饰器来缓存数据

python动态编程语言支持函数中套函数,以及函数返回函数。称之为闭包。装饰器可以理解为外层函数给内层函数提供支持的运行环境。
最pythonic代码:

"""
题目一: 斐波那契数列(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
2. 属性可以修改的函数装饰器

最pythonic代码:

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
# .........

结论

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代码技巧,完善该教程…..
或者您直接将您的建议发给我的邮箱([email protected]),我会及时更新此教程….

猜你喜欢

转载自blog.csdn.net/tong_t/article/details/80301506