Zodiac Wang
  • Home
  • Categories
  • Tags
  • Archives

Pythonic技巧

学过C或C++的人再学python,写出的代码难免会保留C/C++的风格,而看起来没有那么Pythonic。

Python代码的风格实际上是非常简洁的,以最少的代码量写出完整的功能

本文为博主总结的一些帮助我们写出更加Pythonic代码的技巧

Table of Contents

  • 1  变量交换
  • 2  元素遍历
  • 3  字符串连接
  • 4  上下文管理器
  • 5  使用生成式
  • 6  使用装饰器
  • 7  使用 collections 库
  • 8  序列解包
  • 9  遍历字典 key 和 value
  • 10  链式比较操作
  • 11  if else 三目运算
  • 12  真值判断
    • 12.1  判断诸多条件是否至少有一个成立
    • 12.2  或的另一种写法
    • 12.3  判断诸多条件是否全部成立
  • 13  for else 语句
  • 14  字符串格式化的8种方法
  • 15  切片
  • 16  使用生成器
  • 17  获取字典元素的 get 方法
  • 18  预设字典返回的默认值
  • 19  函数参数 unpack
  • 20  注意函数默认参数
  • 21  为 dict 添加 __missing__ 方法
  • 22  python 解释器中的 _
  • 23  try/except/else
  • 24  print 重定向输出到文件
  • 25  省略号
  • 26  pow 还有第三个参数
  • 27  isinstance 可以接收一个元组
  • 28  字典的无限递归
  • 29  Python 可以认识 Unicode 中的数字
  • 30  不能访问到的属性
  • 31  使用计数器对象进行计数
  • 32  漂亮的打印 JSON
  • 33  字典的剧本
    • 33.1  键的存在性
  • 34  无限循环的结束
  • 35  序列乘法
  • 36  显示循环进度条
  • 37  Other-Trick
    • 37.1  Jupyter Notebook 中获取函数帮助
    • 37.2  修改多处的同一标识符名字
    • 37.3  这些Python代码技巧,你肯定还不知道
In [1]:
# 让 notebook 输出所有单行变量
from IPython.core.interactiveshell import InteractiveShell
InteractiveShell.ast_node_interactivity = "all"

变量交换¶

python 中交换两个变量不需要引入临时变量

In [2]:
a, b = 1, 2
a, b

a, b = b, a
a, b
Out[2]:
(1, 2)
Out[2]:
(2, 1)

自然想到三个变量的交换

In [3]:
a, b, c = 1, 2, 3
a, b, c

a, b, c = c, a, b
a, b, c
Out[3]:
(1, 2, 3)
Out[3]:
(3, 1, 2)

元素遍历¶

普通的遍历,类似其他语言中的 for each 或是基于范围的 for 循环

In [4]:
for i in range(5):
    print(i)
0
1
2
3
4

使用 enumerate 进行带索引的容器遍历

In [5]:
colors = ['red', 'green', 'blue', 'yellow']

for index, color in enumerate(colors):
    print('{}, {}'.format(index, color))
0, red
1, green
2, blue
3, yellow

事实上,enumerate 还接受第二个参数,用于指定 index 的起始位置,默认为 0

In [6]:
list(enumerate(colors, 1))
Out[6]:
[(1, 'red'), (2, 'green'), (3, 'blue'), (4, 'yellow')]

字符串连接¶

经常使用的 字符串连接方法有三种,join, format 和 + 操作符 在需要连接的字符串个数较少的情况下,+ 效率较高,随着个数的增加,join 的优势会更大

In [7]:
names = ['raymond', 'rachel', 'matthew', 'roger',
         'betty', 'melissa', 'judith', 'charlie']

print(', '.join(names))
raymond, rachel, matthew, roger, betty, melissa, judith, charlie

上下文管理器¶

In [8]:
fr = open('data/dataa.txt', 'w')
fr.write('x')
fr.close()

with open('data/test.txt') as f:
    data = f.read()
    print(data)
Out[8]:
1
x

使用生成式¶

最常见的是列表推导式

In [9]:
[2*i for i in range(10)]
Out[9]:
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

其次还有:集合推导式和字典推导式,原先在 python2 版本中的 set 是需要通过 set 函数来显示转换的,但后来 python3 中直接进行 set 推导的语法也被移植到了 python2 中。不过要注意的是 () 定义的不是元组推导式而是生成器

In [10]:
numbers = [1, 2, 3]
my_dict = {number: number * 2 for number in numbers if number > 1}

my_dict
Out[10]:
{2: 4, 3: 6}

推导式可以嵌套,而且可以加入一定的条件机制,各个条件之间的关系是 与

