python 冷知识 二十二条

版权声明:本文为博主([email protected])原创文章,未经博主允许不得转载。 https://blog.csdn.net/z_feng12489/article/details/89470174

省略号(…)也是对象

这是省略号,在Python中,一切皆对象。它也不例外。

在 Python 中,它叫做 Ellipsis 。

在 Python 3 中你可以直接写…来得到这玩意。

>>> ...
Ellipsis
>>> type(...)
<class 'ellipsis'>

它转为布尔值时为真

>>> bool(...)
True

最后,这东西是一个单例。

>>> id(...)
140711200433488
>>> id(...)
140711200433488

这东西有啥用呢?据说它是Numpy的语法糖,不玩 Numpy 的人,可以说是没啥用的。

在网上只看到这个 用 … 代替 pass ,稍微有点用,但又不是必须使用的。

>>> try:
...     1/0
... except ZeroDivisionError:
...     ...   # 在语法结构上省略
...
Ellipsis  

类的首字母不一定是大写

在正常情况下,我们所编写的所见到的代码,好像都默许了类名首字母大写,而实例用小写的这一准则。但这并不是强制性的,即使你反过来的也没有关系。

但有一些内置的类,首字母都是小写,而实例都是大写。

比如 bool 是类名,而 True,False 是其实例;
比如 ellipsis 是类名,Ellipsis是实例;
还有 int,string,float,list,tuple,dict 等一系列数据类型都是类名,它们都是小写。

增量赋值的性能更好

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

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

>>> a = 1 ; a += 1
>>> a = 1; a = a + 1

+= 其背后使用的魔法方法是 __iadd__,如果没有实现这个方法则会退而求其次,使用 __add__ 。

这两种写法有什么区别呢?

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

所以在能使用增量赋值的时候尽量使用它。

and 和 or 的取值顺序

and 和 or 是我们再熟悉不过的两个逻辑运算符。而我们通常只用它来做判断,很少用它来取值。

如果一个or表达式中所有值都为真,Python会选择第一个值,而and表达式则会选择第二个。

>>> (2 or 3) * (5 and 7)
14
>>> (0 or 3) * (5 and 7)
21

如何修改解释器提示符

正常情况下,我们在 终端下 执行Python 命令是这样的。

>>> for i in range(2):
...     print(i)
...
0
1

你是否想过 >>> 和 … 这两个提示符也是可以修改的呢?

>>> import sys
>>> sys.ps1
'>>> '
>>> sys.ps2
'... '
>>> sys.ps1 = 'zheAtPythonTerminal>>>'
zheAtPythonTerminal>>> sys.ps2 = '----------------------'
zheAtPythonTerminal>>> for i in range(2):
----------------------   print (i)
----------------------
0
1

默认参数最好不为可变对象

函数的参数分三种

  • 可变参数
  • 默认参数
  • 关键字参数

这三者的具体区别,和使用方法在 廖雪峰的教程 里会详细的解释。这里就不搬运了。

今天要说的是,传递默认参数时,新手很容易踩雷的一个坑。

先来看一个示例:

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

如果你不知道最后一个函数的输出原因请往下看,否则略过:

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

对于参数中提供了初始值的参数,由于 Python 中的函数参数传递的是对象,也可以认为是传地址,在第一次初始化 def 的时候,会先生成这个可变对象的内存地址,然后将这个默认参数 item_list 会与这个内存地址绑定。

在后面的函数调用中,如果调用方指定了新的默认值,就会将原来的默认值覆盖。如果调用方没有指定新的默认值,那就会使用原来的默认值。
在这里插入图片描述

访问类中的私有方法

大家都知道,类中可供直接调用的方法,只有公有方法(protected类型的方法也可以,但是不建议)。也就是说,类的私有方法是无法直接调用的。

这里先看一下例子

>>> class Kls():
...     def public(self):
...             print ('Hello public wprld')
...     def __private(self):
...             print ('Hello private world')
...     def call_private(self):
...             self.__private()
...
>>> ins = Kls()
>>> # 调用公有方法
>>> ins.public()
Hello public wprld
>>> # 调用私有方法  不行
>>> ins.__private()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Kls' object has no attribute '__private'
>>> # 但你可以借用内部的公有方法进行代理
>>> ins.call_private()
Hello private world

