1、and 和 or 取值顺序

  • 当一个or表达式中所有值都为真,Python会选择第一个值
  • 当一个and表达式所有值都为真,Python会选择第二个值。

2、默认参数不能使用可变变量

def func(item, item_list=[]):
	item_list.append(item)
	print(item_list)
func('iphone')
func('xiaomi', item_list=['oppo','vivo'])
func('huawei')

Python中的def语句在每次执行的时候都初始化一个函数对象,这个函数对象就是我们要调用的函数,可以把它当成一个一般的对象,只不过这个对象拥有一个可执行的方法和部分属性。

对于参数中提供了初始值的参数,由于Python中的函数参数传递的是对象,也可以认为是传地址, 在第一次初始化def的时候,会先生成这个可变对象的内存地址,然后将这个默认参数item_list会与这个内存地址绑定。在后面的函数调用中,如果调用方指定了新的默认值,就会将原来的默认值覆 盖。如果调用方没有指定新的默认值,那就会使用原来的默认值。

应该修改为

def func(item, item_list=None):
    if item_list is None:
        item_list=[]
    items=item_list
    items.append(item)
    print(items)

3、哪些情况下不需要续行符?

经过总结,在这些符号中间的代码换行可以省略掉续行符:[],(),{}。另外还有,在多行文本注释中’’’ ,续行符也是可以不写的。

image-20200629194954457

image-20200629195003831

4、return不一定都是函数的终点

众所周知,try…finally…的用法是:不管try里面是正常执行还是有报异常,最终都能保证finally能够执行。同时我们又知道,一个函数里只要遇到return函数就会立马结束。那问题就来了,以上这两种规则,如果同时存在,Python解释器会如何选择?哪个优先级更高?

写个示例验证一下,就明白啦

如果try里的return真的是直接被忽视吗?

我们都知道如果一个函数没有return,会隐式的返回None,假设try里的return真的是直接被忽 视,那当finally下没有显式的return的时候,是不是会返回None昵?

还是写个示例来验证一下:

image-20200629195421863

image-20200629195433351

那结论就出来了,如果finally里有显式的return,那么这个return会直接覆盖try里的return, 而如果finally里没有显式的return,那么try里的return仍然有效。

5、神奇的Intern机制,为什么字符串是不可变对象

Python解释器中使用了 intern (字符串驻留)的技术来提高字符串效率,什么是intern机制? 就是同样的字符串对象仅仅会保存一份,放在一个字符串储蓄池中,是共用的,当然,肯定不能改 变,这也决定了字符串必须是不可变对象。

image-20200629195922618

6、反转字符串/列表

image-20200629200054795

7、字符串的分割技巧

当我们对字符串进行分割时,且分割符是\n,有可能会出现这样一个窘境:

str = "a\nb\n"
print(str)
str.split('\n')
['a', 'b','']

会在最后一行多出一个元素,为了应对这种情况,你可以会多加一步处理。

但我想说的是,完成没有必要,对于这个场景,你可以使用splitlines

str.splitlines()
['a', 'b']

8、+=不等同于=+

对列表进行+=操作相当于extend,而使用=+操作是新增了一个列表。 因此会有如下两者的差异。

image-20200629213240765

image-20200629213251167

9、增量赋值的性能更好

诸如+=和*=这些运算符,叫做增量赋值运算符。

这里使用用+=举例,以下两种写法,在效果上是等价的。

1种
a = 1 ; a += 12种
a = 1; a = a + 1

+=其背后使用的魔法方法是iadd,如果没有实现这个方法则会退而求其次,使用add
这两种写法有什么区别昵?

用列表举例a+=b,使用iadd的话就像是使用了a.extend(b),如果使用add的话,则是a = a+b,前者是直接在原列表上进行扩展,而后者是先从原列表中取出值,在一个新的列表中进行扩 展,然后再将新的列表对象返回给变量,显然后者的消耗要大些。所以在能使用增量赋值的时候尽量使用它。

10、连接多个列表

10.1 利用sum

>>> a = [1,2]
>>> b = [3,4]
>>> c = [5,6]
>>>
>>> sum((a,b,c),[]) [1, 2, 3, 4, 5, 6]

10.2 使用+对多个列表进行相加

list02 + list03
[1, 2, 3, 4, 5, 6, 7, 8, 9]

10.3 借助itertools

使用itertools.chain()函数先可迭代对象(在这里指的是列表)串联起来,组成一个更大的可迭代对象。最后你再利用list将其转化为列表。

>>> from itertools import chain 
>>> list01 = [1,2,3]
>>> list02 = [4,5,6]
>>> list03 = [7,8,9]
>>>
>>> list(chain(list01, list02, list03))
[i, 2, 3, 4, 5, 6, 7, 8, 9]

10.4 使用*解包

使用*可以解包列表,解包后再合并。 示例如下:

>>> list01 = [1,2,3] 

list02 = [4,5,6]

>>> [*list01, *list02] [1, 2, 3, 4, 5, 6]

10.5 使用extend

在字典中,使用update可实现原地更新,而在列表中,使用extend可实现列表的自我扩展。