In [11]:
[(x, y)
 for x in range(10)
 for y in range(10)
 if x + y == 5
 if x > y]
Out[11]:
[(3, 2), (4, 1), (5, 0)]

甚至可以利用列表生成式来实现快速排序,非常优雅,但是对于一般我们写排序算法尽量进行要原址排序

In [12]:
def quicksort(lst):
    if len(lst) == 0:
        return []
    else:
        return quicksort([x for x in lst[1:] if x < lst[0]]) + [lst[0]] + \
            quicksort([x for x in lst[1:] if x >= lst[0]])


quicksort([2, 3, 5, 1, 6, 9, 8, 3])  # 使用装饰器
Out[12]:
[1, 2, 3, 3, 5, 6, 8, 9]

使用装饰器¶

In [13]:
import time
from functools import wraps

def timethis(func):
    '''
    函数计时
    '''
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(func.__name__, end-start)
        return result
    return wrapper

# 下面我们使用这个被包装后的函数并检查它的元信息:

@timethis
def countdown(n):
    '''
    Counts down
    '''
    while n > 0:
        n -= 1

countdown(100000)
countdown 0.003022432327270508

使用 collections 库¶

deque

In [14]:
from collections import deque
names = deque(['raymond', 'rachel', 'matthew', 'roger',
               'betty', 'melissa', 'judith', 'charlie'])
names.popleft()
names.appendleft('mark')
names
Out[14]:
'raymond'
Out[14]:
deque(['mark',
       'rachel',
       'matthew',
       'roger',
       'betty',
       'melissa',
       'judith',
       'charlie'])

deque 还具有 maxlen 关键字参数,可以限制其最大容量

In [15]:
letters = deque(['a', 'b', 'c', 'd', 'e'], maxlen=5)
letters

letters.append('f')
letters
Out[15]:
deque(['a', 'b', 'c', 'd', 'e'])
Out[15]:
deque(['b', 'c', 'd', 'e', 'f'])

序列解包¶

In [16]:
p = 'vttalk', 'female', 30, 'python@qq.com'
name, gender, age, email = p

另外可以用 *somename 来接收不需要的一大段值

In [17]:
name, *middle, email = p
name, middle, email
Out[17]:
('vttalk', ['female', 30], 'python@qq.com')

遍历字典 key 和 value¶

In [18]:
d = {'a': 1, 'b': 2, 'c': 3}

for k, v in d.items():  # python2 有 iteritems 方法,但在 python3 中已经被移除
    print(k, '--->', v)
a ---> 1
b ---> 2
c ---> 3

链式比较操作¶

In [19]:
age = 59

18 < age < 60

False == False == True  # 等价于 (False == False) == (False == True)
Out[19]:
True
Out[19]:
False

if else 三目运算¶

In [20]:
gender = 'female'
text = '男' if gender == 'male' else '女'
text
Out[20]:
'女'

真值判断¶

很多情况下 不需要使用 == 和 != 去比较真假

In [21]:
values = object
if values:
    print('haha')
haha

真假值对照表

类型 False True
bool False True
str '' 非空str
数值 0,0.0 非0数值
容器 [],(),{},set() 非空容器
None None 非None对象

以下为几条 and or 的替代写法

判断诸多条件是否至少有一个成立¶

In [22]:
math, physics, computer = 40, 60, 70

if any([math < 60, physics < 60, computer < 60]):
    print("Not Pass")
Not Pass

或的另一种写法¶

In [23]:
i, a, b = 1, 2, 3
# way 1
if i == a or i == b:
    print('yes')
# way 2
if i in (a, b):
    print('yes')

判断诸多条件是否全部成立¶

In [24]:
math, physics, computer = 61, 60, 70

if all([math >= 60, physics >= 60, computer >= 60]):
    print("Pass")
Pass

for else 语句¶

除非 break 否则 else 语句是一定会执行的

In [25]:
mylist = [0, 1, 2, 3]
for i in mylist:
    if i == 4:
        break  # 跳出整个 for else 循环
    print(i)
else:
    print('oh yeah')
0
1
2
3
oh yeah

字符串格式化的8种方法¶

推荐使用 format 或 fstring

1.+号连接

In [26]:
stra, strb = "Hello", "World"
stra + strb
Out[26]:
'HelloWorld'

2.在print中使用,连接

In [27]:
print(stra, strb)
Hello World

3.直接连接

In [28]:
print('hello'         'world')
print('hello''world')
helloworld
helloworld

这实际上是Python的一个语法糖,连接的字符串会自动拼接为一个字符串

In [29]:
import dis
def func():
    strr = 'a''b'
    
dis.dis(func)
  3           0 LOAD_CONST               1 ('ab')
              2 STORE_FAST               0 (strr)
              4 LOAD_CONST               0 (None)
              6 RETURN_VALUE