既然都是方法,那我们真的没有方法可以直接调用吗?

当然有啦,只是建议你千万不要这样弄,这里只是普及,让你了解一下。

>>> ins._Kls__private()
Hello private world
>>> ins.call_private()
Hello private world

哪些情况下不需要续行符

在写代码时,为了代码的可读性,代码的排版是尤为重要的。

为了实现高可读性的代码,我们常常使用到的就是续行符 \。

>>> a = 'talk is cheap, '\
...     'show me the code.'

那有哪些情况下,是不需要写续行符的呢?

经过总结,在这些符号中间的代码换行可以省略掉续行符:[],(),{}

>>> my_list = [1, 2, 3,
...            4, 5, 6]
>>> my_tuple = (1, 2, 3,
...             4, 5, 6)
>>> my_dict = {'name': 'zhe',
...             'age': '24'}

另外还有,在多行文本注释中 ‘’’ ,续行符也是可以不写的。

>>> test = ''' talk is cheap,
...             show me the code.'''

上面只举了一些简单的例子。

但你要学会举一反三。一样的,在以下这些场景也同样适用

类,和函数的定义。

列表推导式,字典推导式,集合推导式,生成器表达式。

for 死循环

for 循环可以说是 基础得不能再基础的知识点了。但是如果让你用 for 写一个死循环,你会写吗?

这是个开放性的问题,在往下看之前,建议你先尝试自己思考,你会如何解答。

好了,如果你还没有思路,那就来看一下:

>>> for i in iter(int, 1):pass

iter 还有这种用法?这为啥是个死循环?

这真的是个冷知识,关于这个知识点,你如果看中文网站,可能找不到相关资料。

还好你可以通过 IDE 看py源码里的注释内容,介绍了很详细的使用方法。

原来iter有两种使用方法,通常我们的认知是第一种,将一个列表转化为一个迭代器。

而第二种方法,他接收一个 callable对象,和一个sentinel 参数。第一个对象会一直运行,直到它返回 sentinel 值才结束。

在这里插入图片描述
那int 呢,这又是一个知识点,int 是一个内建方法。通过看注释,可以看出它是有默认值0的。你可以在终端上输入 int() 看看是不是返回0。

>>> int()
0

在这里插入图片描述
由于int() 永远返回0,永远返回不了1,所以这个 for 循环会没有终点。一直运行下去。

两次 return

我们都知道,try…finally… 语句的用法,不管 try 里面是正常执行还是报异常,最终都能保证finally能够执行。

同时,我们又知道,一个函数里只要遇到 return 函数就会立马结束。

基于以上这两点,我们来看看这个例子,到底运行过程是怎么样的?

>>> def func():
...     try:
...             return 'try'
...     finally:
...             return 'finally'
...
>>> func()
'finally'

惊奇的发现,在try里的return居然不起作用。

原因是,在try…finally…语句中,try中的return会被直接忽视,因为要保证finally能够执行。

小整数池

>>> a = -6
>>> b = -6
>>> a is b
False

>>> a = -5
>>> b = -5
>>> a is b
True

>>> a = 256
>>> b = 256
>>> a is b
True

>>> a = 257
>>> b = 257
>>> a is b
False

>>> a = 257; b = 257   # 注意
>>> a is b
True

为避免整数频繁申请和销毁内存空间,Python 定义了一个小整数池[-5, 256]这些整数对象是提前建立好的,不会被垃圾回收。

以上代码请在 终端Python环境下测试,如果你是在IDE中测试,并不是这样的效果。

那最后一个示例,为啥又是True?

因为当你在同一行里,同时给两个变量赋同一值时,解释器知道这个对象已经生成,那么它就会引用到同一个对象。如果分成两成的话,解释器并不知道这个对象已经存在了,就会重新申请内存存放这个对象。

intern机制

字符串类型作为 Python 中最常用的数据类型之一,Python解释器为了提高字符串使用的效率和使用性能,做了很多优化。

例如:Python 解释器中使用了 intern(字符串驻留)的技术来提高字符串效率。