>>> list01 = [1,2,3]
>>> list02 = [4,5,6]
>>> list01.extend(list02)
>>> 1ist01
[1, 2, 3, 4, 5, 6]

10.6 使用列表推导式

Python里对于生成列表、集合、字典,有一套非常Pythonnic的写法。那就是列表解析式,集合解析式和字典解析式,通常是Python发烧友的最爱,那么今天的主题:列 表合并,列表推导式还能否胜任昵?当然可以,具体示例代码如下:

>>> list01 = [1,2,3]
>>> list02 = [4,5,6]
>>> 1ist03 = [7,8,9]
>>> [x for l in (list01, list02, list03) for x in l]
[1, 2, 3, 4, 5, 6, 7, 8, 9]

10.7 使用heapq

heapq是Python的一个标准模块,它提供了堆排序算法的实现。 该模块里有一个merge方法,可以用于合并多个列表,如下所示

>>> list01 = [1,2,3]
>>> list02 = [4,5,6]
>>> list03 = [7,8,9]
>>> from heapq import merge
>>> list(merge(list01, list02, list03)) [1, 2, 3, 4, 5, 6, 7, 8, 9]

要注意的是,heapq.merge除了合并多个列表外,它还会将合并后的最终的列表进行排序。

>>> list01 = [2,5,3]
>>> list02 = [1,4,6]
>>> list03 = [7,9,8]
>>> from heapq import merge >>>
>>> list(merge(list01, list02, list03)) 
[1, 2, 4, 5, 3, 6, 7, 9, 8]

它的效果等价于下面这行代码:

sorted(itertools.chain(*iterables))

如果你希望得到一个始终有序的列表,那请第一时间想到heapq.merge,因为它采用堆排序,效率 非常高。但若你不希望得到一个排过序的列表,就不要使用它了。

10.8 使用yield from

在yield from后可接一个可迭代对象,用于迭代并返回其中的每一个元素。 因此,我们可以像下面这样自定义一个合并列表的工具函数。

>>> list01 = [1,2,3]
>>> list02 = [4,5,6]
>>> list03 = [7,8,9]
>>> def merge(*lists):
		for l in lists:
		yield from l
>>> list(merge(list01, list02, list03)) 
[1, 2, 3, 4, 5, 6, 7, 8, 9]

11、 合并字典的8种方法

11.1 最简单的原地更新

字典对象内置了一个update方法,用于把另一个字典更新到自己身上。

profile = {"name": "xiaoming", "age": 27}
ext_info = {"gender": "male"}
profile.update(ext_info)
print(profile)
{TnameT: TxiaomingT, TageT: 27, TgenderT: TmaleT}

如果想使用update这种最简单、最地道原生的方法,但又不想更新到自己身上,而是生成一个新的 对象,那请使用深拷贝。

| >>> profile = {"name": "xiaoming", "age":27}
ext_info = {"gender": "male"}   
from copy import deepcopy                     
full_profile =  deepcopy(profile) full_profile.update(ext_info) 
print(full_profile)  {'name': 'xiaoming', 'age': 27, 'gender': 'male'}
print(profile)  {"name": "xiaoming", "age":  27}

11.2 先解包再合并字典

使用**可以解包字典,解包完后再使用diet或者就可以合并。

profile = {"name": "xiaoming", "age": 27}
ext_info = {"gender": "male"}
full_profile01 = {**profile, **ext_info}
print(full_profile01)
{'name': 'xiaoming', 'age': 27, 'gender': 'male'}
full_profile02 = dict(**profile, **ext_info)
print(full_profile02)
{'name': 'xiaoming', 'age': 27, 'gender': 'male'}

11.3 借助itertools

在Python里有一个非常强大的内置模块,它专门用于操作可迭代对象。正好我们字典也是可迭代对象,自然就可以想到,可以使用itertools.chain()函数先将多个字 典(可迭代对象)串联起来,组成一个更大的可迭代对象,然后再使用diet转成字典。

 import itertools 
profile = {"name": "xiaoming", "age": 27}
ext_info = {"gender": "male"}
dict(itertools.chain(profile.items(),ext_info.items()))
{'name': 'xiaoming''age': 27'gender': 'male'}

11.4 借助ChainMap

如果可以引入一个辅助包,那我就再提一个,ChainMap也可以达到和itertools同样的效

from collections import ChainMap >>>
profile = {"name": "xiaoming""age": 27}
ext_info = {"gender": "male"}
dict(ChainMap(profile, ext_info))
{'name': 'xiaoming''age': 27, 'gender': 'male'}

使用ChainMap有一点需要注意,当字典间有重复的键时,只会取第一个值,排在后面的键值并不 会更新掉前面的(使用itertools就不会有这个问题)。

from collections import ChainMap >>>
profile = {"name": "xiaoming""age": 27}
ext_info={"age": 30}
dict(ChainMap(profile, ext_info))
{'name': 'xiaoming''age': 27}

11.5 字典解析式