4.% 格式化

在 Python 2.6 以前,%操作符是唯一一种格式化字符串的方法,它也可以用于连接字符串

In [30]:
print('%s %s' % (stra, strb))
Hello World

5.format 函数

format 方法是 Python 2.6 中出现的一种代替 % 操作符的字符串格式化方法,同样可以用来连接字符串。

In [31]:
print('{} {}'.format(stra, strb))
Hello World

6.join 函数

字符串的内置方法

In [32]:
print('-'.join([stra, strb]))
Hello-World

f-string

Python 3.6 中引入了 Formatted String Literals(字面量格式化字符串),简称 f-string,f-string 是 % 操作符和 format 方法的进化版,使用 f-string 连接字符串的方法和使用 %操作符、format 方法类似。

In [33]:
f'{stra} {strb}'
Out[33]:
'Hello World'

8.* 号

In [34]:
stra * 3
Out[34]:
'HelloHelloHello'

实际上对应 __mul__ 魔法方法

In [35]:
stra.__mul__(3)
Out[35]:
'HelloHelloHello'

切片¶

切片操作接受三个参数,分别是起始位置,结束位置(不包括)和步长

python 中常见的容器拷贝(浅)就是通过切片来实现的,因为赋值操作本质上是将指针窒息指向目标对象

In [36]:
items = list(range(10))

items[0:4]

items[1::2]

items[::]  # 浅拷贝

items[:]  # 浅拷贝
Out[36]:
[0, 1, 2, 3]
Out[36]:
[1, 3, 5, 7, 9]
Out[36]:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Out[36]:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

使用生成器¶

In [37]:
def fib(n):
    a, b = 0, 1
    while a < n:
        yield a
        a, b = b, a + b


gen = fib(5)

for i in gen:
    print(i)
0
1
1
2
3

Python 3 中的 next 方法变为 next 函数

一个实例化的生成器在迭代完后便不可再使用,即只能迭代使用一次

In [38]:
try:
    next(gen)
except Exception as e:
    print(e)

gen = fib(3)
next(gen)
next(gen)

Out[38]:
0
Out[38]:
1

获取字典元素的 get 方法¶

当 key 不存在时即返回默认值

In [39]:
d = {'name': 'foo'}
d.get("namey", "unknown")
Out[39]:
'unknown'

预设字典返回的默认值¶

当 key 不存在时即返回默认值

第一种方式 setdefault

In [40]:
data = {('a', 1), ('b', 2), ('a', 3)}
groups = {}

for (key, value) in data:
    groups.setdefault(key, []).append(value)

groups
Out[40]:
{'a': [1, 3], 'b': [2]}

第二种方式 defaultdict 库

In [41]:
from collections import defaultdict

groups = defaultdict(list)

for (key, value) in data:
    groups[key].append(value)

groups
Out[41]:
defaultdict(list, {'a': [1, 3], 'b': [2]})

函数参数 unpack¶

本质上就是容器/可迭代对象的解包

In [42]:
def foo(x, y):
    print(x, y)


alist = [1, 2]
adict = {'x': 1, 'y': 2}

foo(*alist)
foo(**adict)
1 2
1 2

注意函数默认参数¶

In [43]:
def foo(x=[]):
    x.append(1)
    print(x)


foo()
foo()
[1]
[1, 1]

更安全的做法

In [44]:
def foo(x=None):
    if x is None:
        x = []
    x.append(1)
    print(x)


foo()
foo()
[1]
[1]

为 dict 添加 __missing__ 方法¶

In [45]:
class Dict(dict):
    def __missing__(self, key):
        self[key] = []
        return self[key]


dct = Dict()
dct["foo"].append(1)
dct["foo"].append(2)

dct["foo"]
dct["bar"]
Out[45]:
[1, 2]
Out[45]:
[]

这种行为很像 collections.defaultdict

In [46]:
from collections import defaultdict
dct = defaultdict(list)

dct["foo"]  # 对不存在的 key 进行访问或自动创建空容器
dct["bar"].append("Hello")
dct
Out[46]:
[]
Out[46]:
defaultdict(list, {'foo': [], 'bar': ['Hello']})

python 解释器中的 _¶

用于获取上一次的对象

In [47]:
list(range(4))
_
Out[47]:
[0, 1, 2, 3]
Out[47]:
[0, 1, 2, 3]

Zen of Python

In [48]:
import this
The Zen of Python, by Tim Peters

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!

try/except/else¶

try:
     Normal execution block
except A:
     Exception A handle
except B:
     Exception B handle
except:
     Other exception handle
else:
     if no exception,get here
finally:
     print("finally")
