M

Mars6364

V1

2022/02/20阅读:64主题:默认主题

Python从入门到放弃-冷知识分享

魔法冷知识

默默无闻的省略号

  1. 默默无闻的省略号 在Python中,一切皆对象,省略号也不例外。在python中你开业直接写 "..." 来得到它

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

    注意:这个特性在Python2中是没有的

    它在转换为布尔值的时候为真

    >>> bool(...)
    True

    最后,这个省略号对象还是一个单例(单例的相关知识点也是很重要的,面试中也会经常问到,后续我也会整理分享一下)

    >>> id(...)
    94705372553616
    >>> id(...)
    94705372553616
    >>> 
    >>> 

    那么问题来了:这东西花里花哨的 有什么用呢?

  2. 它是numpy的一个语法糖

  3. 在Python3中可以使用... 代替 pass

使用end来结束代码块

有很多编程语言在循环、判断的时候需要用end表面结束,这样一定程度上能够使得代码逻辑更为清晰,但是在Python这样对缩进严格要求的语言中并没有必要这样做。

如果你真的想用,也不是没有办法,具体参考下面的实例。

__builtins__.end = None


def index(x):
    if x > 0:
        print("大于0")
    else:
        print("小于0")
    end
end

可以直接运行的zip包

我们可以经常看到python包以zip形式发布,并且可以不用解压就直接使用,我最第一次看到这种使用方式有点不理解,因为我认识中的python包要么就是egg格式,要么就是whel格式的。

那么这种zip包是如何制作的呢,请看下面的实例,

反斜杠的倔强:不写最后

\ 在python中主要用法两种

  1. 在行尾的时候,用作续行符号

    >>> print("hello "\
    ...       "world")
    hello world
    >>> 
    >>> 

  2. 在字符串中,用作转义字符,可以把普通字符转换为带有特殊含义的字符。

    >>> str1='\nhello'  # 换行
    >>> print(str1)

    hello
    >>> 
    >>> 
    >>> 
    >>> str2='\thello'  # 制表符
    >>> print(str2)
            hello
    >>> 
    >>> 

    但是如果使用 单 \ 结尾 是会报语法错误的

     >>> str3="\"
      File "
    <stdin>", line 1
    str3="
    \" ^
    SyntaxError: EOL while scanning string literal

    即使你指定它是一个raw字符串,依然会报错

     >>> str3=r"\"
      File "
    <stdin>", line 1
    str3=r"
    \" ^
    SyntaxError: EOL while scanning string literal

如何修改解释器提示符号

这个当做今天的一个小菜单吧。应该是比较冷门的。

正常情况下我们在终端下面执行python命令的时候是这样的

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

那么,里面的箭头和省略号是否可以被修改呢?

>>> import sys
>>> sys.ps1
'>>> '
>>> sys.ps2
'... '
>>>
>>> sys.ps2 = '---------------- ' 
>>> sys.ps1 = '程序员云梦>>>' 
程序员云梦>>>for i in range(2):
---------------- print (i)
----------------
0
1

简洁而优雅的链式比较

先看一个实例:

>>> False == False == False
True
>>> 

为什么这个表达式会返回False呢?它的运行原理和下面这个类似,是不是明白了:

if 80 < score < 90:
  print("成绩良好")

如果还是不明白,那你看下面下面一个例子的等价写法。

 >>> False == False and False == True
False

这个用法叫做链式比较。

and 和 or 的短路效应

and和or是我们经常用到的两个逻辑运算符,在Python中它也有妙用。

  • 当一个or表达式中所有值都为真的时候,Python会选择第一个值。
  • 当一个and表达式所有值都为真的时候,Python会选择最后一个值。

实例如下:

 >>>(2 or 3) * (5 and 6 and 7)
14  # 2*7

合并多个列表最极客的方式

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

Python中的字典是有序的

在Python3.6之前字典都是不可排序的,是无序的。

但是 在Python3.6+后面的版本中,字典都是有序的,并且效率相比较之前的还有所提示,具体的相关信息开业去查询相关资料。

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

用户无感知的小整数池

Python定义了一个小整数池[-5,256],这些整数对象是提前建立好的,不会被垃圾回收。

以下代码是在Python终端环境下测试实现,如果在IDE中测试,测试结果可能会有差距。