Python里对于生成列表、集合、字典,有一套非常Pythonnic的写法。那就是列表解析式,集合解析式和字典解析式,通常是Python发烧友的最爱,那么今天的主题:字 典合并,字典解析式还能否胜任昵?当然可以,具体示例代码如下:

profile = {"name": "xiaoming", "age": 27}
ext_info = {"gender": "male"} >>>
{k:v for d in [profile, ext_info] for k,v in d.items〇} {'name': 'xiaoming', 'age': 27, 'gender': 'male'}

11.6 Python3.9新特性

在2月份发布的Python3.9.04a版本中,新增了一个抓眼球的新操作符操作符:|,PEP584 将它称之为合并操作符(UnionOperator),用它可以很直观地合并多个字典。

profile = {"name": "xiaoming", "age": 27}
ext_info = {"gender": "male"}
profile | ext_info
{'name': 'xiaoming', 'age': 27, 'gender': 'male'} 
ext_info | profile
{'gender': 'male', 'name': 'xiaoming', 'age': 27}

除了 |操作符之外,还有另外一个操作符|=,类似于原地更新。

profile  |=  ext_info 
profile
{'name': 'xiaoming', 'age': 27, 'gender': 'male'}

12 条件语句的七种写法

12.1 原代码

if age > 18	:
	return	"已成年"
else:	
	return	"未成年"

12.2ifelse

agel = 20 
msg1 ="已成年"if age1 > 18 else "未成年"

12.3andor

msgl = agel > 18 and "已成年"or "未成年"

12.4 (,)[condition]

msg1 =("未成年""已成年")[age1 > 18]

12.5 (lambda:, 1ambda:)

msg1 = (lambda:"未成年"lambda:"已成年")[age1 > 18]()

12.6 {True:, False:}[]

msgl = {True:"已成年"False:"未成年"}[age1 > 18]

12.7 (() and (,)or (,))[0]

msg1 = ((age1 > 18) and ("已成年",)or ("未成年",))[0]

13 /usr/bin/env python 有什么用?

我们经常会在别人的脚本或者项目的入口文件里看到第一行是下面这样 #!/usr/bin/python 或者这样 #!/usr/bin/env python.不加的话,你每次执行这个脚本时,都得这样:python xx.py。

​ 明白了这个后,再来看看!/usr/bin/env python这个又是什么意思? 当我执行env python时,自动进入了 pythonconsole的模式。当你执行env python时,它其实会去env | grep PATH里(也就是 /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin)这几个路径里去依次查找名为 python的可执行文件。找到一个就直接执行,上面我们的python路径是在/usr/bin/python里,在PATH列表里倒 数第二个目录下,所以当我在/usr/local/sbin下创建一个名字也为python的可执行文件时, 就会执行 /usr/bin/python 了。

那么对于这两者,我们应该使用哪个昵?

​ 个人感觉应该优先使用#!/usr/bin/env python ,因为不是所有的机器的python解释器都是 /usr/bin/python

14 没有root权限时,如何安装Python的第三方包昵?

可以使用pip install —user pkg将你的包安装在你的用户环境中,该用户环境与全局环境并 不冲突,并且多用户之间相互隔离,互不影响

15 自带的缓存机制

数据的生成过程可能需要经过计算,规整,远程获取等操作,如果是同一份数据需要多次使用,每次 都重新生成会大大浪费时间。所以,如果将计算或者远程请求等操作获得的数据缓存下来,会加快后

续的数据获取需求。

为了实现这个需求,Python 3.2 +中给我们提供了一个机制,可以很方便的实现,而不需要你去写 这样的逻辑代码。

这个机制实现于functool模块中的lru_cache装饰器。

@functools.1ru_cache(maxsi ze=None, typed=False)

参数解读:

• maxsize:最多可以缓存多少个此函数的调用结果,如果为None,贝I」无限制,设置为2的幂 时,性能最佳

• typed:若为True,则不同参数类型的调用将分别缓存。

举个例子

from functools import 1ru_cache

@1ru_cache(None) 
def add(x, y):
	print("calculating: %s + %s" % (x, y)) 
	return x + y

print(add(l, 2)) 
print(add(l, 2))

#第二次调用并没有真正的执行函数体,而是直接返回缓存里的结果

16 重定向标准输出到日志

假设你有一个脚本,会执行一些任务,比如说集群健康情况的检查。检查完成后,会把各服务的的健康状况以JSON字符串的形式打印到标准输出。如果代码有问题,导致异常处理不足,最终检查失败,是很有可能将一些错误异常栈输出到标准错误 或标准输出上。由于最初约定的脚本返回方式是以JSON的格式输出,此时你的脚本却输出各种错误异常,异常调 用方也无法解析。

如何避免这种情况的发生昵?我们可以这样做,把你的标准错误输出到日志文件中。

import contextlib

1og_fi1e="/var/1og/you.1og"

def you_task()
	pass

@contextlib.contextmanager 
def close_stdout()
	raw_stdout = sys.stdout 
	file = open(log_file, 'a+') 
	sys.stdout = file
	yield
	sys.stdout = raw_stdout 
	file.close()

with close_stdout(): 
	you_task()