什么是 intern 机制?就是同样的字符串对象仅仅会保存一份,放在一个字符串储蓄池中,是共用的,当然,肯定不能改变,这也决定了字符串必须是不可变对象。

# python 3.7.1
>>> s1 = 'hello'
>>> s2 = 'hello'
>>> s1 is s2
True
# 如果有空格,默认不启用intern机制
>>> s1 = 'hell o'
>>> s2 = 'hell o'
>>> s1 is s2
False
# 如果一个字符串长度超过4096个字符,不启动intern机制
>>> s1 = 'a' * 4096
>>> s2 = 'a' * 4096
>>> s1 is s2
True

>>> s1 = 'a' * 4097
>>> s2 = 'a' * 4097
>>> s1 is s2
False

交互式“_”操作符

对于 _ ,我想很多人都非常熟悉。

给变量取名好艰难,用 _;
懒得长长的变量名,用 _;
无用的垃圾变量,用 _;

以上,我们都很熟悉了,今天要介绍的是他在交互式中使用。

>>> 3 + 4
7
>>> _
7
>>> name = 'zhe'
>>> name
'zhe'
>>> _
'zhe'

它可以返回上一次的运行结果。

但是,如果是print函数打印出来的就不行了。

>>> 3 + 4
7
>>> _
7
>>> print('zhe')
zhe
>>> _
7

我自己写了个例子,验证了下,用__repr__输出的内容可以被获取到的。
首先,在我们的目录下,写一个文件 ming.py。内容如下

# ming.py
class mytest():
    def __str__(self):
        return "hello"

    def __repr__(self):
        return "world"
>>> import ming
>>> mt=ming.mytest()
>>> mt
world
>>> print(mt)
hello
>>> _
world

知道这两个魔法方法的人,一看就明白了。

优雅翻转字符串与列表

反转序列并不难,但是如何做到最优雅呢?

先来看看,正常是如何反转的。

最简单的方法是使用列表自带的reverse()方法。

>>> L1 = [1, 2, 3]
>>> L1.reverse()
>>> L1
[3, 2, 1]

但如果你要处理的是字符串,reverse就无能为力了。你可以尝试将其转化成list,再reverse,然后再转化成str。转来转去,也太麻烦了吧?需要这么多行代码(后面三行是不能合并成一行的),一点都Pythonic。

>>> S1 = 'abc'
>>> S1 = list(S1)
>>> S1.reverse()
>>> ''.join(S1)
'cba'

对于字符串还有一种稍微复杂一点的,是自定义递归函数来实现。

>>> def str_reverse(str):
...     if not str:
...             return str
...     else:
...             return str_reverse(str[1:])+str[0]
...
>>> s1 = 'abcdefghijklmn'
>>> str_reverse(s1)
'nmlkjihgfedcba'

在这里,介绍一种最优雅的反转方式,使用切片,不管你是字符串,还是列表,简直通杀。

>>> s1 = 'abcdefghijklmn'
>>> s1[::-1]
'nmlkjihgfedcba'

改变递归次限制

上面才提到递归,大家都知道使用递归是有风险的,递归深度过深容易导致堆栈的溢出。如果你这字符串太长啦,使用递归方式反转,就会出现问题。

那到底,默认递归次数限制是多少呢?

可以使用sys这个库来查看

>>> import sys
>>> sys.getrecursionlimit()
1000

可以查,当然也可以自定义修改次数,退出即失效。不过友情提醒,这玩意还是不要轻易去碰,万一导致系统崩溃了小明可不背锅。

>>> sys.setrecursionlimit(2000)
>>> sys.getrecursionlimit()
2000

一行代码实现FTP服务器

搭建FTP,或者是搭建网络文件系统,这些方法都能够实现Linux的目录共享。但是FTP和网络文件系统的功能都过于强大,因此它们都有一些不够方便的地方。比如你想快速共享Linux系统的某个目录给整个项目团队,还想在一分钟内做到,怎么办?很简单,使用Python中的SimpleHTTPServer。

SimpleHTTPServer是Python 2自带的一个模块,是Python的Web服务器。它在Python 3已经合并到http.server模块中。具体例子如下,如不指定端口,则默认是8000端口。

python -m http.server 8888

访问:

localhost:8888

