写给C类程序员。
从顺序容器中取多个元素赋值给变量
比如我们从数据库查询数据或者逐行一个CSV文件,获取到一条记录,每个字段被存在了一个list中(python的list)。对标C++可能是数组或vector。我们要取第一个、第二个元、第三个字段赋值给三个变量。
C程序员用python可能会这么写:
name = record[0]
age = record[1]
sex = record[2]
pythonic的写法是:
name, age, sex = record[:3]
当然C++11以后的std::tie也能做到类似的事。但比python灵活性还差点,谁让python是脚本语言呢。
交互两个变量的值
交换变量a和b的值,C程序员的习惯,当然是三段式啦。
temp = a
a = b
b = tmp
pythonic的写法是:
b, a = a, b
变量多次比较相等
一个条件,需要在一个变量等于三个值的情况下触发,应该怎么写呢?C程序员的习惯:
if var == 100 or var == 202 or var == 999:
...
pythonic的写法是用in:
if var in (100, 202, 999):
...
当然如果是比较一个变量不等于多个值的时候,可以用not in
循环从dict取数据,key可能不存在怎么办?
比如我们有一组数据,list里面套着dict。求dict里面某个key的value之和。但是dict里面这个key不是都存在(不存在的时候视作0)。
C程序员的习惯:
sum = 0
for data in data_list:
if data.has_key('score'): # 或者 if 'score' in data:
sum += data['score']
这样写当然没错,但是我们可以更简洁一点:
sum = 0
for data in data_list:
sum += data.get('score', 0)
没有三目运算符怎么办?
python没有?: 这个运算符。所以想实现一些比较简短的条件表达式(比如max = (a>b)?a:b ),不能像C那样简洁。C程序员感到不适,但也很耐心的写下:
if a > b:
max = a
else:
max = b
其实python也可以压缩到一行:
max = a if a > b else b
这个你可能觉得还不算pythonic,那么你还可以这样写:
max = (a > b and [a] or [b])[0]
如果,a,b都是非负数。可以这样写:
# 只适用不存在负数的情况
max = (a > b and a or b)
上面这个用法不适合存在负数的情况。因为当a是0的时候会视作False,即使a > b也会输出b。所以可以用转换为list的那种写法。
一行 if 可以做很多事。同样一行for也能做很多。
给list每个元素乘方
C语言的循环,C++的for_each?不用。
li = [1, 2, 3, 4, 5]
li = [i*i for i in li]
当然我们还有map函数:
m = map(lambda x:x*x, li)
li = list(m)
因为map函数返回的类型不是list了。所以有时候可能要转换一下。当然除了map,也少不了reduce。其他的还有filter等。这些函数用起来就和STL的algorithm很像了,C++程序员可能不会觉得pythonic。
一行代码提取出dict中的key存储到list
conf_dict = {'host':'127.0.0.1', 'port':3600, 'user':'root', 'pswd':123456}
key_list = [key for key in conf_dict]
当然提取value组成list也可以啦:
value_list = [conf_dict[key] for key in conf_dict]
仅仅是举例子如何用一行for。其实提取字典key和value有keys()、values()、items()。
但是如果你不是想单纯地提取key和value,这个一行for是有价值的。比如,提取key组成list,并对key统一做一个操作:
#key转大写
key_list = [key.upper() for key in conf_dict]
提取value组成list,然后对value统一做一个操作:
#批量减法
student_dict = {'xiaowang': 1992, 'xiaoming': 1993, 'xiaozhang': 1995}
aget_list = [2020-student_dict[key] for key in student_dict]
一行for构造dict
alpha_list = ['aaa', 'bbb', 'ccc', 'ddd']
alpha_dict = {alpha: alpha for alpha in alpha_list}
# 'aaa':'aaa'
alpha_dict = {alpha: alpha.upper() for alpha in alpha_list}
# 'aaa':'AAA'
alpha_dict = {alpha: alpha*2 for alpha in alpha_list}
# 'aaa':'aaaaaa'
有一定局限性,因为value和key要有某种关联。
活学活用的话,可以配合函数使用,生成一个函数名到函数的映射。来实现一个简单反射。
# 假设有一个list,里面存放的是函数
def test_a():
pass
def test_b():
pass
...
fun_list = [test_a, test_b, test_c, test_d]
fun_dict = {fun.__name__: fun for fun in fun_list}
# 'test_a':test_a
# 调用
fun_dict['test_a']()
当然Python语法天生支持反射,只是语法稍显繁琐,没有特别需求的话,自己像这样简单弄一个就行啦。
没有重载怎么办?
相信我。你可能并不需要重载。
def test(*argv, **kwargs):
print('========')
if len(argv) != 0:
print(argv)
print(*argv)
if len(kwargs) != 0:
print(kwargs)
test(1, 2, 4)
test({'name': 'guodong', 'age': 26, 'sex': 1})
test(name='guodong', age=26, sex=1)
d = {'name': 'guodong', 'age': 26, 'sex': 1}
test(**d)
test(2020, 3, 24, name='guodong', age=26, sex=1)
输出:
========
(1, 2, 4)
1 2 4
========
({'name': 'guodong', 'age': 26, 'sex': 1},)
{'name': 'guodong', 'age': 26, 'sex': 1}
========
{'name': 'guodong', 'age': 26, 'sex': 1}
========
{'name': 'guodong', 'age': 26, 'sex': 1}
========
(2020, 3, 24)
2020 3 24
{'name': 'guodong', 'age': 26, 'sex': 1}
不管什么类型都能传递。list也好,dict也罢,或者直接打散传入参数也好,参数个数可变也行。太灵活了。
打开文件
OK,这个老生常谈了。
普通写法:
f = open('file.txt')
lines = f.reads()
...
f.close()
pythonic:
with open('file.txt') as f:
lines = f.reads()
...
手撸AOP?NO
Java程序员可能对AOP模式比较熟悉。python中官方支持的装饰器语法,是实现AOP的利器。简单很多。这里不展开AOP了,介绍一下装饰器,其实思想就是GOF23的装饰器模式,只不过python官方给出了语法糖!好吧,这糖我吃了。
无侵入性地打印函数名和函数参数:
import functools
def log(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print('===========')
print(func.__name__)
print('args:', *args)
print('kwargs:', *kwargs)
return func(*args, **kwargs)
return wrapper
@log
def test(*argv, **kwargs):
pass
像上面介绍*argv和**kwargs的那个例子,来执行一遍,这次我把test的函数体清空了。因为之前它里面也只是为了示例,里面没有逻辑只是打印参数而以,现在我们不需要了。
调用:
test(1, 2, 4)
test({'name': 'guodong', 'age': 26, 'sex': 1})
test(name='guodong', age=26, sex=1)
d = {'name': 'guodong', 'age': 26, 'sex': 1}
test(**d)
test(2020, 3, 24, name='guodong', age=26, sex=1)
可以输出:
===========
test
args: 1 2 4
kwargs:
===========
test
args: {'name': 'guodong', 'age': 26, 'sex': 1}
kwargs:
===========
test
args:
kwargs: name age sex
===========
test
args:
kwargs: name age sex
===========
test
args: 2020 3 24
kwargs: name age sex
为什么python要单独提一个pythonic,像C、C++、Java都没有Cic、C++ic、Javaic这种说法呢……个人感觉啊,那就是从C类编程语言(C、C++、Java、C#)入门开始学习编程的人太多了。把C类语言中的一些习惯,当成了编程语言的共性。然后Python又是一门很容易上手的语言,把知识进行替换,概念做一下迁移,很容易学会使用Python。但是这个代码风格始终逃不出C的园囿。多年以前我也会用Python写点脚本、写一些批处理任务。没觉得自己有什么问题,直到后来换了工作。用Python跑MR的时候,同事看了我的代码,说了一句:你这个代码啊,从实现来说,写的没错。但是呢,不够pythonic!
一句惊醒梦中人。自以为Python简单,自己会写,但对于Python之博大,其实并不自知,像我这样。这篇回答主要写给C类程序员,写给你,也写给我。好了。pythonic的语法技巧有很多,先介绍到这,没介绍的后面可能会补,比如yield,for else等。但是有些其实很难界定说是不是pythonic。只能挑出一些和C类语言迥异的写法,姑且认为因为和C类或其他语言写作习惯不一样,故而称之为pythonic吧……。
python的语法有爽点,也有痛点,语言风格不值得升华到什么哲学高度。比如没有switch,没有++,我真不会洗……一门语言而言,学一学,玩一玩,凑合着混口饭吃。捕蛇者说zhuanlan.zhihu.com