>>> a = -6
>>> b = -6
>>> is b
False
>>> a = 256
>>> b = 256
>>> is b
True
>>> a = 257
>>> b = 257
>>> is b
False
>>> a = 257; b = 257
>>> is b
True

那么问题来了:最后一个实例,为什么结果是True?

因为在最后一个实例赋值的时候,是同时给两个变量赋同一个值,解释器知道这个数据对象已经生成,那么他就会引用到同一个对象。如果分成两行的话,解释器并不知道这个对象已经存在,就会重新申请内存存放这个对象。

那么问题又来了:实例代码中可以验证到数据对象其实是存储是数据引用地址,那么Python为什么要这样设计呢?有什么优点呢?有什么缺点呢?

>>> a=8
>>> b=8
>>> id(a)
94786084233984
>>> id(b)
94786084233984
>>> 
>>> 
>>> 
>>> c=256
>>> d=256
>>> id(c)   
94786084241920
>>> id(d)
94786084241920
>>> 
>>> 
>>> 
>>> e=257
>>> f=257
>>> id(e)
140620066601872
>>> id(f)
140620066051312
>>> 

优点:小整数存在的覆盖了很多常用区间范围内的数字,此处可以联想到到Python的垃圾回收机制里面的引用计数原理,即使多个对象使用同一个数值,也只是改数据的引用计数+1,不会在使用的时候不需要去频繁的开辟新的内存,消耗内存空间。

缺点:如果使用过程中涉及到大量的计算,其实并不是对数据本身进行了计算,而且开辟了一块新的内存空间,创建了新的对象。

神奇的intern机制

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

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

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

>>> s1="hello"
>>> s2="hello"
>>> s1 is s2
True
#如果有空格,默认不启动intern机制 >>> s1="hell o"
>>> s2="hell o"
>>> s1 is s2
False
## 如果一个字符串长度超过20个字符串 也不启动intern机制 >>> s1 = "a" * 20
>>> s2 = "a" * 20
>>> s1 is s2
True
>>> s1 = "a" * 21
>>> s2 = "a" * 21
>>> s1 is s2
False
>> s1 = "ab" * 10
>>> s2 = "ab" * 10
>>> s1 is s2
True
>>> s1 = "ab" * 11
>>> s2 = "ab" * 11
>>> s1 is s2
False

site-packages和dist-packages

如果你足够细心,你会发现在你的机器上,有些包是安装在site-packages下,有些是在dist-pacjages下面。

他们有什么区别呢?

一般情况下,我们可能在IDE中会看到site-packages。因为我们pip安装的包是安装在这个目录下面。

而disr-packges其实是debian系统的Linux系统(比如Ubuntu)才有的目录,当你使用apt去安装的Python包会使用dist-packges,而你使用pip或者easy_install安装的包还是照常在site-packages下。

之所以这样设计,是为了减少不同来源的Python之间产生冲突。

>>> from distutils.sysconfig import get_python_lib
>>> print(get_python_lib())
/usr/local/python3/lib/python3.7/site-packages
>>> 

argument和parameter的区别

字面意思 两个单词的翻译都是参数,在中文使用场景目录下,二者混用基本没有问题,毕竟都是参数。

但是既然有两个相近的存在,那么就必然会有一点差距,类比MySQL的char和varchar数据类型。

那么这两个“参数”有什么区别呢?

  • parameter:形参(formal parameter),体现在函数内部,作用域是这个函数体。
  • argument:实参(actual parameter),调用函数实际传递的参数。 举个例子:
def output_msg(msg):
    print(msg)
output_msg("error")

这个例子中,“error”是argument,而“msg”是parameter

dict()和{}生成空字典有什么区别?

在初始化一个字典的时候,有的人会写dict,有的人会写{},

最开始我以为二者是相同的,但实际上有很大区别。

在运行效率上,后者会比前者快三倍左右。

具体看下面的实例:

➜  ~ python -m timeit -n 1000000 -r 5 -v "dict()"
raw times: 0.133 0.115 0.108 0.105 0.1
1000000 loops, best of 50.1 usec per loop
➜  ~ 
➜  ~ 
➜  ~ 
➜  ~ python -m timeit -n 1000000 -r 5 -v "{}"
raw times: 0.0374 0.03 0.0311 0.0299 0.0343
1000000 loops, best of 50.0299 usec per loop
➜  ~ 
➜  ~ 
➜  ~ 