在这里插入图片描述
SimpleHTTPServer有一个特性,如果待共享的目录下有index.html,那么index.html文件会被视为默认主页;如果不存在index.html文件,那么就会显示整个目录列表。

让你晕头转向的 else 用法

if else 用法可以说最基础的语法表达式之一,但是今天不是讲这个的,一定要讲点不一样的。

if else 早已烂大街,但可能有很多人都不曾见过 for else 和 try else 的用法。为什么说它曾让我晕头转向,因为它不像 if else 那么直白,非黑即白,脑子经常要想一下才能才反应过来代码怎么走。反正我是这样的。

先来说说,for else

>>> def check_item(source_list, target):
...     for item in source_list:
...             if item == target:
...                     print('exists!')
...                     break
...     else:
...             print('Does not exist!')
...

在往下看之前,你可以思考一下,什么情况下才会走 else。是循环被 break,还是没有break?

给几个例子,你体会一下。

>>> check_item(['huawei', 'oppo', 'vivo'], 'oppo')
exists!
>>> check_item(['huawei', 'oppo', 'vivo'], 'apple')
Does not exist!

可以看出,没有被 break 的程序才会正常走else流程。

再来看看,try else 用法。

>>> def test_try_else(attr1=None):
... 	try:
...			if attr1:
...         	pass
...        	else:
...            	raise
...     except:
...      	print('Exception occurred...')
...     else:
...      	print('No Exception occurred...')
...

同样来几个例子。当不传参数时,就抛出异常。

>>> test_try_else()
Exception occurred...
>>> test_try_else('zhe')
No Exception occurred...

可以看出,没有 try 里面的代码块没有抛出异常的,会正常走else。

总结一下,for else 和 try else 相同,只要代码正常走下去,不被 break,不抛出异常,就可以走else。

空字符串计数

求一个字符串里,某子字符(串)出现的次数。在Python中使用 count() 函数,就可以轻松实现。

比如下面几个常规例子

>>> 'aabb'.count('a')
2
>>> 'aabb'.count('b')
2
>>> 'aabb'.count('c')
0
>>> 'aabb'.count('ab')
1

但是如果使用空字符串呢,你可能想不到会是这样的结果。

>>> 'aabb'.count('')
5

具体原因,我不敢妄下结论。

由此我还衍生出另一个想法,实验了下。不知道空字符串,是一种什么样的存在,难道字母与字母之间 “缝隙” 也算吗?

>>> '' in ''
True
>>> '' in 'aabb'
True

解释:字母与字母之间 “缝隙” 。

负负得正

从初中开始,我们就开始接触了负数 这个概念。知道了负负得正,这和武侠世界里的以毒功毒,有点神似。

Python 作为一门高级语言,它的编写符合人类的思维逻辑,这其中也包括负负得正这个思想。

>>> 3-5
-2
>>> 3--5
8
>>> 3+-5
-2
>>> 3++5
8
>>> 5---3
2

字符串的比较

在 Python2 中,数字可以与字符串直接比较。结果是数值永远比字符串小。

>>> 100000000 < ""
True
>>> 100000000 < "ming"
True

但在 Python3 中,却不行。

>>> 100000000 < "ming"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: '<' not supported between instances of 'int' and 'str'

循环中的局部变量泄露

在Python 2中x的值在一个循环执行之后被改变了。

# Python2
>>> x = 1
>>> [x for x in range(5)]
[0, 1, 2, 3, 4]
>>> x
4

不过在Python3 中这个问题已经得到解决了。

>>> x = 1
>>> [x for x in range(4)]
[0, 1, 2, 3]
>>> x
1

让字典可排序

字典不可排序的思想,似乎已经根深蒂固。

# Python2.7.10
>>> mydict = {str(i):i for i in range(5)}
>>> mydict
{'1': 1, '0': 0, '3': 3, '2': 2, '4': 4}

在 Python3 中字典已经是有序的。

# Python3.6.7
>>> mydict = {str(i):i for i in range(5)}
>>> mydict
{'0': 0, '1': 1, '2': 2, '3': 3, '4': 4}

猜你喜欢

转载自blog.csdn.net/z_feng12489/article/details/89470174
今日推荐