In [49]:
try:
    print(parrot)
except NameError:
    print("'E's pining!")
else:
    print("This parrot is no more!")
finally:
    print('done')
'E's pining!
done

print 重定向输出到文件¶

In [50]:
# Python 3 已移除
# print >> open("somefile", "w+"), "Hello World"

省略号¶

In [51]:
...
Out[51]:
Ellipsis

pow 还有第三个参数¶

pow 还接受第三个参数,用来求模 pow(x, y, z) == (x ** y) % z

In [52]:
pow(4, 2, 2)
Out[52]:
0

isinstance 可以接收一个元组¶

元组内的类型是或的关系

In [53]:
isinstance("1.3", (float, int))
isinstance("1.3", (int, float, str))
Out[53]:
False
Out[53]:
True

字典的无限递归¶

In [54]:
a, b = {}, {}
a['b'] = b
b['a'] = a
a
Out[54]:
{'b': {'a': {...}}}
In [55]:
a, b = [], []
a.append(b)
b.append(a)
a
Out[55]:
[[[...]]]

Python 可以认识 Unicode 中的数字¶

In [56]:
int(u'1234')
Out[56]:
1234

不能访问到的属性¶

In [57]:
class O(object):
    pass


o = O()
setattr(o, "can't touch this", 123)

这个属性是访问不到的,因为 '的存在,不过,能用 setattr 设置属性,自然就可以用 getattr 取出

使用计数器对象进行计数¶

这是 collections 库中的一个 dict 子类,专门用于解决计数问题, 子类还包括 most_common 等方法

In [58]:
from collections import Counter

c = Counter('hello world')

c
c.most_common(2)
Out[58]:
Counter({'h': 1, 'e': 1, 'l': 3, 'o': 2, ' ': 1, 'w': 1, 'r': 1, 'd': 1})
Out[58]:
[('l', 3), ('o', 2)]

漂亮的打印 JSON¶

使用 indent 关键字参数即可偏亮的打印 JSON

In [59]:
import json
data = {'a': 1, 'b': 2}

print(json.dumps(data))
print(json.dumps(data, indent=2))
{"a": 1, "b": 2}
{
  "a": 1,
  "b": 2
}

字典的剧本¶

键的存在性¶

In [60]:
dct = {'a': 1}
# 不推荐
# dct.has_key('a') Python 3 移除
# 推荐
'a' in dct
Out[60]:
True

无限循环的结束¶

In [61]:
# 不推荐
file = open("data/dataa.txt", "r")

while 1:   # infinite loop
    line = file.readline()
    if not line:  # 'readline()' returns None at end of file.
        break
    print(line)
file.close()

file = open("data/dataa.txt", "r")

# 推荐
for line in file:
    print(line)
file.close()

# 更好 使用上下文管理器
with open('data/dataa.txt', 'r') as f:
    for line in f:
        print(line)
x
x
x

open 函数的功能也可由 file 替代

序列乘法¶

In [62]:
[0] * 2

' ' * 2
Out[62]:
[0, 0]
Out[62]:
'  '

显示循环进度条¶

In [63]:
import time

N = 100

for i in range(N+1):
    time.sleep(0.01)
    if i % 10 == 0:
        print(i, end="\r")
100

进阶版

In [64]:
import sys
import time


def progress_bar(num, total):
    rate = num/total
    pecent = int(100*rate)
    r = "\r[{}{}]{}%".format("*"*pecent, " "*(100-pecent), pecent)
    sys.stdout.write(r)
    sys.stdout.flush()


for i in range(N+1):
    time.sleep(0.01)
    progress_bar(i, N)
[****************************************************************************************************]100%

Other-Trick¶

Jupyter Notebook 中获取函数帮助¶

光标移动至函数名右侧(或选中函数名),按住Shift + Tab键弹出帮助文本框

修改多处的同一标识符名字¶

按住Ctrl鼠标移动光标同时选中多处编辑位置,启动多行编辑

这些Python代码技巧,你肯定还不知道¶

References

  • 公众号文章:代码这样写更优雅(Python版)
  • 公众号文章:代码这样写不止于优雅(Python版)
  • Python的隐藏特性(StackOverflow)
  • 提高你的Python编码效率
  • Python:字典的剧本(翻译自Python:The Dictionary Playbook)
  • Python 惯例
  • 18式优雅你的Python
  • 这些Python代码技巧,你肯定还不知道
  • PyZh

  • « MNIST数据集分析
  • PythonCookbook笔记(1)-数据结构和算法 »

Published

10 3, 2018

Category

posts

Tags

  • Python 16

Contact

  • Zodiac Wang - A Fantastic Learner
  • Powered by Pelican. Theme: Elegant