那为什么会这样呢?

可以使用dis模块对比一下。

当使用{}的时候。

➜  ~ cat demo.py
{}
➜  ~ 
➜  ~ python -m dis demo.py
  1           0 BUILD_MAP         0
              2 POP_TOP
              4 LOAD_CONST        0(None)
              6 RETURN_VALUE


当使用dict()的时候:

➜  ~ cat demo.py
dict()
➜  ~ 
➜  ~ python -m dis demo.py
  1           0 LOAD_NAME          0(dict)
              2 CALL_FUNCTION      0
              4 POP_TOP
              6 LOAD_CONST         0(None)
              8 RETURN_VALUE

可以发现使用dict(),会多了一个调用函数的过程,而这个过程会有进出栈的操作,相对更耗时。

return不一定都是函数的终点

众所周知,try。。。finally。。。的用法:不管try的代码是正常执行还是报异常,finally里面的代码都会执行。

同时我们也知道,一个函数遇到return函数救护立马结束。

那么问题来了,如果这两种规则同时存在,Python解释器会如何选择呢?谁的优先级会更高呢?

看下面的实例:

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

从程序的输出结果可以看到,在上面的例子中,try中的return会被直接忽视(这里的return不是函数的终点),只是要保证finally能够执行。

那么 try中的return真的是被直接忽略吗?

我们都知道一个函数如果没有return,会隐式的返回None,假设try里面的return真的是被直接忽视,当finally没有显示的return会不会范湖None呢。

上代码!

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

从结果来看,当finally里面没有return的时候,其实try里面的return仍然还是有效的。

那么结论就有了,如果finally里面有显示的return,那么这个return会直接覆盖try里面的return;而如果finally里面没有显示的return,那么try里面的return还是有效的。

时有时无的切片异常

看下面的实例,一个列表只有五个元素,如果根据下标取第六个元素的时候,会抛出索引异常。这很正常,与我们预料的结果一致,

>>> alist = [01234]
>>> alist[5]
Traceback (most recent call last):
  File "<stdin>", line 1in <module>
IndexError: list index out of range

但是 当使用区间取值的时候,即使区间范围超出列表长度,也不会抛出异常,而是会返回一个新的列表。

>>> alist = [01234]
>>> alist[5:]
[]
>>> alist[100:]
[]

谜一样的字符串

  1. 实例1

    ## Python2.7
    >>> a = "Hello_Python"
    >>> id(a)
    32045616
    >>> id("Hello" + "_" + "Python")
    32045616
    ## Python3.7
    >>> a = "Hello_Python"
    >>> id(a)
    38764272
    >>> id("Hello" + "_" + "Python")
    32045616
  2. 实例2

    >>> a = "MING"
    >>> b = "MING"
    >>> is b
    True
    ## Python2.7
    >>> a, b = "MING!""MING!"
    >>> is b
    True
    ## Python3.7
    >>> a, b = "MING!""MING!"
    >>> is b
    False
  3. 实例3

    ## Python2.7
    >>> 'a' * 20 is 'aaaaaaaaaaaaaaaaaaaa'
    True
    >>> 'a' * 21 is 'aaaaaaaaaaaaaaaaaaaaa'
    False
    ## Python3.7
    >>> 'a' * 20 is 'aaaaaaaaaaaaaaaaaaaa'
    True
    >>> 'a' * 21 is 'aaaaaaaaaaaaaaaaaaaaa'
    True

X与+X等价吗?

在大多数情况下,这个等式是成立的。

>>> n1 = 10086
>>> n2 = +n1
>>>
>>> n1 == n2
True

但是 在某些情况下 这个等式会不成立。

由于Counter的机制,+用于两个counter的实例相加,而相加的结果 如果元素的格式≤0,就会被丢弃。

>>> from collections import Counter
>>> ct = Counter('abcdbcaa')
>>> ct
Counter({'a'3'b'2'c'2'd'1})
>>> ct['c'] = 0
>>> ct['d'] = -2
>>>
>>> ct
Counter({'a'3'b'2'c'0'd'-2})
>>>
>>> +ct
Counter({'a'3'b'2})

+= 不等同于 =+

对列表进行+=操作相当于执行extend,但是使用=+操作是新增了一个列表。

因此会有以下两者的差异:

## =+
>>> a = [1234]
>>> b = a
>>> a = a + [5678]
>>> a
[12345678]
>>> b
[1234]
## +=
>>> a = [1234]
>>> b = a
>>> a += [5678]
 >>> a
[12345678]
>>> b
[12345678]

循环中的局部变量泄露

在python2版本中 x的值在一个循环执行之后被改变了

## Python2
>>> x = 1
>>> [x for x in range(5)]
[01234]
>>> x
4

在python3中这问题已经得到解决了

## Python3
>>> x = 1
>>> [x for x in range(5)]
[01234]
>>> x
1

break和continue和上下文管理器那个优先级更高

众所周知,在循环体中(无论是for还是while),continue会用来跳入下一个循环,而break用来跳出某个循环体。

同时我们知道,在上下文管理器中,被包裹的程序主题代码结束会运行上下文管理器中的一段代码(通常是资源的释放)

但是如果把上下文管理器放在一个循环体中,然后在这个上下文管理器中执行了break,是否会直接跳出循环呢,

换句话说 break和continue和上下文管理器那个优先级更高一些?

实验代码如下:

import time
import contextlib
@contextlib.contextmanager
def runtime(value):
    time.sleep(1)
    print("start: a = " + str(value))
    yield
    print("end: a = " + str(value))
a=0
while True:
    a+=1
    with runtime(a):
        if a % 2 == 0:
            break

从输出的结果来看,当a=2的时候执行了break,此时并不会直接跳出循环,仍然要运行上下文管理器里面释放资源的代码,此处使用打印代替。

start: a = 1
end: a = 1
start: a = 2
end: a = 2

另外还有几个类似的问题,

  1. continue和break一样,如果先遇到上下文管理器会先进行资源的释放,
  2. 上面只距离了while的循环体,而for循环也是同样的。

如果想awk一样分割字符串?

如果使用过shell的awk工具,你会发现使用awk分割字符串是非常方便的。特别少多个连续的空格会被当做一个处理。

➜  ~ cat demo.txt
hello      world
➜  ~
➜  ~ awk '{print$1,$2}' demo.txt
hello world

但是 转换到python上面来,结果是这样的:

>>> msg='hello    world'
>>> msg.split(' ')
['hello''''''''world']

如上所示,多个空格会被分割多次。

那么如何实现awk一样的效果呢。

两种方法

  • 第一种方法: 不加参数,这种只适用于将多个空格当初一个空格处理的场景,如果不是一空格为分隔符的应用场景,这种就不适合了。

    >>> msg='hello    world'
    >>> msg.split()
    ['hello''world']
  • 第二种方法 使用filter来辅助,这种使用与所有的分隔符,下面一“-”为分隔符来举例:

    >>> msg='hello----world'
    >>> msg.split('-')
    ['hello''''''''world']
    >>>
    >>> filter(None, msg.split('-'))
    ['hello''world']

    是不是很神奇,filter印象中第一个参数接受的是函数,这里传None居然有奇效。 查看了源代码以后,发现原来这个函数会适配None的情况,当第一个参数是空的时候,返回第二个参数(可迭代对象)中非空的值,非常方便。

这里顺便回顾一下filter()函数的用法:

用法:filter函数用于过滤序列,过滤掉不符合条件的元素,返回复合条件的元素构成新列表。

filter语法如下:

filter(function,iterable)
 
# 其中function为函数,iterable为序列 或者说是可迭代对象


序列中的每个元素都会作为参数传递给函数进行判断,返回True或者False,最后想返回True的元素放到新列表中。

filter用法实例:

筛选出序列中为奇数的元素:

def is_odd(n):
    return n%2 == 1
lst1 = filter(is_odd,[1,2,3,4,5,6,7,8,9,10])
 
# lst = [1,3,5,7,9]

如何让大数据变的更易于阅读?

当一个数据非常大的时候,可能过百万,也可能上亿,太多位的数字,可读性就会变得很差,给我们的阅读提高了障碍,比如 889323737 你能一下说出他是多少万呢 还是多少亿呢

是不是不能很快的辨识出来

这个时候,就可以使用_来辅助便是,如下写法就会清晰很多了

8233_9499_0384_3

而且 这种写法在代码中并不会报错。(Python2不支持)

>>> number=281_028_344
>>> number
281028344

分类:

后端

标签:

Python

作者介绍

M
Mars6364
V1