Jupyter 乐趣

这是真正的学习时代, 云端大脑装着任何你需要的知识, 而你要做的, 只是简单的理解基本概念, 用自然语言组织你的问题…

安装 Anaconda,将正文末尾的 jupyter附件 下载到本地(或到 jupyterfun 下载源码,所有 .ipynb 文件及附件都有),启动 jupyter,直接运行更棒。

Python

本站的知识已经价值不大, 这是早期的知识整理, AI 的诞生, 只需要极简的知识…… 2024, 11, 22

Python 语法基础知识详解和查询手册

python 很重要, 但不需要这么多知识…… 2024. 11. 22

数字类型概述

数字类型属于内置类型。有三种不同的数字类型:整数,浮点数和复数。

整数通常只有数字,浮点数有小数点,而复数有实部(省略则为 0)和虚部,虚部必须有 J 或 j。

它们的类型分别是 int,float 和 complex。

type(1), type(1.0), type(1j)
(int, float, complex)

它们虽然属于不同的类型,但 Python 完全支持其混合运算。

而且布尔类型属于整数类型的子类型,布尔类型也可参与混合运算。

True/3 + 1.5 - 1J
(1.8333333333333333-1j)

数字之间比较时,比较的是它们的精度决定的精确值:

True == 1 == 1.0
True

对于不同类型的数字,只要精确值相等,哈希值必定相等:

hash(True) == hash(1) == hash(1.0)
True

因此作为集合的元素时,它们是一个元素;作为字典的键时,是同一个键。但这不是明智之举,因为浮点数存在精度问题:

{True, 1, 1.0}
{True}
{True:1, 1:2, 1.0:3}
{True: 3}
1.00000000000000009 == 1
True

整数,浮点数和复数的构造函数分别是:int()float()complex()。构造细节详见相应内置函数知识点。

int('1'), float('1.0'), complex('1')
(1, 1.0, (1+0j))

关于各数字类型的特性和详细介绍见后面章节。

整数及其位运算

整数具有无限精度。有四种整数表示法:十六进制整数(0x 或 0X 开头),十进制整数,八进制整数(0o 或 0O 开头)和二进制整数(0b 或 0B 开头)。

# 十六进制
0x10
16
# 十进制
10
10
# 八进制
0O10
8
# 二进制
0b10
2

数字之间或基数说明符(例如 0b)后,可以放一个下划线:

123_456_789
123456789
0b_1010
10

十进制整数不可以 0 开头:

012
  File "<ipython-input-44-95f378563ada>", line 1
    012
      ^
SyntaxError: leading zeros in decimal integer literals are not permitted; use an 0o prefix for octal integers

整数类型按位运算

按位运算只对整数有意义。以优先级升序排序的按位运算:

  • x | y x 和 y 按位 或 运算
  • x ^ y x 和 y 按位 异或 运算
  • x & y x 和 y 按位 与 运算
  • x << n x 左移 n 位,相当于 x*2**n
  • x >> n x 右移 n 位,相当于 x//(2**n)
  • ~x x 逐位取反

下面以八位机为例,x 取 6,y 取 8,n 取 2:

6 和 8 在内存中的储存分别为 00000110,00001000。

按位 或 运算,规则如下:

0|0, 1|0, 1|1
(0, 1, 1)

6|8 逐位运算后结果为 00001110,十进制就是 14:

6|8, 0b00001110
(14, 14)

同理可按下列计算规则,计算出 6^8, 6&8:

0^0, 0^1, 1^1
(0, 1, 0)
0&0, 0&1, 1&1
(0, 0, 1)
6^8, 6&8
(14, 0)

6 左移 2 位结果为 00011000,十进制就是 24,相当于 6*2**2

6<<2, 0b00011000, 6*2**2
(24, 24, 24)

同理可计算出 6»2,结果为 1:

6>>2, 6//(2**2)
(1, 1)

6 逐位取反为 11111001,因为我们是以八位机来举例的,八位机中 11111001 表示 -7,这是个天才设计,正负整数计算可以利用一种电路即可完成,有兴趣的可以查资料了解详情。

~6
-7

例如 8 + (-7) 如此计算:

  00001000 
+ 11111001 
=100000001

因为是八位机,结果有 9 位,所以第一个 1 溢出,结果就是 1。

布尔值及布尔运算

布尔值有 True 和 False,布尔类型是整数类型的子类型,所以整数的运算都适用布尔值运算。

issubclass(bool,int)
True
True + 1
2
~True
-2

任何对象都可以进行布尔值的检测,以便在 if 或 while 中作为条件或是作为下文所述布尔运算的操作数来使用。

一个对象在默认情况下均被视为真值,除非当该对象被调用时其所属类定义了 __bool__() 方法且返回 False 或是定义了 __len__() 方法且返回零。

下面基本完整地列出了会被视为假值的内置对象:

  • 被定义为假值的常量: None 和 False。
  • 任何数值类型的零: 0, 0.0, 0j, Decimal(0), Fraction(0, 1)
  • 空的序列和多项集: ‘’, (), [], {}, set(), range(0)

可以使用内置函数 bool() 来查看任意对象的布尔值:

bool(None), bool(int), bool(0)
(False, True, False)

布尔运算

布尔运算按优先级升序排列:

  • x or y, if x is false, then y, else x
  • x and y, if x is false, then x, else y
  • not x, if x is false, then True, else False

任何值(包括表达式求值结果),除了自身之外,还有相应的逻辑值(布尔值),所以布尔运算符 or,and,not 可对任何值进行运算。

举例如下:

1>2 or 'python'
'python'

1>2 表达式结果为 False,所以布尔运算结果为 ‘python’。

1+1 or 'python'
2

1+1 表达式结果为 2,布尔值为 True,所以布尔运算结果为 2。

1>2 and 'python'
False

1>2 表达式的结果为 False,所以布尔运算结果为 False。

1<2 and 'python'
'python'

1<2 表达式的结果为 True,所以布尔运算结果为 ‘python’。

not 'python'
False

‘python’ 的布尔值为 True,所以布尔运算结果为 False。

not 0
True

0 的布尔值为 False,所以布尔运算结果为 True。

浮点数

浮点数简单理解就是带小数点的数,通常使用 C 语音中的 double 来实现。有关你的程序运行所在计算机上浮点数的精度和内部表示法可在 sys.float_info 中查看。

import sys
sys.float_info
sys.float_info(max=1.7976931348623157e+308, max_exp=1024, max_10_exp=308, min=2.2250738585072014e-308, min_exp=-1021, min_10_exp=-307, dig=15, mant_dig=53, epsilon=2.220446049250313e-16, radix=2, rounds=1)

浮点数可以省略 . 前后的数;可以使用科学计数法;可以在数字之间插入下划线;可以以 0 开头。

举例如下:

# e 大小写均可 e-2 表示 10 的 -2 次方
0., .5, 0.1e-2, 3_1.1_4, 01.2e02
(0.0, 0.5, 0.001, 31.14, 120.0)

整数除法除一定得到浮点数:

True/3, 3/3
(0.3333333333333333, 1.0)

整数和浮点数可以相互转换,浮点数转为整数,直接去掉小数部分:

int(3.56), float(0)
(3, 0.0)

复数

复数包含实部和虚部(带 j 或 J 的部分),分别以一个浮点数表示。在一个数字后面加上 j 或 J,则得到一个实部为零的复数。

-1j
(-0-1j)
1 + 0j
(1+0j)

看起来实部和虚部并不是以浮点数表示,可以使用 .real 和 .imag,从一个复数中提取这两个部分查看。

(1 + 0j).real, (1 + 0j).imag
(1.0, 0.0)
-1j.real, -1j.imag
(-0.0, -1.0)

实部和虚部都是以浮点数表示,所以复数可以使用浮点数的所有表示法:

0.J, .5J, 0.1e-2J, 3_1.1_4J, 01.2e02J
(0j, 0.5j, 0.001j, 31.14j, 120j)

对复数取绝对值,就是计算它的模——实部和虚部平方的和开根号:

abs(3+4j), (3**2 + 4**2)**0.5
(5.0, 5.0)

数字运算

所有数字类型都支持下列运算(复数不支持 // 和 %):

  • x + y, x 和 y 的和
  • x - y, x 和 y 的差
  • x * y, x 和 y 的乘积
  • x / y, x 和 y 的商
  • x // y, x 和 y 的商数
  • x % y, x / y 的余数
  • -x, x 取反
  • +x, x 不变
  • x ** y, x 的 y 次幂

Python 完全支持数字的混合运算:当一个二元算术运算符的操作数有不同数值类型时,“较窄” 类型的操作数会拓宽到另一个操作数的类型。其中整数比浮点数窄,浮点数比复数窄。但整数除以整数得到浮点数。

运算示例如下:

True + 1 + 3.14
5.140000000000001
True + 1 + 3.14 + 0j
(5.140000000000001+0j)
3/3, 3/1j
(1.0, -3j)
6//2, 6//2.0
(3, 3.0)
6%4, 6%3.0
(2, 0.0)
3**2, 3**2.0, 3**0j
(9, 9.0, (1+0j))

优先级升序排列如下:

  • +, - 加和减
  • *, /, //, % 乘,除,整除,取余
  • +x, -x 正,负
  • ** 乘方

幂运算符 ** 绑定的紧密程度低于在其右侧的算术:

1 + -20 * 4**-1
-4.0

为了易读,应该多使用小括号:

1 + (-20) * 4**(-1)
-4.0

字符串概述

字符串概述

Python 中处理文本数据使用 str 类型对象,也称为字符串。

type('abc')
str

字符串是由 Unicode 码位构成的不可变序列。每个字符的 Unicode 码位可由内建函数 ord() 查看:

ord('a'), ord('b'), ord('c')
(97, 98, 99)

内建函数 str() 可将其他类型对象转换为字符串:

str(3.14)
'3.14'

字符串有多种不同写法:

  • 单引号标示

单引号标示法,如果字符串中有单引号,需要用 \' 表示:

'it\'s a book'
"it's a book"
  • 双引号标示

双引号标示法,如果字符串中有双引号,需要用 \" 表示:

"it's a \"book\""
'it\'s a "book"'
  • 三重引号标示

三重引号标示法,可以是三重单引号 ''',也可以是三重双引号 """,字符串中的单引号或双引号不受影响,但不能紧挨着:

# 注意最后四个双引号中有一个空格
'''it's a book''', \
"""it's a "book" """ 
("it's a book", 'it\'s a "book" ')

三重引号标示的字符串可以换行,自动以 \n 表示:

'''
it's a 
book
'''
"\nit's a \nbook\n"

若不想引入 \n,可以使用续航符 \

'''\
it's a \
book\
'''
"it's a book"

字符串可带前缀 u(或 U),r(或 R),f(或 F)。

u 表示 Unicode 编码字符串(默认,可省略);r 表示原始字符串,其中的反斜杠会被当作其本身来处理;f 表示格式化字符串字面值。r 和 f 可连用。

'a\nb{1+1}'
'a\nb{1+1}'
r'a\nb{1+1}'
'a\\nb{1+1}'
f'a\nb{1+1}'
'a\nb2'
rf'a\nb{1+1}'
'a\\nb2'

注意带前缀 b(或 B),表示字节串对象:

type(b'abc')
bytes

字符串是不可变序列,下列切片操作得到的是原来的字符串:

a = 'abc'
b = a[:]
a is b
True

字符串拼接

空格拼接

相邻的两个字符串,无论中间隔了多少个空格(包括 0 个),都会自动连接到一起:

'Py''thon', 'Py'    'thon'
('Python', 'Python')

甚至可以用 () 包围实现换行拼接,这在字符串(或正则表达式)很长时非常有用:

('Py'
f'thon{3}'
r'\Go')
'Python3\\Go'

运算符 + 拼接

运算符 + 拼接字符串,和空格类似,但 + 拼接字符串可以是变量的形式:

a = 'Py'
b = 'thon'
'Py' + b, a + b
('Python', 'Python')

运算符 + 还可以和赋值运算符 = 连用,拼接字符串的同时进行赋值:

# 将 a 和 b 拼接,赋值给 a
a += b
print(a)
# 将 b 和 a 拼接,赋值给 b
b += a
b
Python


'thonPython'

转义字符

转义字符 \ 本身不被当作字符,如果要表示一个字符 \,需要自己将自己转义:

'\\'
'\\'
'\'
  File "<ipython-input-2-d44a383620ab>", line 1
    '\'
       ^
SyntaxError: EOL while scanning string literal

上面这一行报错信息是 SyntaxError: EOL while scanning string literal。这是因为 \' 表示的是单引号字符 '(Literal)—— 是可被输出到屏幕的 ',而不是用来标示字符串的那个 '—— 别急,无论哪个初学者第一次读到前面的句子都觉得有点莫名其妙…… —— 于是,Python 编译器扫描这个 “字符串” 的时候,还没找到标示字符串末尾的另外一个 ' 的时候就读到了 EOL(End Of Line)。

如果你想输出这么个字符串,He said, it’s fine.,如果用双引号扩起来 " 倒没啥问题,但是如果用单引号扩起来就麻烦了,因为编译器会把 it 后面的那个单引号 ' 当作字符串结尾。

'He said, it's fine.'
  File "<ipython-input-3-2bcf2ca6dd95>", line 1
    'He said, it's fine.'
                 ^
SyntaxError: invalid syntax

于是你就得用转义符 \

# 要么你这么写:
print('He said, it\'s fine.')
# 要么你这么写:
print("He said, it's fine.")
# 要么,不管用单引号还是双引号标示字符串,
# 都习惯于用 \' 和 \" 书写属于字符串内部的引号……
"He said, it\'s fine."
He said, it's fine.
He said, it's fine.


"He said, it's fine."

转义字符 \ 可与其他字符组合成有特殊含义的字符:

转义字符 说明
\(在行尾时) 续行符
\\ 反斜杠符号
\' 单引号
\" 双引号
\a 响铃
\b 退格
\n 换行
\v 纵向制表符
\t 横向制表符
\r 回车
\f 换页
\yy 八进制数 yy 码位的字符
\xyy 十六进制数 yy 码位的字符

续航符,可以将两行代码(或字符串)连接起来,表示一行:

for i in \
range(3): # 两行相当于 for i in range(10):
    print(i)
0
1
2
'hello \
world'
'hello world'

八进制和十六进制字符举例:

# 八进制字符
'\101', '\102'
('A', 'B')
# 十六进制字符
'\x41', '\x42'
('A', 'B')
# 十进制
chr(65),chr(66)
('A', 'B')

在正则表达式中,转义字符 \ 的应用更加普遍。详情请看《正则指引》

str.count 统计

字符串方法 str.count(),Python 官方文档描述如下:

help(str.count)
Help on method_descriptor:

count(...)
    S.count(sub[, start[, end]]) -> int
    
    Return the number of non-overlapping occurrences of substring sub in
    string S[start:end].  Optional arguments start and end are
    interpreted as in slice notation.

返回回子字符串 sub 在 [start, end] 范围内非重叠出现的次数。可选参数 start 与 end 会被解读为切片表示法。

只给定 sub 一个参数的话,于是从第一个字符开始搜索到字符串结束;如果,随后给定了一个可选参数的话,那么它是 start,于是从 start 开始,搜索到字符串结束;如果 start 之后还有参数的话,那么它是 end;于是从 start 开始,搜索到 end - 1 结束(即不包含索引值为 end 的那个字符)。

'python'.count('0')
0
'pyyython'.count('yy')
1
'pythonpythonn'.count('n',5)
3
'pythonpythonn'.count('n',5,7)
1

str.replace 替换

字符串方法 str.replace(),Python 官方文档描述如下:

help(str.replace)
Help on method_descriptor:

replace(self, old, new, count=-1, /)
    Return a copy with all occurrences of substring old replaced by new.
    
      count
        Maximum number of occurrences to replace.
        -1 (the default value) means replace all occurrences.
    
    If the optional argument count is given, only the first count occurrences are
    replaced.

返回字符串的副本,其中出现的所有子字符串 old 都将被替换为 new。如果给出了可选参数 count,则只替换前 count 次出现。

'python python'.replace('p','C')
'Cython Cython'
'python python'.replace('py','Cpy',1)
'Cpython python'

如果 old 为空字符串,则在每个字符之间(包括前后)插入 new:

'python python'.replace('','C')
'CpCyCtChCoCnC CpCyCtChCoCnC'

如果 new 为空字符串,则相当于去除了 old:

'python python'.replace('p','')
'ython ython'

str.expandtabs 替换制表符

字符串方法 str.expandtabs(),Python 官方文档描述如下:

help(str.expandtabs)
Help on method_descriptor:

expandtabs(self, /, tabsize=8)
    Return a copy where all tab characters are expanded using spaces.
    
    If tabsize is not given, a tab size of 8 characters is assumed.

返回字符串的副本,其中所有的制表符会由一个或多个空格替换,具体取决于当前列位置和给定的制表符宽度。每 tabsize 个字符设为一个制表位(默认值 8 时设定的制表位在列 0, 8, 16 依次类推)。

要展开字符串,当前列将被设为零并逐一检查字符串中的每个字符。如果字符为制表符 (\t),则会在结果中插入一个或多个空格符,直到当前列等于下一个制表位。(制表符本身不会被复制。)

如果字符为换行符 (\n) 或回车符 (\r),它会被复制并将当前列重设为零。任何其他字符会被不加修改地复制并将当前列加一,不论该字符在被打印时会如何显示。

'01\t012\t0123\t01234'.expandtabs()
'01      012     0123    01234'
'01\t012\t0123\t01234'.expandtabs(4)
'01  012 0123    01234'
'\n\t01\r2\t0123\t01234'.expandtabs(4)
'\n    01\r2   0123    01234'

str.split 拆分

字符串方法 str.split(),Python 官方文档描述如下:

help(str.split)
Help on method_descriptor:

split(self, /, sep=None, maxsplit=-1)
    Return a list of the words in the string, using sep as the delimiter string.
    
    sep
      The delimiter according which to split the string.
      None (the default value) means split according to any whitespace,
      and discard empty strings from the result.
    maxsplit
      Maximum number of splits to do.
      -1 (the default value) means no limit.

返回一个由字符串内单词组成的列表,使用 sep 作为分隔字符串。如果给出了 sep,则连续的分隔符不会被组合在一起而是被视为分隔空字符串;如果给出了 maxsplit,则最多进行 maxsplit 次拆分(因此,列表最多会有 maxsplit+1 个元素)。如果 maxsplit 未指定或为 -1,则不限制拆分次数(进行所有可能的拆分)。

'a.bc'.split('.')
['a', 'bc']
'a.b.c'.split('.',maxsplit=1)
['a', 'b.c']
'a.b..c'.split('.')
['a', 'b', '', 'c']
'a.b..c'.split('..')
['a.b', 'c']
''.split('.')
['']

如果 sep 未指定或为 None,则会应用另一种拆分算法:连续的空格会被视为单个分隔符,如果字符串包含前缀或后缀空格,其结果将不包含开头或末尾的空字符串。因此,使用 None 拆分空字符串或仅包含空格的字符串将返回 []。

'a b  c '.split()
['a', 'b', 'c']
'  \n '.split()
[]
''.split()
[]

str.rsplit 拆分

字符串方法 str.rsplit(),Python 官方文档描述如下:

help(str.rsplit)
Help on method_descriptor:

rsplit(self, /, sep=None, maxsplit=-1)
    Return a list of the words in the string, using sep as the delimiter string.
    
      sep
        The delimiter according which to split the string.
        None (the default value) means split according to any whitespace,
        and discard empty strings from the result.
      maxsplit
        Maximum number of splits to do.
        -1 (the default value) means no limit.
    
    Splits are done starting at the end of the string and working to the front.

返回一个由字符串内单词组成的列表,使用 sep 作为分隔字符串。如果给出了 maxsplit,则最多进行 maxsplit 次拆分,从 最右边开始。如果 sep 未指定或为 None,任何空白字符串都会被作为分隔符。除了从右边开始拆分,rsplit() 的其他行为都类似于 split()。

'p\nyth   on '.rsplit()
['p', 'yth', 'on']
'p\nyth on'.rsplit('y')
['p\n', 'th on']
'p\nyth on'.rsplit(maxsplit=1)
['p\nyth', 'on']
'p\nyth   on '.rsplit(maxsplit=2)
['p', 'yth', 'on']

多个分隔符在一起,则会解读为拆分空字符串:

'\n\np\nyth on'.rsplit('\n')
['', '', 'p', 'yth on']

str.partition 拆分

字符串方法 str.partition(),Python 官方文档描述如下:

help(str.partition)
Help on method_descriptor:

partition(self, sep, /)
    Partition the string into three parts using the given separator.
    
    This will search for the separator in the string.  If the separator is found,
    returns a 3-tuple containing the part before the separator, the separator
    itself, and the part after it.
    
    If the separator is not found, returns a 3-tuple containing the original string
    and two empty strings.

在 sep 首次出现的位置拆分字符串,返回一个 3 元组,其中包含分隔符之前的部分、分隔符本身,以及分隔符之后的部分。如果分隔符未找到,则返回的 3 元组中包含字符本身以及两个空字符串。

'python'.partition('m')
('python', '', '')
'python'.partition('th')
('py', 'th', 'on')
'python'.partition('ht')
('python', '', '')

str.rpartition 拆分

字符串方法 str.rpartition(),Python 官方文档描述如下:

help(str.rpartition)
Help on method_descriptor:

rpartition(self, sep, /)
    Partition the string into three parts using the given separator.
    
    This will search for the separator in the string, starting at the end. If
    the separator is found, returns a 3-tuple containing the part before the
    separator, the separator itself, and the part after it.
    
    If the separator is not found, returns a 3-tuple containing two empty strings
    and the original string.

在 sep 最后一次出现的位置拆分字符串,返回一个 3 元组,其中包含分隔符之前的部分、分隔符本身,以及分隔符之后的部分。如果分隔符未找到,则返回的 3 元组中包含两个空字符串以及字符串本身。

'python'.rpartition('n')
('pytho', 'n', '')
'pythhhon'.rpartition('hh')
('pyth', 'hh', 'on')

分隔符未找到,则返回的 3 元组中 字符串本身 排在最后:

'python'.rpartition('ht') 
('', '', 'python')

str.splitlines 按行拆分

字符串方法 str.splitlines(),Python 官方文档描述如下:

help(str.splitlines)
Help on method_descriptor:

splitlines(self, /, keepends=False)
    Return a list of the lines in the string, breaking at line boundaries.
    
    Line breaks are not included in the resulting list unless keepends is given and
    true.

返回由原字符串中各行组成的列表,在行边界的位置拆分。结果列表中不包含行边界,除非给出了 keepends 且为真值。

此方法会以下列行边界进行拆分。特别地,行边界是 universal newlines 的一个超集。

  • \n 换行
  • \r 回车
  • \r\n 回车 + 换行
  • \v\x0b 行制表符
  • \f\x0c 换表单
  • \x1c 文件分隔符
  • \x1d 组分隔符
  • \x1e 记录分隔符
  • \x85 下一行
  • \u2028 行分隔符
  • \u2029 段分隔符
'ab c\n\nde fg\rkl\r\n'.splitlines()
['ab c', '', 'de fg', 'kl']
'ab c\n\nde fg\rkl\r\n'.splitlines(keepends=True)
['ab c\n', '\n', 'de fg\r', 'kl\r\n']

分隔空字符串此方法将返回一个空列表;末尾的换行不会令结果中增加额外的空字符串:

''.splitlines()
[]
"One line\nTwo lines\n".splitlines()
['One line', 'Two lines']
'One line\nTwo lines\n'.split('\n')
['One line', 'Two lines', '']

str.strip 移除两边字符

字符串方法 str.strip(),Python 官方文档描述如下:

help(str.strip)
Help on method_descriptor:

strip(self, chars=None, /)
    Return a copy of the string with leading and trailing whitespace remove.
    
    If chars is given and not None, remove characters in chars instead.

返回原字符串的副本,移除其中的前导和末尾字符。chars 参数为指定要移除字符的字符串。如果省略或为 None,则 chars 参数默认移除空格符。实际上 chars 参数并非指定单个前缀或后缀;而是会移除参数值中的所有字符:

' python '.strip()
'python'
' python '.strip('p')
' python '
' python '.strip('p n')
'ytho'
' pythonnnn '.strip('p n')
'ytho'

str.lstrip 移除左边字符

字符串方法 str.lstrip(),Python 官方文档描述如下:

help(str.lstrip)
Help on method_descriptor:

lstrip(self, chars=None, /)
    Return a copy of the string with leading whitespace removed.
    
    If chars is given and not None, remove characters in chars instead.

返回原字符串的副本,移除其中的前导字符。chars 参数为指定要移除字符的字符串。如果省略或为 None,则 chars 参数默认移除空格符。实际上 chars 参数并非指定单个前缀;而是会移除参数值中出现的所有字符:

'  python  '.lstrip()
'python  '
'  python  '.lstrip('y p')
'thon  '
'  python  '.lstrip('py')
'  python  '
'ppppython  '.lstrip('y p')
'thon  '

str.rstrip 移除右边字符

字符串方法 str.rstrip(),Python 官方文档描述如下:

help(str.rstrip)
Help on method_descriptor:

rstrip(self, chars=None, /)
    Return a copy of the string with trailing whitespace removed.
    
    If chars is given and not None, remove characters in chars instead.

返回原字符串的副本,移除其中的末尾字符。chars 参数为指定要移除字符的字符串。如果省略或为 None,则 chars 参数默认移除空格符。实际上 chars 参数并非指定单个后缀;而是会移除参数值中的所有字符串:

' python '.rstrip()
' python'
' python '.rstrip('n o')
' pyth'
' python '.rstrip('n')
' python '
' pythonnnnn'.rstrip('no')
' pyth'

str.find 查找最小索引

字符串方法 str.find(),Python 官方文档描述如下:

help(str.find)
Help on method_descriptor:

find(...)
    S.find(sub[, start[, end]]) -> int
    
    Return the lowest index in S where substring sub is found,
    such that sub is contained within S[start:end].  Optional
    arguments start and end are interpreted as in slice notation.
    
    Return -1 on failure.

返回子字符串 sub 在 s[start:end] 切片内被找到的最小索引。可选参数 start 与 end 会被解读为切片表示法。如果 sub 未被找到则返回 -1。

只给定 sub 一个参数的话,于是从第一个字符开始搜索到字符串结束;如果,随后给定了一个可选参数的话,那么它是 start,于是从 start 开始,搜索到字符串结束;如果 start 之后还有参数的话,那么它是 end;于是从 start 开始,搜索到 end - 1 结束(即不包含索引值为 end 的那个字符)。

'pythonpython'.find('y')
1
'pythonpython'.find('pt')
-1
'pythonpython'.find('y',5)
7
'pythonpython'.find('y',5,7)
-1

str.rfind 查找最大索引

字符串方法 str.rfind(),Python 官方文档描述如下:

help(str.rfind)
Help on method_descriptor:

rfind(...)
    S.rfind(sub[, start[, end]]) -> int
    
    Return the highest index in S where substring sub is found,
    such that sub is contained within S[start:end].  Optional
    arguments start and end are interpreted as in slice notation.
    
    Return -1 on failure.

返回子字符串 sub 在字符串内被找到的最大(最右)索引,这样 sub 将包含在 s[start:end] 当中。可选参数 start 与 end 会被解读为切片表示法。如果未找到则返回 -1。

只给定 sub 一个参数的话,于是从第一个字符开始搜索到字符串结束;如果,随后给定了一个可选参数的话,那么它是 start,于是从 start 开始,搜索到字符串结束;如果 start 之后还有参数的话,那么它是 end;于是从 start 开始,搜索到 end - 1 结束(即不包含索引值为 end 的那个字符)。

'python python'.rfind('on')
11
'python python'.rfind('on',1,7)
4
'python python'.rfind('on',7)
11
'python python'.rfind('no')
-1

str.index 查找最小索引

字符串方法 str.index(),Python 官方文档描述如下:

help(str.index)
Help on method_descriptor:

index(...)
    S.index(sub[, start[, end]]) -> int
    
    Return the lowest index in S where substring sub is found,
    such that sub is contained within S[start:end].  Optional
    arguments start and end are interpreted as in slice notation.
    
    Raises ValueError when the substring is not found.

返回子字符串 sub 在 s[start:end] 切片内被找到的最小索引。可选参数 start 与 end 会被解读为切片表示法。类似于 find(),但在找不到 sub 时会引发 ValueError。

只给定 sub 一个参数的话,于是从第一个字符开始搜索到字符串结束;如果,随后给定了一个可选参数的话,那么它是 start,于是从 start 开始,搜索到字符串结束;如果 start 之后还有参数的话,那么它是 end;于是从 start 开始,搜索到 end - 1 结束(即不包含索引值为 end 的那个字符)。

'pythonpython'.index('y')
1
'pythonpython'.index('pt')
---------------------------------------------------------------------------

ValueError                                Traceback (most recent call last)

<ipython-input-3-4252de1acf66> in <module>
----> 1 'pythonpython'.index('pt')


ValueError: substring not found
'pythonpython'.index('y',5)
7
'pythonpython'.index('y',5,7)
---------------------------------------------------------------------------

ValueError                                Traceback (most recent call last)

<ipython-input-5-35705122f03a> in <module>
----> 1 'pythonpython'.index('y',5,7)


ValueError: substring not found

str.rindex 查找最大索引

字符串方法 str.rindex(),Python 官方文档描述如下:

help(str.rindex)
Help on method_descriptor:

rindex(...)
    S.rindex(sub[, start[, end]]) -> int
    
    Return the highest index in S where substring sub is found,
    such that sub is contained within S[start:end].  Optional
    arguments start and end are interpreted as in slice notation.
    
    Raises ValueError when the substring is not found.

返回子字符串 sub 在字符串内被找到的最大(最右)索引,这样 sub 将包含在 s[start:end] 当中。可选参数 start 与 end 会被解读为切片表示法。如果未找到则返回 -1。类似于 rfind(),但在子字符串 sub 未找到时会引发 ValueError。

只给定 sub 一个参数的话,于是从第一个字符开始搜索到字符串结束;如果,随后给定了一个可选参数的话,那么它是 start,于是从 start 开始,搜索到字符串结束;如果 start 之后还有参数的话,那么它是 end;于是从 start 开始,搜索到 end - 1 结束(即不包含索引值为 end 的那个字符)。

'python python'.rindex('on')
11
'python python'.rindex('on',1,7)
4
'python python'.rindex('on',7)
11
'python python'.rindex('no')
---------------------------------------------------------------------------

ValueError                                Traceback (most recent call last)

<ipython-input-5-92aeb174dba9> in <module>
----> 1 'python python'.rindex('no')


ValueError: substring not found

str.join 拼接字符串

字符串方法 str.join(),Python 官方文档描述如下:

help(str.join)
Help on method_descriptor:

join(self, iterable, /)
    Concatenate any number of strings.
    
    The string whose method is called is inserted in between each given string.
    The result is returned as a new string.
    
    Example: '.'.join(['ab', 'pq', 'rs']) -> 'ab.pq.rs'

返回一个由 iterable 中的字符串拼接而成的字符串。如果 iterable 中存在任何非字符串值则会引发 TypeError。调用该方法的字符串将作为元素之间的分隔。

'~'.join('abc')
'a~b~c'
'acb'.join(['1','2'])
'1acb2'
''.join(['1','2'])
'12'
'-'.join({'1':1,'2':2})
'1-2'
'-'.join(['1',2])
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-9-fc346e5ca62e> in <module>
----> 1 '-'.join(['1',2])


TypeError: sequence item 1: expected str instance, int found
'-'.join(b'abc')
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-8-9d04d7060926> in <module>
----> 1 '-'.join(b'abc')


TypeError: sequence item 0: expected str instance, int found

str.startswith 指定字符串开头?

字符串方法 str.startswith(),Python 官方文档描述如下:

help(str.startswith)
Help on method_descriptor:

startswith(...)
    S.startswith(prefix[, start[, end]]) -> bool
    
    Return True if S starts with the specified prefix, False otherwise.
    With optional start, test S beginning at that position.
    With optional end, stop comparing S at that position.
    prefix can also be a tuple of strings to try.

如果字符串以指定的 prefix 开始则返回 True,否则返回 False。prefix 也可以为由多个供查找的前缀构成的元组。如果有可选项 start,将从所指定位置开始检查。如果有可选项 end,将在所指定位置之前停止比较。

'a.b.a.c'.startswith('ab')
False
'a.b.a.c'.startswith('a.')
True
'a.b.a.c'.startswith('ab',2)
False
'a.b.a.c'.startswith('a.',4)
True
'a.b.a.c'.startswith('a',1,4)
False

str.endswith 指定字符串结尾?

字符串方法 str.endswith(),Python 官方文档描述如下:

help(str.endswith)
Help on method_descriptor:

endswith(...)
    S.endswith(suffix[, start[, end]]) -> bool
    
    Return True if S ends with the specified suffix, False otherwise.
    With optional start, test S beginning at that position.
    With optional end, stop comparing S at that position.
    suffix can also be a tuple of strings to try.

如果字符串以指定的 suffix 结束返回 True,否则返回 False。suffix 也可以为由多个供查找的后缀构成的元组。如果有可选项 start,将从所指定位置开始检查。如果有可选项 end,将在所指定位置之前停止比较。

'python.exe'.endswith('.exe')
True
'python.exe'.endswith(('.exe','.txt'), 5)
True
'python.exe'.endswith(('.py','.txt'), 5)
False
'python.exe'.endswith(('.exe','.txt'), 5,9)
False

str.ljust 左对齐

字符串方法 str.ljust(),Python 官方文档描述如下:

help(str.ljust)
Help on method_descriptor:

ljust(self, width, fillchar=' ', /)
    Return a left-justified string of length width.
    
    Padding is done using the specified fill character (default is a space).

返回长度为 width 的字符串,原字符串在其中靠左对齐。使用指定的 fillchar 填充空位 (默认使用 ASCII 空格符)。如果 width 小于等于字符串长度 len(str) 则返回原字符串的副本。

'python'.ljust(1)
'python'
'python'.ljust(10,'~')
'python~~~~'
'python'.ljust(10)
'python    '

str.center 居中

字符串方法 str.center(),Python 官方文档描述如下:

help(str.center)
Help on method_descriptor:

center(self, width, fillchar=' ', /)
    Return a centered string of length width.
    
    Padding is done using the specified fill character (default is a space).

返回长度为 width 的字符串,原字符串在其正中。使用指定的 fillchar 填充两边的空位(默认使用ASCII 空格符)。如果 width 小于等于字符串长度,则返回原字符串的副本:

'Python'.center(1)
'Python'
'Python'.center(10)
'  Python  '
'Python'.center(20,'~')
'~~~~~~~Python~~~~~~~'

str.rjust 右对齐

字符串方法 str.rjust(),Python 官方文档描述如下:

help(str.rjust)
Help on method_descriptor:

rjust(self, width, fillchar=' ', /)
    Return a right-justified string of length width.
    
    Padding is done using the specified fill character (default is a space).

返回长度为 width 的字符串,原字符串在其中靠右对齐。使用指定的 fillchar 填充空位 (默认使用 ASCII 空格符)。如果 width 小于等于字符串长度 len(str) 则返回原字符串的副本。

'python'.rjust(1)
'python'
'python'.rjust(10,'~')
'~~~~python'
'python'.rjust(10)
'    python'

str.format 格式化

字符串方法 str.format(),Python 官方文档描述如下:

help(str.format)
Help on method_descriptor:

format(...)
    S.format(*args, **kwargs) -> str
    
    Return a formatted version of S, using substitutions from args and kwargs.
    The substitutions are identified by braces ('{' and '}').

执行字符串格式化操作。调用此方法的字符串可以包含字符串字面值或者以花括号 {} 括起来的替换域。每个替换域可以包含一个位置参数的数字索引,或者一个关键字参数的名称。返回的字符串副本中每个替换域都会被替换为对应参数的字符串值。

"The sum of 1 + 2 is {0}".format(1+2)
'The sum of 1 + 2 is 3'

如果你需要在字面文本中包含花括号字符,可以通过重复来转义:

"{{python}}".format()
'{python}'

位置传参和关键字传参方式非常灵活,多个位置索引依次为 0,1,2……,且可以不插入字符串中;关键字传参则需要将关键字插入字符串中:

'{} and {} are both {age} years old.\
'.format('A','B',age=18)
'A and B are both 18 years old.'
'{1} and {0} are both {age} years old.\
'.format('A','B',age=18)
'B and A are both 18 years old.'
'{age} and {} are both {} years old.\
'.format('A','B',age=18)
'18 and A are both B years old.'
'{0} and {0} are both {0} years old.\
'.format('A','B',age=18)
'A and A are both A years old.'

通常,格式化值的工作由值本身的 __format__() 方法来完成。但是,在某些情况下最好强制将类型格式化为一个字符串,覆盖其本身的格式化定义。通过在调用 __format__() 之前将值转换为字符串,可以绕过正常的格式化逻辑。

目前支持的转换旗标有三种: ‘!s’ 会对值调用 str(),’!r’ 调用 repr() 而 ‘!a’ 则调用 ascii()。

'{1!s} and {0!r} are both {age!a} years old.\
'.format('A','B',age=18)
"B and 'A' are both 18 years old."

可以包含值应如何呈现的规格描述,例如字段宽度、对齐、填充、小数精度等细节信息。每种值类型可以定义自己的 “格式化迷你语言” 或解读方式。

各种格式化方式示例:

# 复数格式化
('The complex number {0} is formed'
 ' from the real part {0.real} '
 'and the imaginary part {0.imag}.').format(3-5j)
'The complex number (3-5j) is formed from the real part 3.0 and the imaginary part -5.0.'
# 利用索引取出某项格式化,不可切片
'X: {0[0]}; Y: {0[1]}'.format([1,2,3])
'X: 1; Y: 2'
# 切片格式化
a = [1,2,3]
'X: {0}; Y: {1}'.format(a[:2],a[-2:])
'X: [1, 2]; Y: [2, 3]'
# 右对齐
'{:>20}'.format('right aligned')
'       right aligned'
# 填充 ~ 居中
'{:~^20}'.format('centered')
'~~~~~~centered~~~~~~'
# 更复杂的排版
for i, w in zip('<^>', ['left', 'center', 'right']):
    print('{0:{fill}{align}20}'.format(w, fill=i, align=i))
left<<<<<<<<<<<<<<<<
^^^^^^^center^^^^^^^
>>>>>>>>>>>>>>>right
# 数字前填充 0
'{:05}'.format(12)
'00012'
# 设置保留精度
'{:f}; {:+.1f}'.format(3.14, -3.14)
'3.140000; -3.1'
# 各种进制格式化
"int: {0:d}; hex: {0:x}; oct: {0:o}; \
bin: {0:b}".format(42)
'int: 42; hex: 2a; oct: 52; bin: 101010'
# 保留进制前缀
"int: {0:d}; hex: {0:#x}; oct: {0:#o}; \
bin: {0:#b}".format(42)
'int: 42; hex: 0x2a; oct: 0o52; bin: 0b101010'
# 让数字更易读
'{:,}'.format(1234567890)
'1,234,567,890'
# 百分比格式化
'Correct answers: {:.2%}'.format(5/6)
'Correct answers: 83.33%'
# 特定类型的专属格式化
import datetime
d = datetime.datetime(2010, 7, 4, 12, 15, 58)
'{:%Y-%m-%d %H:%M:%S}'.format(d)
'2010-07-04 12:15:58'
# IP地址格式化
octets = [192, 168, 0, 1]
'{:02X}{:02X}{:02X}{:02X}'.format(*octets)
'C0A80001'

“格式化迷你语言” 总结

各种对齐选项的含义:

  • ‘<’ 强制字段在可用空间内左对齐(这是大多数对象的默认值)。
  • ‘>’ 强制字段在可用空间内右对齐(这是数字的默认值)。
  • ‘=’ 强制将填充放置在符号(如果有)之后但在数字之前。这用于以 “+000000120” 形式打印字段。此对齐选项仅对数字类型有效。当 ’0’ 紧接在字段宽度之前时,它成为默认值。
  • ‘^’ 强制字段在可用空间内居中。

仅对数字类型有效选项:

  • ‘+’ 表示标志应该用于正数和负数。
  • ‘-’ 表示标志应仅用于负数(这是默认行为)。
  • space 表示应在正数上使用前导空格,在负数上使用减号。
  • ‘#’ 选项可以让“替代形式”被用于转换。替代形式可针对不同类型分别定义。对于整数类型,当使用二进制、八进制或十六进制输出时,此选项会为输出值添加相应的 ‘0b’, ‘0o’ 或 ‘0x’ 前缀。
  • ‘,’ 选项表示使用逗号作为千位分隔符。对于感应区域设置的分隔符,请改用 ’n’ 整数表示类型。
  • ‘_’ 选项表示对浮点表示类型和整数表示类型 ’d’ 使用下划线作为千位分隔符。对于整数表示类型 ‘b’,‘o’, ‘x’ 和 ‘X’,将为每 4 个数位插入一个下划线。对于其他表示类型指定此选项则将导致错误。

确定了数据应如何呈现:

  • ’s’ 字符串格式。这是字符串的默认类型,可以省略。
  • ‘b’ 二进制格式。输出以 2 为基数的数字。
  • ‘c’ 字符。在打印之前将整数转换为相应的 unicode 字符。
  • ’d’ 十进制整数。输出以 10 为基数的数字。
  • ‘o’ 八进制格式。输出以 8 为基数的数字。
  • ‘x’ 十六进制格式。输出以 16 为基数的数字,使用小写字母表示 9 以上的数码。
  • ‘X’ 十六进制格式。输出以 16 为基数的数字,使用大写字母表示 9 以上的数码。
  • ’n’ 数字。这与 ’d’ 相似,不同之处在于它会使用当前区域设置来插入适当的数字分隔字符。
  • ’e’ 指数表示。以使用字母’e’ 来标示指数的科学计数法打印数字。默认的精度为 6。
  • ‘E’ 指数表示。与 ’e’ 相似,不同之处在于它使用大写字母’E’ 作为分隔字符。
  • ‘f’ 定点表示。将数字显示为一个定点数。默认的精确度为 6。
  • ‘F’ 定点表示。与 ‘f’ 相似,但会将 nan 转为 NAN 并将 inf 转为 INF。
  • ‘g’ 常规格式。对于给定的精度 p >= 1,这会将数值舍入到 p 位有效数字,再将结果以定点格式或科学计数法进行格式化,具体取决于其值的大小。
  • ‘G’ 常规格式。类似于 ‘g’,不同之处在于当数值非常大时会切换为 ‘E’。无穷与 NaN 也会表示为大写形式。
  • ’n’ 数字。这与 ‘g’ 相似,不同之处在于它会使用当前区域设置来插入适当的数字分隔字符。
  • ‘%’ 百分比。将数字乘以 100 并显示为定点 (‘f’) 格式,后面带一个百分号。

str.format_map 格式化

字符串方法 str.format_map(),Python 官方文档描述如下:

help(str.format_map)
Help on method_descriptor:

format_map(...)
    S.format_map(mapping) -> str
    
    Return a formatted version of S, using substitutions from mapping.
    The substitutions are identified by braces ('{' and '}').

类似于 str.format(**mapping),不同之处在于 mapping 会被直接使用。适宜使用此方法的一个例子是当 mapping 为 dict 的子类的情况:

# 创建一个字典子类型,当 键值对 不存在时,返回键
class Default(dict):
    def __missing__(self, key):
        return key
d = Default(a=1)
d['a'], d['b']
(1, 'b')
# country 键值对不存在,所以直接格式化键 ‘country’
'{name} was born in {country}'.format_map(
    Default(name='Guido'))
'Guido was born in country'

与 format 格式化对比:

'{a} is {age}'.format_map({'a':'A', 'age':18})
'A is 18'
'{a} is {age}'.format(**{'a':'A', 'age':18})
'A is 18'

f-string 格式化字符串

f-string 即格式化字符串字面值。字符串以 ‘f’ 或 ‘F’ 为前缀。这种字符串可包含替换字段,即以 {} 标示的表达式。格式化字符串字面值,会在运行时将表达式求值,而其他字符串字面值总是一个常量。

格式化字符串字面值中的表达式会被当作包含在圆括号中的普通 Python 表达式一样处理,但有少数例外。

空表达式不被允许,lambda 和赋值表达式 :=(python 3.8版添加)必须显式地加上圆括号。

f'{(a := 1+1)}' # python 3.8 才能运行
'2'
f'{(lambda x:1)}'
'<function <lambda> at 0x000001D70B06CA60>'

替换表达式可以包含换行(例如在三重引号字符串中),但是不能包含注释。

a = 3; b = 2
f'''3+2\
-5=
{a +
b - 5}'''
'3+2-5=\n0'

每个表达式会在格式化字符串字面值所包含的位置按照从左至右的顺序被求值。

f'{1+2 > 3}'
'False'

可以在表达式后加一个等于号 ‘=’(3.8 新版功能),提供了等于号 ‘=’ 的时候,输出将包含 ‘=’、’=’ 前后的空格以及求值结果。默认情况下,’=’ 会导致表达式的 repr() 被使用,除非专门指定了格式。

foo = "bar"
f"{ foo = }"
" foo = 'bar'"

可以带一个以叹号 ‘!’ 标示的转换字段,转换符 ‘!s’ 即对结果调用 str(),’!r’ 为调用 repr(),而 ‘!a’ 为调用 ascii()。

foo = "bar"
f"{foo = !s}"
'foo = bar'

还可带一个以冒号 ‘:’ 标示的格式说明符,“格式化迷你语言” 与 str.format() 方法所使用的微语言一致,详见 str.format 方法。

foo = 3.14
f"{foo:.4f}"
'3.1400'
f'{123:#o}'
'0o173'
a=5/6
f'{a:.2%}'
'83.33%'

格式表达式中不允许有反斜杠,这会引发错误:

f"newline: {ord('\n')}"
  File "<ipython-input-23-30c78f70325d>", line 1
    f"newline: {ord('\n')}"
    ^
SyntaxError: f-string expression part cannot include a backslash

想包含需要用反斜杠转义的值,可以创建一个临时变量:

newline = ord('\n')
f"newline: {newline}"
'newline: 10'

格式化字符串字面值不可用作文档字符串,即便其中没有包含表达式:

def foo():
    f"Not a docstring"

print(foo.__doc__)
None

字符串操作符

操作符 *

操作符 * 可以实现将字符串重复 n(整数)遍相连接:

'Python' * 3
'PythonPythonPython'

n 是小于 1 的整数,则得到空字符串:

'Python' * -1
''

* 操作符可以与 = 连用,重复拼接并赋值:

a = 'py'
a *= 3
a
'pypypy'

由于字符串是可迭代对象,因此可以使用 * 对字符串进行拆包:

(*'Python',)
('P', 'y', 't', 'h', 'o', 'n')

操作符 %

字符串使用 % 操作符,官方文档叫 “printf 风格的字符串格式化”。比较早的格式化方法,官方已不推荐使用,了解它能更好地读懂别人的代码。

转换标记符包含两个或更多字符并具有以下组成,且必须遵循如下规定的顺序:

  1. ‘%’ 字符,用于标记转换符的起始。
  2. 映射键(可选),由加圆括号的字符序列组成。
  3. 转换旗标(可选),用于影响某些转换类型的结果。
  4. 最小字段宽度(可选)。如果指定为 ‘*’ (星号),则实际宽度会从 values 元组的下一元素中读取,要转换的对象则为最小字段宽度和可选的精度之后的元素。
  5. 精度(可选),以在 ‘.’ (点号) 之后加精度值的形式给出。如果指定为 ‘*’ (星号),则实际精度会从 values 元组的下一元素中读取,要转换的对象则为精度之后的元素。
  6. 长度修饰符(可选)。
  7. 转换类型。
'hi %r' % 'python'
"hi 'python'"
'%s %r' % ('hi','python')
"hi 'python'"

转换旗标为:

标志 含义
‘#’ 值的转换将使用“替代形式”。
‘0’ 转换将为数字值填充零字符。
‘-’ 转换值将靠左对齐(如果同时给出 ‘0’ 转换,则会覆盖后者)。
’ ' (空格) 符号位转换产生的正数(或空字符串)前将留出一个空格。
‘+’ 符号字符 (’+’ 或 ‘-’) 将显示于转换结果的开头(会覆盖 ”空格” 旗标)。
'A is %#x' % 18
'A is 0x12'
'A is %    d' % 18
'A is  18'
'A is %05o' % 18
'A is 00022'

转换类型为:

转换符 含义
’d’ 有符号十进制整数。
‘i’ 有符号十进制整数。
‘o’ 有符号八进制数。
‘x’ 有符号十六进制数(小写)。
‘X’ 有符号十六进制数(大写)。
’e’ 浮点指数格式(小写)。
‘E’ 浮点指数格式(大写)。
‘f’ 浮点十进制格式。
‘F’ 浮点十进制格式。
‘g’ 浮点格式。如果指数小于 -4 或不小于精度则使用小写指数格式,否则使用十进制格式。
‘G’ 浮点格式。如果指数小于 -4 或不小于精度则使用大写指数格式,否则使用十进制格式。
‘c’ 单个字符(接受整数或单个字符的字符串)。
‘r’ 字符串(使用repr() 转换任何 Python 对象)。
’s’ 字符串(使用str() 转换任何 Python 对象)。
‘a’ 字符串(使用ascii() 转换任何 Python 对象)。
‘%’ 不转换参数,在结果中输出一个 ‘%’ 字符。
'%f' % 3.14
'3.140000'
'%.3e' % 3.14
'3.140e+00'
'%.1f%%' % (3.14*100)
'314.0%'

当右边的参数为一个字典(或其他映射类型)时,字符串中的格式 必须包含加圆括号的映射键,对应 % 字符之后字典中的每一项。映射键将从映射中选取要格式化的值:

'%(language)s has %(number)03d quote types.' %\
{'language': "Python", "number": 2}
'Python has 002 quote types.'

str.encode 编码为字节串

字符串方法 str.encode(),Python 官方文档描述如下:

help(str.encode)
Help on method_descriptor:

encode(self, /, encoding='utf-8', errors='strict')
    Encode the string using the codec registered for encoding.
    
    encoding
      The encoding in which to encode the string.
    errors
      The error handling scheme to use for encoding errors.
      The default is 'strict' meaning that encoding errors raise a
      UnicodeEncodeError.  Other possible values are 'ignore', 'replace' and
      'xmlcharrefreplace' as well as any other name registered with
      codecs.register_error that can handle UnicodeEncodeErrors.

返回原字符串编码为字节串对象的版本。默认编码为 ‘utf-8’。可以给出 errors 来设置不同的错误处理方案。errors 的默认值为 ‘strict’,表示编码错误会引发 UnicodeError。

下列为 ‘utf-8’ 和 ‘gbk’ 两种编码比较:

'嗨 python'.encode()
b'\xe5\x97\xa8 python'
'嗨 python'.encode('gbk')
b'\xe0\xcb python'
'▲ python'.encode('gbk')
b'\xa1\xf8 python'
'🔺 python'.encode()
b'\xf0\x9f\x94\xba python'
'🔺 python'.encode('gbk') #gbk 不能编码 🔺
---------------------------------------------------------------------------

UnicodeEncodeError                        Traceback (most recent call last)

<ipython-input-15-60e87a9208be> in <module>
----> 1 '🔺 python'.encode('gbk')


UnicodeEncodeError: 'gbk' codec can't encode character '\U0001f53a' in position 0: illegal multibyte sequence

拓展:

将字节串解码为字符串用 bytes.decode:

help(bytes.decode)
Signature: bytes.decode(self, /, encoding='utf-8', errors='strict')
Docstring:
Decode the bytes using the codec registered for encoding.

encoding
  The encoding with which to decode the bytes.
errors
  The error handling scheme to use for the handling of decoding errors.
  The default is 'strict' meaning that decoding errors raise a
  UnicodeDecodeError. Other possible values are 'ignore' and 'replace'
  as well as any other name registered with codecs.register_error that
  can handle UnicodeDecodeErrors.
Type:      method_descriptor
b'\xf0\x9f\x94\xba python'.decode()
'🔺 python'

str.capitalize 首字符大写

字符串方法 str.capitalize(),Python 官方文档描述如下:

help(str.capitalize)
Help on method_descriptor:

capitalize(self, /)
    Return a capitalized version of the string.
    
    More specifically, make the first character have upper case and the rest lower
    case.

返回原字符串的副本,其首个字符大写,其余为小写:

'pyTHON'.capitalize()
'Python'

只有首个字符是字母,才会将首个字符大写:

'嗨 pyTHON'.capitalize()
'嗨 python'

str.casefold 消除大小写

字符串方法 str.casefold(),Python 官方文档描述如下:

help(str.casefold)
Help on method_descriptor:

casefold(self, /)
    Return a version of the string suitable for caseless comparisons.

返回原字符串消除大小写的副本。消除大小写的字符串可用于忽略大小写的匹配。

消除大小写类似于转为小写,但是更加彻底一些,因为它会移除字符串中的所有大小写变化形式。例如,德语小写字母 ‘ß’ 相当于 “ss”,由于它已经是小写,lower() 不会对 ‘ß’ 做任何改变,而 casefold() 则会将其转换为 “ss”。

'pYthOn'.casefold()
'python'
'ß'.casefold()
'ss'
'ß'.lower()
'ß'

str.lower 转小写

字符串方法 str.lower(),Python 官方文档描述如下:

help(str.lower)
Help on method_descriptor:

lower(self, /)
    Return a copy of the string converted to lowercase.

返回原字符串的副本,其所有区分大小写的字符均转换为小写。

'PyThon'.lower()
'python'
'嗨 PyThon'.lower()
'嗨 python'
'PyThon Γ'.lower()
'python γ'

str.title 单词首字母大写

字符串方法 str.title(),Python 官方文档描述如下:

help(str.title)
Help on method_descriptor:

title(self, /)
    Return a version of the string where each word is titlecased.
    
    More specifically, words start with uppercased characters and all remaining
    cased characters have lower case.

返回原字符串的标题版本,其中每个单词第一个字母为大写,其余字母为小写。

'hi python'.title()
'Hi Python'
'嗨python'.title()
'嗨Python'

该算法使用一种简单的与语言无关的定义,将连续的字母组合视为单词。该定义在多数情况下都很有效,但它也意味着代表缩写形式与所有格的撇号也会成为单词边界,这可能导致不希望的结果:

"they're bill's friends from the UK".title()
"They'Re Bill'S Friends From The Uk"

str.upper 转大写

字符串方法 str.upper(),Python 官方文档描述如下:

help(str.upper)
Help on method_descriptor:

upper(self, /)
    Return a copy of the string converted to uppercase.

返回原字符串的副本,其中所有区分大小写的字符均转换为大写。

'嗨python'.upper()
'嗨PYTHON'
'πpython'.upper()
'ΠPYTHON'

str.swapcase 大小写互转

字符串方法 str.swapcase(),Python 官方文档描述如下:

help(str.swapcase)
Help on method_descriptor:

swapcase(self, /)
    Convert uppercase characters to lowercase and lowercase characters to uppercase.

返回原字符串的副本,其中大写字符转换为小写,反之亦然。请注意 s.swapcase().swapcase() == s 并不一定为真值。

'PythoN'.swapcase()
'pYTHOn'
'pYTHOn'.swapcase()
'PythoN'
'ß'.swapcase() # 德语的小写字母 ß 相当于 ss
'SS'
'SS'.swapcase()
'ss'
'ß'.swapcase().swapcase() == 'ß'
False

str.zfill 填充 0

字符串方法 str.zfill(),Python 官方文档描述如下:

help(str.zfill)
Help on method_descriptor:

zfill(self, width, /)
    Pad a numeric string with zeros on the left, to fill a field of the given width.
    
    The string is never truncated.

返回原字符串的副本,在左边填充 ASCII ‘0’ 数码使其长度变为 width。正负值前缀 (’+’/’-’) 的处理方式是在正负符号 之后填充而非在之前。如果 width 小于等于 len(str) 则返回原字符串的副本。

"42a".zfill(5)
'0042a'
"-42".zfill(5)
'-0042'
"-42".zfill(1)
'-42'

str.translate 按表转换

文档描述如下:

help(str.translate)
Help on method_descriptor:

translate(self, table, /)
    Replace each character in the string using the given translation table.
    
      table
        Translation table, which must be a mapping of Unicode ordinals to
        Unicode ordinals, strings, or None.
    
    The table must implement lookup/indexing via __getitem__, for instance a
    dictionary or list.  If this operation raises LookupError, the character is
    left untouched.  Characters mapped to None are deleted.

返回原字符串的副本,其中每个字符按给定的转换表进行映射。转换表必须是一个使用 __getitem__() 来实现索引操作的对象,通常为 mapping 或 sequence。当以 Unicode 码位序号(整数)为索引时,转换表对象可以做以下任何一种操作:返回 Unicode 序号或字符串,将字符映射为一个或多个字符;返回 None,将字符从结果字符串中删除;或引发 LookupError 异常,将字符映射为其自身。

ord('p'),ord('C')
(112, 67)
'python'.translate({112:67})
'Cython'
'python'.translate({112:'Cp'})
'Cpython'
'python'.translate({112:None})
'ython'

你可以使用 str.maketrans() 基于不同格式的字符到字符映射来创建一个转换映射表。

table = str.maketrans('pto','123')
'python'.translate(table)
'1y2h3n'

str.maketrans 生成转换表

字符串方法 str.maketrans(),该方法是一个静态方法(没有 self),Python 官方文档描述如下:

help(str.maketrans)
Help on built-in function maketrans:

maketrans(x, y=None, z=None, /)
    Return a translation table usable for str.translate().
    
    If there is only one argument, it must be a dictionary mapping Unicode
    ordinals (integers) or characters to Unicode ordinals, strings or None.
    Character keys will be then converted to ordinals.
    If there are two arguments, they must be strings of equal length, and
    in the resulting dictionary, each character in x will be mapped to the
    character at the same position in y. If there is a third argument, it
    must be a string, whose characters will be mapped to None in the result.

返回一个可供 str.translate() 使用的转换对照表。

如果只有一个参数,则它必须是一个将 Unicode 码位序号(整数)或字符(长度为 1 的字符串)映射到 Unicode 码位序号、(任意长度的)字符串 或 None 的字典。字符键将会被转换为码位序号。

str.maketrans({97:'123'})
{97: '123'}
str.maketrans({'a':97})
{97: 97}
str.maketrans({'a':None})
{97: None}

如果有两个参数,则它们必须是两个长度相等的字符串,并且在结果字典中,x 中每个字符将被映射到 y 中相同位置的字符。

str.maketrans('abc','123')
{97: 49, 98: 50, 99: 51}

如果有第三个参数,它必须是一个字符串,其中的字符将在结果中被映 射到 None。

str.maketrans('ab','12','xy')
{97: 49, 98: 50, 120: None, 121: None}

str.isalnum 是字母或数字?

字符串方法 str.isalnum(),Python 官方文档描述如下:

help(str.isalnum)
Help on method_descriptor:

isalnum(self, /)
    Return True if the string is an alpha-numeric string, False otherwise.
    
    A string is alpha-numeric if all characters in the string are alpha-numeric and
    there is at least one character in the string.

如果字符串中的所有字符都是字母或数字且至少有一个字符,则返回 True ,否则返回 False 。

''.isalnum()
False
'python123'.isalnum()
True
'python 123'.isalnum()
False
'γ'.isalnum()
True

str.isalpha 是字母(包括汉字等)?

字符串方法 str.isalpha(),Python 官方文档描述如下:

help(str.isalpha)
Help on method_descriptor:

isalpha(self, /)
    Return True if the string is an alphabetic string, False otherwise.
    
    A string is alphabetic if all characters in the string are alphabetic and there
    is at least one character in the string.

如果字符串中的所有字符都是字母,并且至少有一个字符,返回 True ,否则返回 False 。

字母字符是指那些在 Unicode 字符数据库中定义为 ”Letter” 的字符,即那些具有 ”Lm”、”Lt”、”Lu”、”Ll” 或 ”Lo” 之一的通用类别属性的字符。注意,这与 Unicode 标准中定义的 ”字母” 属性不同。

此处的字母包括汉字等。

''.isalpha()
False
'γ'.isalpha()
True
'嗨你好'.isalpha()
True
'嗨!你好'.isalpha()
False

str.isdecimal 是十进制字符?

字符串方法 str.isdecimal(),Python 官方文档描述如下:

help(str.isdecimal)
Help on method_descriptor:

isdecimal(self, /)
    Return True if the string is a decimal string, False otherwise.
    
    A string is a decimal string if all characters in the string are decimal and
    there is at least one character in the string.

如果字符串中的所有字符都是十进制字符且该字符串至少有一个字符,则返回 True,否则返回 False。

十进制字符指那些可以用来组成 10 进制数字的字符。严格地讲,十进制字符是 Unicode 通用类别 ”Nd” 中的一个字符。

''.isdecimal()
False
'3.14'.isdecimal()
False
'0123'.isdecimal()
True
'5²'.isdecimal()
False
'python'.isdecimal()
False
b'100'.isdecimal()
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-13-52e1682babfd> in <module>
----> 1 b'100'.isdecimal()


AttributeError: 'bytes' object has no attribute 'isdecimal'

str.isdigit 是数字?

字符串方法 str.isdigit(),Python 官方文档描述如下:

help(str.isdigit)
Help on method_descriptor:

isdigit(self, /)
    Return True if the string is a digit string, False otherwise.
    
    A string is a digit string if all characters in the string are digits and there
    is at least one character in the string.

如果字符串中的所有字符都是数字,并且至少有一个字符,返回 True ,否则返回 False 。

数字包括十进制字符和需要特殊处理的数字,如兼容性上标数字。这包括了不能用来组成 10 进制数的数字,如 Kharosthi 数。严格地讲,数字是指属性值为 Numeric_Type=Digit 或 Numeric_Type=Decimal 的字符。

'一'.isdigit()
False
'3.14'.isdigit()
False
'123'.isdigit()
True
b'123'.isdigit()
True
'5²'.isdigit()
True

str.isnumeric 是数值字符?

字符串方法 str.isnumeric(),Python 官方文档描述如下:

help(str.isnumeric)
Help on method_descriptor:

isnumeric(self, /)
    Return True if the string is a numeric string, False otherwise.
    
    A string is numeric if all characters in the string are numeric and there is at
    least one character in the string.

如果字符串中至少有一个字符且所有字符均为数值字符则返回 True,否则返回 False。

数值字符包括数字字符,以及所有在 Unicode 中设置了数值特性属性的字符,例如 U+2155, VUL-GAR FRACTION ONE FIFTH。正式的定义为:数值字符就是具有特征属性值 Numeric_Type=Digit, Numeric_Type=Decimal 或 Numeric_Type=Numeric 的字符。

此处所指数字包括罗马数字,汉字数字等。

'②'.isnumeric()
True
'3.14'.isnumeric()
False
'5²'.isnumeric()
True
'Ⅷ'.isnumeric()
True
'一'.isnumeric()
True
'壹'.isnumeric()
True

str.islower 是小写?

字符串方法 str.islower(),Python 官方文档描述如下:

help(str.islower)
Help on method_descriptor:

islower(self, /)
    Return True if the string is a lowercase string, False otherwise.
    
    A string is lowercase if all cased characters in the string are lowercase and
    there is at least one cased character in the string.

如果字符串中至少有一个区分大小写的字符且此类字符均为小写则返回 True,否则返回 False。

'嗨'.islower()
False
'嗨 Abc'.islower()
False
'嗨 abc'.islower()
True

str.isupper 是大写?

字符串方法 str.isupper(),Python 官方文档描述如下:

help(str.isupper)
Help on method_descriptor:

isupper(self, /)
    Return True if the string is an uppercase string, False otherwise.
    
    A string is uppercase if all cased characters in the string are uppercase and
    there is at least one cased character in the string.

如果字符串中至少有一个区分大小写的字符且此类字符均为大写则返回 True,否则返回 False。

'Γ'.isupper()
True
'嗨 AB'.isupper()
True
'嗨 Ab'.isupper()
False

str.istitle 是标题字符串?

字符串方法 str.istitle(),Python 官方文档描述如下:

help(str.istitle)
Help on method_descriptor:

istitle(self, /)
    Return True if the string is a title-cased string, False otherwise.
    
    In a title-cased string, upper- and title-case characters may only
    follow uncased characters and lowercase characters only cased ones.

如果字符串中至少有一个字符且为标题字符串则返回 True,例如大写字符之后只能带非大写字符而小写字符必须有大写字符打头。否则返回 False。

'Abc Py'.istitle()
True
'嗨 A11'.istitle()
True
'嗨 Abc'.istitle()
True
'嗨 ABC'.istitle()
False

str.isascii 是 ASCII 字符?

字符串方法 str.isascii(),Python 官方文档描述如下:

help(str.isascii)
Help on method_descriptor:

isascii(self, /)
    Return True if all characters in the string are ASCII, False otherwise.
    
    ASCII characters have code points in the range U+0000-U+007F.
    Empty string is ASCII too.

如果字符串为空或字符串中的所有字符都是 ASCII ,返回 True,否则返回 False。ASCII 字符的码点范围是 U+0000-U+007F。

''.isascii()
True
'python'.isascii()
True
'python.3'.isascii()
True
'嗨 python'.isascii()
False

str.isidentifier 是有效标识符?

字符串方法 str.isidentifier(),Python 官方文档描述如下:

help(str.isidentifier)
Help on method_descriptor:

isidentifier(self, /)
    Return True if the string is a valid Python identifier, False otherwise.
    
    Call keyword.iskeyword(s) to test whether string s is a reserved identifier,
    such as "def" or "class".

如果字符串是有效的标识符,返回 True,否则返回 False。

''.isidentifier()
False
'1mycode'.isidentifier()
False
'_mycode'.isidentifier()
True
'123'.isidentifier()
False
'_123'.isidentifier()
True
'变量名'.isidentifier()
True
'for'.isidentifier()
True

str.isprintable 是可打印字符?

字符串方法 str.isprintable(),Python 官方文档描述如下:

help(str.isprintable)
Help on method_descriptor:

isprintable(self, /)
    Return True if the string is printable, False otherwise.
    
    A string is printable if all of its characters are considered printable in
    repr() or if it is empty.

如果字符串中所有字符均为可打印字符或字符串为空则返回 True,否则返回 False。

不可打印字符是在 Unicode 字符数据库中被定义为 ”Other” 或 ”Separator” 的字符,例外情况是 ASCII 空格字符 (0x20) 被视作可打印字符。

请注意在此语境下可打印字符是指当对一个字符串发起调用 repr() 时不必被转义的字符。它们与字符串写入 sys.stdout 或 sys.stderr 时所需的处理无关。

''.isprintable()
True
' '.isprintable()
True
'\n'.isprintable()
False
'\python'.isprintable()
True
'py\thon'.isprintable()
False

str.isspace 是空白字符?

字符串方法 str.isspace(),Python 官方文档描述如下:

help(str.isspace)
Help on method_descriptor:

isspace(self, /)
    Return True if the string is a whitespace string, False otherwise.
    
    A string is whitespace if all characters in the string are whitespace and there
    is at least one character in the string.

如果字符串中只有空白字符且至少有一个字符则返回 True,否则返回 False。

''.isspace()
False
' '.isspace()
True
'\n\t\r\f'.isspace()
True
' \\'.isspace()
False

str.removeprefix 移除前缀

字符串方法 str.removeprefix()。

3.9 版本新功能。

str.removeprefix(prefix, /),如果字符串以 前缀字符串 prefix 开头,返回 string[len(prefix):],否则,返回原始字符串的副本。

'TestHook'.removeprefix('Test')
'Hook'
'BaseTestCase'.removeprefix('Test')
'BaseTestCase'

str.removesuffix 移除后缀

字符串方法 str.removesuffix()。

3.9 版本新功能。

str.removesuffix(suffix, /),如果字符串以 后缀字符串 suffix 结尾,并且后缀非空,返回 string[:-len(suffix)],否则,返回原始字符串的副本。

'MiscTests'.removesuffix('Tests')
'Misc'
'TmpDirMixin'.removesuffix('Tests')
'TmpDirMixin'

列表

列表对象 list 是高级数据结构的一种,通过方括号括起、逗号分隔的一组值得到。类型是 list。

type([1,2,3])
list

列表是可变序列,通常用于存放同类项目的集合。但没做限制,可以存放任意对象。

[1,'a',int]
[1, 'a', int]

列表是可变的,分别创建两个值相同的列表,一定是不同的对象;而同一个列表对象中的元素是可以改变的:

a, b = [1,2], [1,2]
a == b
True
id(a), id(b)
(2004948061384, 2004947934152)
a[0] = 'a'
a, id(a)
(['a', 2], 2004948061384)

列表创建方法有:直接用一对方括号表示;列表推导式;或使用内建函数 list()

[], [1,2]
([], [1, 2])
[i for i in range(3)]
[0, 1, 2]
list('abc')
['a', 'b', 'c']

列表的所有切片都将得到一个新列表,这意味着以下切片操作会返回列表的一个浅拷贝:

list_1 = [1,2,3]
list_2 = list_1[:]
id(list_1), id(list_2)
(2004947038600, 2004948064968)

然而,列表可以利用切片改变自身的内容,列表仍然是原来的列表:

list_1[:] = 'abc'
list_1, id(list_1)
(['a', 'b', 'c'], 2004947038600)

列表推导式

列表推导式提供了一个更简单的创建列表的方法。常见的用法是把某种操作应用于序列或可迭代对象的每个元素上,然后使用其结果来创建列表,或者通过满足某些特定条件元素来创建子序列。

对比下列两种创建数值平方列表的方式:

squares = []
for x in range(5):
    squares.append(x**2)

print(squares)
print(x) # 产生了一个变量 x 
[0, 1, 4, 9, 16]
4
squares = [y**2 for y in range(5)]
print(squares)
print(y) # 没有多余变量 y
[0, 1, 4, 9, 16]


---------------------------------------------------------------------------

NameError                                 Traceback (most recent call last)

<ipython-input-3-c81791cbad54> in <module>
      1 squares = [y**2 for y in range(5)]
      2 print(squares)
----> 3 print(y) #


NameError: name 'y' is not defined

我们发现,列表推导式简洁漂亮、易读,不会产生多余的变量而可能带来副作用。

列表推导式示例:

 [(x, y) for x in [1,2] for y in [1,3] if x != y]
[(1, 3), (2, 1), (2, 3)]

相当于:

_list = []
for x in [1,2]:
    for y in [1,3]:
        if x != y:
            _list.append((x,y))
_list
[(1, 3), (2, 1), (2, 3)]

列表推导式可以使用复杂的表达式和嵌套函数:

from math import pi
[str(round(pi, i)) for i in range(1, 4)]
['3.1', '3.14', '3.142']

嵌套的列表推导式:

matrix = [[1, 2, 3],
          [5, 6, 7]]
[[row[i] for row in matrix] for i in range(3)]
[[1, 5], [2, 6], [3, 7]]

相当于:

matrix = [[1, 2, 3],
          [5, 6, 7]]
_list = []
for i in range(3):
    lst = []
    for row in matrix:
        lst.append(row[i])
    _list.append(lst)
_list
[[1, 5], [2, 6], [3, 7]]

判断语句在前的列表推导式:

[True if i % 2 == 0 else False for i in [1,4,7,9]]
[False, True, False, False]

相当于:

_list = []
for i in [1,4,7,9]:
    if i % 2 == 0:
        _list.append(True)
    else:
        _list.append(False)
_list
[False, True, False, False]

列表的索引和切片

列表是序列类型中的可变类型,它的索引和切片操作,和通用的序列操作比较,有许多不一样的地方,主要体现在利用索引和切片对列表元素进行增、删、改。

索引和切片是序列类型通用操作,详细的规则和用法,见序列索引和切片

列表可以利用索引替换某一个元素:

_list = [1,2,3]
_list[1] = 0
_list
[1, 0, 3]

利用切片将切片中的元素替换为可迭代对象中的元素:

_list = [1,2,3]
_list[1:3] = '123'
_list
[1, '1', '2', '3']
# 该替换,切片长度和可迭代对象长度必须相等
_list = [1,2,3,4,5]
_list[1:5:2] = '24'
_list
[1, '2', 3, '4', 5]

需要注意,替换并没有改变原来的列表对象:

list_1 = [1,2,3]
print(id(list_1),list_1)
list_1[2] = 0
print(id(list_1),list_1)
list_1[:] = range(5)
print(id(list_1),list_1)
list_1[::2] = ('a','b','c')
print(id(list_1),list_1)
2065281773704 [1, 2, 3]
2065281773704 [1, 2, 0]
2065281773704 [0, 1, 2, 3, 4]
2065281773704 ['a', 1, 'b', 3, 'c']

可以利用切片在列表中任意位置插入数据片段:

_list = [1,2,3]
# 在前面插入两个 0
_list[:0] = (0,0)
print(_list)
# 在索引为 2 的元素前面插入 'a','b'
_list[2:2] = ['a','b']
_list
[0, 0, 1, 2, 3]


[0, 0, 'a', 'b', 1, 2, 3]

列由切片浅拷贝列表,生成新的列表,但列表中有可变对象时,新列表中的可变对象和对应的原来列表中的可变对象是同一个对象的多次引用:

list_1 = [[1,2],3,4]
list_2 = list_1[:]
print('原列表:',id(list_1),'新列表:',id(list_2))
print('原列表中的可变对象:',
      id(list_1[0]),
      '\n新列表中对应的可变对象:',id(list_2[0]))
原列表: 2065281774536 新列表: 2065281774664
原列表中的可变对象: 2065281383112 
新列表中对应的可变对象: 2065281383112

改变其中一个,都会跟着改变。需要注意区分修改列表中元素的元素,和修改列表元素的区别:

list_1[0][0] = 'a'
print(list_1, list_2)
list_1[0] = 'a'
print(list_1, list_2)
[['a', 2], 3, 4] [['a', 2], 3, 4]
['a', 3, 4] [['a', 2], 3, 4]

list.clear 删除所有元素

列表方法 list.clear(),Python 官方文档描述如下:

help(list.clear)
Help on method_descriptor:

clear(self, /)
    Remove all items from list.

删除列表中所有的元素。相当于 del a[:](a 是列表)。

_list = [1,2,3]
_list.clear()
_list
[]

del a[:] 是删除列表 a 中的所有元素,和 a 的浅拷贝无关:

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

该方法是一个过程 (过程就是不返回有意义结果的函数;在 Python 中,过程的返回值为 None), 直接对原列表进行修改:

_list = [1,2,3]
a = _list.clear()
print(a)
None

列表操作符

列表可以使用 比较运算符,但比较运算符对数据通用,单独用一个知识点讲解。该知识点介绍有列表自己特性的操作符 +*

操作符 +

操作符 +,可以让两个列表元素顺序不变地合并到一个新的列表中:

list_1 = [1,2]
list_2 = [3,4]
list_1 + list_2, list_2 + list_1
([1, 2, 3, 4], [3, 4, 1, 2])

操作符 + 还可以与赋值操作符 = 连用,将一个列表中的元素,加到另一个列表中(相当于 list.extend()),而不创建一个新列表:

list_1 = [1,2]
list_2 = [3,4]
print(id(list_1))
list_1 += list_2
id(list_1), list_1
2893532728008


(2893532728008, [1, 2, 3, 4])

操作符 *

操作符 *,可以将列表中的元素重复 n 次,得到一个新列表:

list_1 = [1,2]
list_2 = list_1*2
print(id(list_1),id(list_2))
list_2
2893532600136 2893532727432


[1, 2, 1, 2]

**注意:**如果列表中有可变对象,重复 n 次后,可变对象是同一个对象的多次引用,改变一个,都会跟着改变。

list_1 = [[0]]
list_2 = list_1*3
print(list_2)
list_1[0][0] = 1
list_2
[[0], [0], [0]]


[[1], [1], [1]]

操作符 *,还可以与赋值操作符 = 连用,用一个列表的元素重复 n 次来扩充它自身,而不创建新列表:

_list = [1,2]
print(id(_list))
_list *= 2
id(_list), _list
2893532727624


(2893532727624, [1, 2, 1, 2])

当 n 小于 1 时,则得到空列表:

[1,2,3]*0
[]

列表是可迭代对象,* 可以将列表拆包:

print(*[1,2,3])
1 2 3

list.append 添加一个元素

列表方法 list.append(),Python 官方文档描述如下:

help(list.append)
Help on method_descriptor:

append(self, object, /)
    Append object to the end of the list.

在列表的末尾添加一个元素。相当于 a[len(a):] = [x](a 是一个列表)。添加的元素可以是任何对象。

_list = [1,2]
_list.append('abc')
_list.append(list.append)
_list
[1, 2, 'abc', <method 'append' of 'list' objects>]

该方法是一个过程 (过程就是不返回有意义结果的函数;在 Python 中,过程的返回值为 None), 直接对原列表进行修改:

_list = []
a = _list.append('')
print(a)
None

list.extend 加入可迭代对象中元素

列表方法 list.extend(),Python 官方文档描述如下:

help(list.extend)
Help on method_descriptor:

extend(self, iterable, /)
    Extend list by appending elements from the iterable.

使用可迭代对象中的所有元素来扩展列表。相当于 a[len(a):] = iterable(a 是一个列表)。

_list = [1,2]
_list.extend('abc')
_list
[1, 2, 'a', 'b', 'c']
_list.extend(range(2))
_list
[1, 2, 'a', 'b', 'c', 0, 1]

该方法是一个过程 (过程就是不返回有意义结果的函数;在 Python 中,过程的返回值为 None), 直接对原列表进行修改:

_list = [1,2]
a = _list.extend('abc')
print(a)
None

list.insert 插入一个元素

列表方法 list.insert(),Python 官方文档描述如下:

help(list.insert)
Help on method_descriptor:

insert(self, index, object, /)
    Insert object before index.

在给定的位置插入一个元素。第一个参数是要插入的元素的索引,所以 a.insert(0, x) 插入列表头部,a.insert(len(a), x) 等同于 a.append(x)(a 是一个列表)。

_list = [1,2,3]
_list.insert(0,0)
_list
[0, 1, 2, 3]
_list.insert(len(_list),[4])
_list
[0, 1, 2, 3, [4]]

该方法是一个过程 (过程就是不返回有意义结果的函数;在 Python 中,过程的返回值为 None), 直接对原列表进行修改:

_list = [1,2,3]
a = _list.insert(0,0)
print(a)
None

list.sort 对列表原地排序

列表方法 list.sort(),Python 官方文档描述如下:

help(list.sort)
Help on method_descriptor:

sort(self, /, *, key=None, reverse=False)
    Sort the list in ascending order and return None.
    
    The sort is in-place (i.e. the list itself is modified) and stable (i.e. the
    order of two equal elements is maintained).
    
    If a key function is given, apply it once to each list item and sort them,
    ascending or descending, according to their function values.
    
    The reverse flag can be set to sort in descending order.

此方法会对列表进行原地排序,只使用 < 来进行各项间比较。异常不会被屏蔽——如果有任何比较操作失败,整个排序操作将失败(而列表可能会处于被部分修改的状态)。

_list = [2,3,6,1,'5']
_list.sort()
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-2-87abda0b5baf> in <module>
      1 _list = [2,3,6,1,'5']
----> 2 _list.sort()


TypeError: '<' not supported between instances of 'str' and 'int'
_list
[1, 2, 3, 6, '5']

sort() 接受两个仅限以关键字形式传入的参数 (仅限关键字参数):

  • key 指定带有一个参数的函数,应用于列表中的每一个元素,按求值之后的大小,对原列表进行排序。默认值 None 表示直接对列表项排序。
_list = [-3,2,-5,1,4]
_list.sort(key=abs)
_list
[1, 2, -3, 4, -5]
_list.sort()
_list
[-5, -3, 1, 2, 4]
  • reverse 为一个布尔值。如果设为 True,则每个列表元素将按反向顺序比较进行排序。
_list = [-3,2,-5,1,4]
_list.sort(reverse=True)
_list
[4, 2, 1, -3, -5]

当对较大的列表排序时,此方法会原地修改该序列以保证空间经济性。因此它并不会返回排序后的序列。

sort() 方法确保是稳定的——如果一个排序确保不会改变比较结果相等的元素的相对顺序就称其为稳定的。

_list = [1,'1.0',2,'1']
_list.sort(key=float)
_list
[1, '1.0', '1', 2]

该方法是一个过程 (过程就是不返回有意义结果的函数;在 Python 中,过程的返回值为 None), 直接对原列表进行修改:

_list = [5,1,4]
a = _list.sort()
print(a)
None

list.reverse 反转列表中元素

列表方法 list.reverse(),Python 官方文档描述如下:

help(list.reverse)
Help on method_descriptor:

reverse(self, /)
    Reverse *IN PLACE*.

反转列表中的元素。

_list = [1,2,3]
_list.reverse()
_list
[3, 2, 1]

该方法是一个过程 (过程就是不返回有意义结果的函数;在 Python 中,过程的返回值为 None), 直接对原列表进行修改:

_list = [1,2,3]
a = _list.reverse()
print(a)
None

list.pop 删除元素并返回

列表方法 list.pop(),Python 官方文档描述如下:

help(list.pop)
Help on method_descriptor:

pop(self, index=-1, /)
    Remove and return item at index (default last).
    
    Raises IndexError if list is empty or index is out of range.

删除列表中给定位置的元素并返回它。如果没有给定位置,list.pop() 将会删除并返回列表中的最后一个元素。给定位置超出范围,抛出 IndexError 错误。

_list = [1,2,3,4]
_list.pop()
4
_list
[1, 2, 3]
_list.pop(0)
1
_list
[2, 3]
_list.pop(5)
---------------------------------------------------------------------------

IndexError                                Traceback (most recent call last)

<ipython-input-4-efa5a84417c8> in <module>
----> 1 _list.pop(5)


IndexError: pop index out of range

list.remove 移除一个元素

列表方法 list.remove(),Python 官方文档描述如下:

help(list.remove)
Help on method_descriptor:

remove(self, value, /)
    Remove first occurrence of value.
    
    Raises ValueError if the value is not present.

移除列表中第一个值为 value 的元素。如果没有这样的元素,则抛出 ValueError 异常。

_list = [1,3,2,3]
_list.remove(3)
_list
[1, 2, 3]
_list.remove(4)
---------------------------------------------------------------------------

ValueError                                Traceback (most recent call last)

<ipython-input-3-5747ddae04fe> in <module>
----> 1 _list.remove(4)


ValueError: list.remove(x): x not in list

该方法是一个过程 (过程就是不返回有意义结果的函数;在 Python 中,过程的返回值为 None), 直接对原列表进行修改:

_list = [1,3,2,3]
a = _list.remove(3)
print(a)
None

list.count 统计元素出现次数

列表方法 list.count(),Python 官方文档描述如下:

help(list.count)
Help on method_descriptor:

count(self, value, /)
    Return number of occurrences of value.

返回元素 value 在列表中出现的次数,没有出现为 0。

_list = [1,2,3,1]
_list.count(1), _list.count([1])
(2, 0)

list.index 查找最小索引

列表方法 list.index(),Python 官方文档描述如下:

help(list.index)
Help on method_descriptor:

index(self, value, start=0, stop=9223372036854775807, /)
    Return first index of value.
    
    Raises ValueError if the value is not present.

返回列表中第一个值为 value 的元素从零开始的索引。如果没有这样的元素将会抛出 ValueError 异常。

可选参数 start 和 stop 是切片符号,用于将搜索限制为列表的特定子序列。返回的是相对于整个序列开始计算的索引,而不是相对于 start 参数。

_list = [1,2,3,4,3]
_list.index(3)
2
_list.index(3,1,4)
2
_list.index(3,4,10)
4
_list.index(3,5,10)
---------------------------------------------------------------------------

ValueError                                Traceback (most recent call last)

<ipython-input-6-3e258cbffc85> in <module>
----> 1 _list.index(3,5,10)


ValueError: 3 is not in list

list.copy 列表的一个浅拷贝

列表方法 list.copy(),Python 官方文档描述如下:

help(list.copy)
Help on method_descriptor:

copy(self, /)
    Return a shallow copy of the list.

返回列表的一个浅拷贝。相当于 a[:](a 是一个列表)。浅拷贝得到新的列表,列表中有可变对象时,浅拷贝中的可变对象元素,是原列表中同一个对象的多次引用。

list_1 = [[1],2,3]
list_2 = list_1.copy()
print(list_2)
id(list_1),id(list_2)
[[1], 2, 3]


(2622792783240, 2622792782728)
# 同一个对象
id(list_1[0]), id(list_2[0])
(2622792782792, 2622792782792)

改变其中一个都会跟着改变:

list_1[0][:] = 'abc'
list_1, list_2
([['a', 'b', 'c'], 2, 3], [['a', 'b', 'c'], 2, 3])

元组概述

元组是不可变序列,通常用一对小括号包围元素,元素之间逗号隔开。元组的元素可以是任何对象。元组类型是 tuple:

type((1,2,['1','2']))
tuple

空元组用一对圆括号表示:

()
()

单元组后面必须有一个逗号:

(1,)
(1,)

除了空元组,括号可以省略,因为决定生成元组的其实是逗号而不是圆括号,圆括号是可选的,但元组是更大的表达式的一部分或会产生语法歧义的情况,括号不能省略:

1,
(1,)
1, 1+1, ('单元组',)
(1, 2, ('单元组',))

内置函数 tuple() 用来构造元组:

tuple(), tuple('123')
((), ('1', '2', '3'))

当元组中包含可变对象(例如列表)时,元组本身不可变(不能增、删、改元组本身的元素),但包含的可变对象仍然具有它的可变性质及其操作,此时的元组不能再作为集合的元素或字典的键:

a = ([1,2],3,3)
print(id(a))
del a[0][:]
id(a), a
2207053762176


(2207053762176, ([], 3, 3))
{a}
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-14-29dfa031a339> in <module>
----> 1 {a}


TypeError: unhashable type: 'list'

tuple.count 统计元素出现次数

元组方法 tuple.count(),Python 官方文档描述如下:

help(tuple.count)
Help on method_descriptor:

count(self, value, /)
    Return number of occurrences of value.

返回值为 value 的元素在元组中的出现次数。

(1,2,1,3).count(1)
2
(1,2,1,3).count(4)
0

tuple.index 查找元素索引

元组方法 tuple.index(),Python 官方文档描述如下:

help(tuple.index)
Help on method_descriptor:

index(self, value, start=0, stop=9223372036854775807, /)
    Return first index of value.
    
    Raises ValueError if the value is not present.

返回列表中第一个值为 value 的元素从零开始的索引。如果没有这样的元素将会抛出 ValueError 异常。

可选参数 start 和 stop 是切片符号,用于将搜索限制为元组的特定子序列。返回的是相对于整个序列开始计算的索引,而不是相对于 start 参数。

(1,2,3,4,1).index(1)
0
(1,2,3,4,1).index(1,1)
4
(1,2,3,4,1).index(1,1,3)
---------------------------------------------------------------------------

ValueError                                Traceback (most recent call last)

<ipython-input-4-c6a812394eea> in <module>
----> 1 (1,2,3,4,1).index(1,1,3)


ValueError: tuple.index(x): x not in tuple

元组操作符

元组可以使用 + 拼接,拼接后按原来的顺序排列:

(4,5,6) + (1,2,3)
(4, 5, 6, 1, 2, 3)
1,2,3 + 4,5,6
(1, 2, 7, 5, 6)

+ 操作符可以与 = 连用,拼接同时赋值,元组是不可变的,该操作是创建了一个新元组:

a = 1,2,3
print(id(a))
a += 4,5,6
id(a), a
2741588191344


(2741587773864, (1, 2, 3, 4, 5, 6))

元组还可使用 * 进行 n 次重复拼接:

(1,)*3
(1, 1, 1)

当 n 小于 1 时,得到空元组:

(1,2,3)*0
()

元组中的元素如果是可变对象,* 重复拼接后,可变对象的性质同 列表操作符 知识点一样,仍然是同一个对象的多次引用:

a = ([],)
b = a*3
# 在元组 a 的第一个元素中加入 1
a[0].append(1)
b
([1], [1], [1])

同列表一样可以用 *=,重复拼接并赋值:

a = 1,
a *= 3
a
(1, 1, 1)

元组是可迭代对象,所以可以用 * 将元组拆包:

{*(1,1,2,3)}
{1, 2, 3}

序列类型概述

序列类型有列表,元组,range,字符串,字节串等。之所以称为序列类型,因为它们中的元素是按顺序排列的,可以使用下标索引或切片取值。

a = [1,2,3]
b = 1,2,3
c = range(1,4)
d = '123'
e = b'123'
a[0],b[0],c[0],d[0],e[0]
(1, 1, 1, '1', 49)
chr(49) # 字节串索引取到的是 Unicode 码位值
'1'

序列类型又分为可变和不可变两种,列表是可变序列类型,元组,range,字符串,字节串是不可变序列类型。不可变序列类型普遍实现而可变序列类型未实现的唯一操作就是对 hash() 内置函数的支持。

这种支持允许不可变类型,例如元组被用作字典的键,以及存储在集合中。

尝试对包含有不可哈希值的不可变序列进行哈希运算将会导致 TypeError。

hash((1,2)), hash('12')
(-3550055125485641917, -480100434937186025)
{(1,2), '12'}
{(1, 2), '12'}
{([1,2],3),4}
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-10-b847264454c5> in <module>
----> 1 {([1,2],3),4}


TypeError: unhashable type: 'list'

可变序列类型,可以就地进行元素的增、删、改,而不可变序列类型不可以,有时看起来像,但其实是创建了新对象:

a, b = [1,2], (1,2)
print(id(a),id(b))
a += [3] # 将元素 3 加入列表中,相当于 a.extend([3])
b += (3,) # 将元组 b 与元组 (3,) 拼接赋值给 b
print(a,b)
print(id(a), id(b))
2058115879744 2058115232576
[1, 2, 3] (1, 2, 3)
2058115879744 2058115884416

range 对象

range 对象是一个整数等差数列,用内置函数 range() 构造得到,类型是 range:

type(range(3))
range
range(-1,5,2)
range(-1, 5, 2)

range 通常用于在 for 循环中循环指定的次数:

for i in range(3):
    print('重要的事情说三遍')
重要的事情说三遍
重要的事情说三遍
重要的事情说三遍

range 对象可以指定起始值(默认 0),结束值(不包含),和等差数列的公差(默认 1)。

指定一个大于 0 的值则默认从 0 开始,公差为 1,到指定值之前一个整数结束:

list(range(5))
[0, 1, 2, 3, 4]

因为公差默认为 1,指定一个小于等于 0 的值则得到空 range:

list(range(-5))
[]

起始值,结束值,公差都指定,则得到相应等差数列:

list(range(1,5))
[1, 2, 3, 4]
list(range(1,-5,-1))
[1, 0, -1, -2, -3, -4]

range 类型相比常规 list 或 tuple,优势在于一个 range 对象总是占用固定的(较小)内存,不论其所表示的范围有多大。因为 range 类型只保存了 start, stop 和 step 值,并会根据需要计算具体单项或子范围的值。

除了拆包可以使用操作符*,range 对象不可以像列表,元组等一样,使用 +*+=*=进行拼接或重复:

(*range(3),)
(0, 1, 2)

序列索引和切片

序列类型都可以使用下标进行索引或切片取值,这是序列类型通用操作。可变序列类型比较特殊,例如列表,可以利用索引或切片进行元素增、删、改,详见 列表的索引和切片

下标从前往后,则以 0 开始,从后往前,则从 -1 开始,双向索引:

('a', 'b', 'c', 'd')
  0    1    2    3
 -4   -3   -2   -1

下标索引,直接取出对应索引下的值,超出范围则报错:

a = [1,2,3,4]
a[10]
---------------------------------------------------------------------------

IndexError                                Traceback (most recent call last)

<ipython-input-18-5680bb375980> in <module>
      1 a = [1,2,3,4]
----> 2 a[10]


IndexError: list index out of range
a = [1,2,3,4]
a[0], a[-2]
(1, 3)

下标切片取值规则:[起始:结束:步长],不包含结束。

  • 没有指定起始,则默认为 0;
  • 没有指定结束则默认到最后一个元素结束;
  • 下标可以双向混合使用;
  • 没有指定步长,则默认为 1;
  • 步长为负数,则反向取值,-1 开始。
a = [1,2,3,4,5,6,7]
a[:3]
[1, 2, 3]
a = [1,2,3,4,5,6,7]
a[3:]
[4, 5, 6, 7]
a = [1,2,3,4,5,6,7]
a[1:-3], a[-6:4]
([2, 3, 4], [2, 3, 4])
a = [1,2,3,4,5,6,7]
a[::2]
[1, 3, 5, 7]
a = [1,2,3,4,5,6,7]
a[::-2] # 默认 -1 开始取
[7, 5, 3, 1]
a = [1,2,3,4,5,6,7]
a[0:7:-2]
[]
a = [1,2,3,4,5,6,7]
a[-1:0:-2]
[7, 5, 3]

切片范围可以无限大,范围内没有元素,则得到空的容器:

a = [1,2,3,4,5,6,7]
a[5:100], a[1:1], a[5:-10]
([6, 7], [], [])

序列通用操作

序列类型有许多通用的操作,在各个知识点都有详细的介绍,下面将他们综合起来比较。

序列类型都可以使用操作符 + 进行拼接,使用 += 拼接并赋值(range 除外),但对于可变序列类型,使用 += 是原地修改:

a = [1,2]
print(id(a))
a += [3,4]
id(a), a
2612604344328


(2612604344328, [1, 2, 3, 4])
'12'+'34', [1,2]+[3,4], (1,2)+(3,4)
('1234', [1, 2, 3, 4], (1, 2, 3, 4))

序列类型都可以使用操作符 * 进行重复并拼接,使用 *= 拼接并赋值(range 除外),但对于可变序列类型,使用 *= 是原地修改:

'1'*2, 2*[1], (1,)*2
('11', [1, 1], (1, 1))
a = [1]
print(id(a))
a *= 2
id(a), a
2612603190024


(2612603190024, [1, 1])

序列类型都可以使用 * 进行拆包,拆包之后需要包含在列表,元组,集合中,或用于函数传参:

[*(1,2)], (*[1,2],), {*range(1,3)}
([1, 2], (1, 2), {1, 2})
print(*'123')
1 2 3

序列类型都可以使用索引或切片操作取值,但对于可变序列类型,还可以使用索引或切片操作进行内容的增、删、改:

a = '1234'
b = [1,2,3,4]
c = (1,2,3,4)
d = range(1,5)
a[0], b[0], c[0], d[0]
('1', 1, 1, 1)
a[1:3], b[1:3], c[1:3], d[1:3]
('23', [2, 3], (2, 3), range(2, 4))
b[1:3] = 'abc'
b
[1, 'a', 'b', 'c', 4]

序列类型都有方法 index 和 count,但字符串和字节串的 count 方法可以指定范围(具体详见各知识点的方法):

'1234'.index('23'),\
[1,2,3,4].index(2),\
range(1,4).index(2)
(1, 1, 1)
'1231'.count('1',1,3),\
(1,2,3,1).count(1)
(0, 2)

集合概述

集合是由具有唯一性的可哈希对象(一个对象的哈希值如果在其生命周期内绝不改变)组成的无序多项集。

目前有两种内置集合类型,set 和 frozenset,前者是可变类型,后者是不可变类型。

type({1}), type(frozenset())
(set, frozenset)

因为一对花括号表示字典而非集合,所以创建空集合必须使用构造函数 set()frozenset() 来表示。两个内置函数还可将可迭代对象转换为集合:

type({}), set(), frozenset()
(dict, set(), frozenset())
set('121'), frozenset('112')
({'1', '2'}, frozenset({'1', '2'}))

构造 set 集合还可以直接使用一对花括号包含元素,元素之间用逗号隔开:

{1,2,1}
{1, 2}

集合的元素必须是可哈希的,例如字符串,元组(不能包含不可哈希对象,例如元组里有列表);也是唯一的,同时存在多个哈希值相等的元素,只保留一个。

print(hash(False),hash(0))
0 0
{'12', (1,2), False, 0}
{(1, 2), '12', False}

set 类型集合是可变类型,没有哈希值,不可作为集合的元素或字典的键,而 frozenset 类型的集合可以:

{frozenset([1,2])}
{frozenset({1, 2})}

两种类型集合可进行比较,并集,交集,差集,对称差集操作:

{1,2} == frozenset({1,2})
True
{1,2} | frozenset({1,2,3})
{1, 2, 3}
frozenset({1,2,3}) ^ {1,2}
frozenset({3})

set 类型是可变的,还可进行元素增、删、改,等操作,而 frozenset 类型不可以:

a = set()
print(id(a))
# a 增加元素 1
a.add(1)
print(id(a),a)
# 将一个集合并入 a
a |= frozenset({1,2,3})
print(id(a),a)
# 清空集合 a
a.clear()
print(id(a),a)
2029775797832
2029775797832 {1}
2029775797832 {1, 2, 3}
2029775797832 set()

集合是按哈希值对元素进行储存,而不是按顺序,因此集合没有索引和切片操作。搜索元素时,由于是通过哈希值匹配,集合比序列类型效率高。

由于集合仅定义了部分排序(子集关系),比较大小的排序并无定义。

{2,1,5} > {2,5}
True
a = [{2,1,5},{9},{6},{2,5}]
a.sort() 
a
[{1, 2, 5}, {9}, {6}, {2, 5}]

set.isdisjoint 交集为空吗?

集合方法 set.isdisjoint(),Python 官方文档描述如下:

help(set.isdisjoint)
Help on method_descriptor:

isdisjoint(...)
    Return True if two sets have a null intersection.

两个集合的交集为空,则返回 True。方法中的参数可以是可迭代对象。

{1,2}.isdisjoint([1,2])
False
{1,2}.isdisjoint([3,4])
True
{1,2}.isdisjoint('12') # 参数是字符串而非数字
True

set.issubset 是子集吗?

集合方法 set.issubset(),Python 官方文档描述如下:

help(set.issubset)
Help on method_descriptor:

issubset(...)
    Report whether another set contains this set.

检查一个集合中的元素,是否都在另一个集合中。相当于 a <= b(a,b 是两个集合),但方法中的参数可以是可迭代对象。

{1,2}.issubset([1,2,3])
True
{1,2} <= {1,2,3}
True
{1,2}.issubset((1,1,2))
True

该方法检查是否是子集,而对于真子集,可以使用 < 进行检查:

{1,2} < {1,1,2}
False
{1,2} < {1,2,3}
True

set.issuperset 是超集吗?

集合方法 set.issuperset(),Python 官方文档描述如下:

help(set.issuperset)
Help on method_descriptor:

issuperset(...)
    Report whether this set contains another set.

检查一个集合是否是另一个集合的超集。相当于 a >= b(a,b 是两个集合),但方法中的参数可以是可迭代对象。

{1,2}.issuperset({True:'1',2:'2'})
True
{1,2,3} >= {True,2}
True

对于真超集,使用 > 进行检查:

{1,2,3} > {True,2}
True
{1,2} > {True,2}
False

set.union 并集

集合方法 set.union(),Python 官方文档描述如下:

help(set.union)
Help on method_descriptor:

union(...)
    Return the union of sets as a new set.
    
    (i.e. all elements that are in either set.)

该方法接收任意的位置参数,返回一个所有集合的元素组成的新集合。相当于 a | b | … (a,b 是集合),但方法中的参数可以是可迭代对象。

{1}.union([2],(3,4))
{1, 2, 3, 4}
{1} | {2} | {3,4}
{1, 2, 3, 4}

set.intersection 交集

集合方法 set.intersection(),Python 官方文档描述如下:

help(set.intersection)
Help on method_descriptor:

intersection(...)
    Return the intersection of two sets as a new set.
    
    (i.e. all elements that are in both sets.)

该方法接收任意的位置参数,返回一个所有集合中共有的元素组成的新集合。相当于 a & b & … (a,b 是集合),但方法中的参数可以是可迭代对象。

{1,2,3}.intersection({3,4},[1,5,3])
{3}
{1,2,3} & {3,4} & {1,5,3}
{3}

set.difference 差集

集合方法 set.difference(),Python 官方文档描述如下:

help(set.difference)
Help on method_descriptor:

difference(...)
    Return the difference of two or more sets as a new set.
    
    (i.e. all elements that are in this set but not the others.)

该方法接收任意的位置参数,返回一个集合在其他所有集合中都不存在的元素组成的新集合。相当于 a - b - … (a,b 是集合),但方法的参数可以是可迭代对象。

{1,2,3,4}.difference((2,1,5),{6,2,4})
{3}
{1,2,3,4} - {2,1,5} - {6,2,4}
{3}

set.symmetric_difference 对称差

集合方法 set.symmetric_difference(),Python 官方文档描述如下:

help(set.symmetric_difference)
Help on method_descriptor:

symmetric_difference(...)
    Return the symmetric difference of two sets as a new set.
    
    (i.e. all elements that are in exactly one of the sets.)

返回两个集合中非共同元素组成的新集合。相当于 a ^ b(a,b 是集合),但方法的参数可以是可迭代对象。

{1,2,3}.symmetric_difference([2,3,4])
{1, 4}
{1,2,3} ^ {2,3,4}
{1, 4}

set.copy 浅拷贝

集合方法 set.copy(),Python 官方文档描述如下:

help(set.copy)
Help on method_descriptor:

copy(...)
    Return a shallow copy of a set.

返回集合的一个浅拷贝。

a = {1,2}
print(id(a),a)
b = a.copy()
id(b),b
2091426558696 {1, 2}


(2091426559368, {1, 2})

set.update 合并更新

集合方法 set.update(),Python 官方文档描述如下:

help(set.update)
Help on method_descriptor:

update(...)
    Update a set with the union of itself and others.

该方法接收任意的位置参数,将其他集合的元素合并到一个集合中。相当于 a |= b | … (a,b 是集合),但方法的参数可以是可迭代对象。

a = {1,2}
a.update([3],(4,5))
a
{1, 2, 3, 4, 5}
a = {1,2}
a |= {3} | {4,5}
a
{1, 2, 3, 4, 5}

该方法是一个过程,就地修改集合,返回值为 None。

a = {1,2}
b = a.update([3],(4,5))
print(b)
None

set.intersection_update 交集更新

集合方法 set.intersection_update(),Python 官方文档描述如下:

help(set.intersection_update)
Help on method_descriptor:

intersection_update(...)
    Update a set with the intersection of itself and another.

该方法接收任意的位置参数,更新集合,只保留在其他所有集合中都存在的元素。相当于 a &= b & …(a,b 是集合),但方法的参数可以是可迭代对象。

a = {1,2,3,4}
a.intersection_update((1,2,5),{1,3,5})
a
{1}
a = {1,2,3,4}
a &= {1,2,5} & {1,3,5}
a
{1}

该方法是一个过程,就地修改集合,返回值为 None。

a = {1,2,3,4}
b = a.intersection_update()
print(b)
None

set.difference_update 差集更新

集合方法 set.difference_update(),Python 官方文档描述如下:

help(set.difference_update)
Help on method_descriptor:

difference_update(...)
    Remove all elements of another set from this set.

该方法接收任意的位置参数,更新集合,移除在其他集合中也存在的元素。相当于 a -= b | …(a,b 是集合),但方法的参数可以是可迭代对象。

a = {1,2,3,4}
a.difference_update((1,3,5),[4,5])
a
{2}
a = {1,2,3,4}
a -= {1,3,5} | {4,5}
a
{2}

该方法是一个过程,就地修改集合,返回值为 None。

a = {1,2,3,4}
b = a.difference_update([4,5])
print(b)
None

set ^= other 对称差集更新

集合的对称差集更新操作,相当于集合方法 set.symmetric_difference_update,其文档描述如下:

help(set.symmetric_difference_update)
Help on method_descriptor:

symmetric_difference_update(...)
    Update a set with the symmetric difference of itself and another.

更新集合,只保留两个集合中非共同部分。

a = {1,2,3}
a.symmetric_difference_update([2,3,4])
a
{1, 4}
a = {1,2,3}
a ^= {2,3,4}
a
{1, 4}

该方法是一个过程,就地修改集合,返回值为 None。

a = {1,2,3}
b = a.symmetric_difference_update([2,3,4])
print(b)
None

set.add 添加元素

集合方法 set.add(),Python 官方文档描述如下:

help(set.add)
Help on method_descriptor:

add(...)
    Add an element to a set.
    
    This has no effect if the element is already present.

集合中增加一个元素,如果元素已经存在,没有任何影响。

a = {1,2}
a.add(3)
a
{1, 2, 3}
a = {1,2}
a.add(1)
a
{1, 2}

该方法是一个过程,就地修改集合,返回值为 None。

a = {1,2}
b = a.add(3)
print(b)
None

set.remove 删除元素

集合方法 set.remove(),Python 官方文档描述如下:

help(set.remove)
Help on method_descriptor:

remove(...)
    Remove an element from a set; it must be a member.
    
    If the element is not a member, raise a KeyError.

删除一个指定元素,删除元素不存在则引发 KeyError。

a = {1,2}
a.remove(1)
a
{2}
a = {1,2}
a.remove(3)
a
---------------------------------------------------------------------------

KeyError                                  Traceback (most recent call last)

<ipython-input-3-d0f57d460301> in <module>
      1 a = {1,2}
----> 2 a.remove(3)
      3 a


KeyError: 3

该方法是一个过程,就地修改集合,返回值为 None。

a = {1,2}
b = a.remove(1)
print(b)
None

set.discard 删除元素

集合方法 set.discard(),Python 官方文档描述如下:

help(set.discard)
Help on method_descriptor:

discard(...)
    Remove an element from a set if it is a member.
    
    If the element is not a member, do nothing.

从集合中删除一个指定元素,元素不存在没有任何影响。

a = {1,2}
a.discard(1)
a
{2}
a = {1,2}
a.discard(3)
a
{1, 2}

该方法是一个过程,就地修改集合,返回值为 None。

a = {1,2}
b = a.discard(1)
print(b)
None

set.pop 删除元素并返回

集合方法 set.pop(),Python 官方文档描述如下:

help(set.pop)
Help on method_descriptor:

pop(...)
    Remove and return an arbitrary set element.
    Raises KeyError if the set is empty.

集合中删除任意一个元素,并返回它。如果集合为空,引发 KeyError。

a = {1,2}
a.pop()
1
a
{2}
set().pop()
---------------------------------------------------------------------------

KeyError                                  Traceback (most recent call last)

<ipython-input-6-db3bab0ab3b8> in <module>
----> 1 set().pop()


KeyError: 'pop from an empty set'

set.clear 清空集合元素

集合方法 set.clear(),Python 官方文档描述如下:

help(set.clear)
Help on method_descriptor:

clear(...)
    Remove all elements from this set.

清空集合所有元素。

a = {1,2}
a.clear()
a
set()

该方法是一个过程,就地修改集合,返回值为 None。

a = {1,2}
b = a.clear()
print(b)
None

字典概述

字典属于映射类型,将可哈希对象映射到任意对象,可哈希对象为字典的键,映射的对象为键对应的条目(值)。因此:

  • 键必须是可哈希且唯一的;
  • 值可以是任意对象(此处 值 仅指键对应的条目)。

所以字典的键必须是可哈希对象,例如字符串,元组(不能包含不可哈希对象);哈希值相等的对象作为字典的键会被当作一个键。

{'1':1, (1,2):2, 1:3, True:4}
{'1': 1, (1, 2): 2, 1: 4}

字典是目前唯一的标准映射类型。其类型为 dict。空字典用一对花括号表示。

type({})
dict

字典可以通过花括号包含逗号分隔的 键值对 来创建,也可以用内置函数 dict() 来创建。

{'a':1, 'b':2}
{'a': 1, 'b': 2}
dict(a=1,b=2)
{'a': 1, 'b': 2}
dict(zip(['a','b'],(1,2)))
{'a': 1, 'b': 2}

字典是可变对象,可以通过键,来获取对应的项,或更新、添加项。获取项时,键不存在则引发 KeyError。

# 获取
d = {'a':1}
d['a']
1
# 更新
d['a'] = [1,2]
d
{'a': [1, 2]}
# 键不存在则添加项
d['b'] = {1,2}
d
{'a': [1, 2], 'b': {1, 2}}
d['c']
---------------------------------------------------------------------------

KeyError                                  Traceback (most recent call last)

<ipython-input-9-05ba6e0488c7> in <module>
----> 1 d['c']


KeyError: 'c'

字典的元素(键值对)是按哈希值来存储的,没有序列类型的双向索引操作,但字典会保留元素插入时的顺序,更新不会改变插入顺序。而且使用 popitem 方法删除元素是按后进先出原则删除。

d = {}
d[3] = 3
d[1] = 1
d[2] = 2
d
{3: 3, 1: 1, 2: 2}
d[1] = 10
d
{3: 3, 1: 10, 2: 2}
d.popitem(),d.popitem(),d.popitem()
((2, 2), (1, 10), (3, 3))

dict.keys 键视图

字典方法 dict.keys(),Python 官方文档描述如下:

help(dict.keys)
Help on method_descriptor:

keys(...)
    D.keys() -> a set-like object providing a view on D's keys

返回一个由字典的键组成的动态视图。字典的视图支持成员检测,可以被迭代。

d = {'a':1, 'b':2}
d.keys()
dict_keys(['a', 'b'])
'a' in d.keys()
True
list(d.keys())
['a', 'b']
d = {'a':1, 'b':2}
d_view = d.keys()
print(d_view)
d['c'] = 3 # 添加元素,视图也动态变化
print(d_view)
dict_keys(['a', 'b'])
dict_keys(['a', 'b', 'c'])

dict.values 值视图

字典方法 dict.values(),Python 官方文档描述如下:

help(dict.values)
Help on method_descriptor:

values(...)
    D.values() -> an object providing a view on D's values

返回由字典的值组成的动态视图。字典的视图支持成员检测,可以被迭代。

d = {'a':1, 'b':2}
d_view = d.values()
print(d_view)
d['c'] = 3 # 添加元素,视图也动态变化
print(d_view)
dict_values([1, 2])
dict_values([1, 2, 3])
1 in d_view
True
[i for i in d_view]
[1, 2, 3]

dict.items 键值对视图

字典方法 dict.items(),Python 官方文档描述如下:

help(dict.items)
Help on method_descriptor:

items(...)
    D.items() -> a set-like object providing a view on D's items

返回字典的 (键,值) 元组组成的动态视图。字典的视图支持成员检测,可以被迭代。

d = {'a':1, 'b':2}
d_view = d.items()
print(d_view)
d['c'] = 3 # 添加元素,视图也动态变化
print(d_view)
dict_items([('a', 1), ('b', 2)])
dict_items([('a', 1), ('b', 2), ('c', 3)])
('a', 1) in d_view
True
[k*j for k, j in d_view]
['a', 'bb', 'ccc']

dict.get 获取某个键的值

字典方法 dict.get(),Python 官方文档描述如下:

help(dict.get)
Help on method_descriptor:

get(self, key, default=None, /)
    Return the value for key if key is in the dictionary, else default.

获取字典的项目,如果 key 存在于字典中则返回 key 的值,否则返回 default 指定的值,默认为 None。

d = {'a':1, 'b':2}
d.get('a')
1
print(d.get('c'))
None
d.get('c', 3)
3

dict.copy 浅拷贝

字典方法 dict.copy(),Python 官方文档描述如下:

help(dict.copy)
Help on method_descriptor:

copy(...)
    D.copy() -> a shallow copy of D

返回字典的一个浅拷贝。字典是可变对象,浅拷贝将创建一个新字典,但如果字典中某个 键值对 的值是可变对象,则是同一个对象的多次引用。

d = {'a':[1,2], 'b':3}
print(id(d), d)
d1 = d.copy()
print(id(d1), d1)
2276967660712 {'a': [1, 2], 'b': 3}
2276967660784 {'a': [1, 2], 'b': 3}
# 同一个对象的多次引用
id(d['a']), id(d1['a'])
(2276966678024, 2276966678024)
# 改变一个都会改变
d['a'].extend('34')
print(d)
print(d1)
{'a': [1, 2, '3', '4'], 'b': 3}
{'a': [1, 2, '3', '4'], 'b': 3}

dict.pop 删除元素并返回值

字典方法 dict.pop(),Python 官方文档描述如下:

help(dict.pop)
Help on method_descriptor:

pop(...)
    D.pop(k[,d]) -> v, remove specified key and return the corresponding value.
    If key is not found, d is returned if given, otherwise KeyError is raised

如果字典的键 k 存在,则移除 k 对应的 键值对,并返回 值;

如果 k 不存在,但指定了可选参数 d,则返回 d;

如果 k 不存在且未指定 d,则引发 KeyError。

d = {'a':1, 'b':2}
d.pop('a')
1
d = {'a':1, 'b':2}
d.pop('c', 3)
3
d = {'a':1, 'b':2}
d.pop('c')
---------------------------------------------------------------------------

KeyError                                  Traceback (most recent call last)

<ipython-input-4-1e5ab2c0b86c> in <module>
      1 d = {'a':1, 'b':2}
----> 2 d.pop('c')


KeyError: 'c'

dict.popitem 删除元素并返回键值对

字典方法 dict.popitem(),Python 官方文档描述如下:

help(dict.popitem)
Help on method_descriptor:

popitem(self, /)
    Remove and return a (key, value) pair as a 2-tuple.
    
    Pairs are returned in LIFO (last-in, first-out) order.
    Raises KeyError if the dict is empty.

按 后进先出 的原则,删除字典的元素,并以 (key, value) 元组返回。如果字典为空,则引发 KeyError。

d = {2:2,1:1}
d.popitem()
(1, 1)
d = {}
d[1] = 1
d[3] = 3
d[2] = 2
d[3] = 30 # 更新并不改变元素插入顺序
d.popitem(),d.popitem(),d.popitem()
((2, 2), (3, 30), (1, 1))
d = {}
d.popitem()
---------------------------------------------------------------------------

KeyError                                  Traceback (most recent call last)

<ipython-input-13-3d5a99fd0340> in <module>
      1 d = {}
----> 2 d.popitem()


KeyError: 'popitem(): dictionary is empty'

dict.fromkeys 创建字典

字典的方法 fromkeys,Python 官方文档描述如下:

help(dict.fromkeys)
Help on built-in function fromkeys:

fromkeys(iterable, value=None, /) method of builtins.type instance
    Create a new dictionary with keys from iterable and values set to value.

使用可迭代对象 iterable 中的元素作为字典的键,value 为值(默认是 None)创建字典。

dict.fromkeys('123')
{'1': None, '2': None, '3': None}
dict.fromkeys([1,2,3], 0)
{1: 0, 2: 0, 3: 0}

可迭代对象中不能包含不可哈希对象:

dict.fromkeys([[1],2,3], 0)
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-6-470a0d0c77b4> in <module>
----> 1 dict.fromkeys([[1],2,3], 0)


TypeError: unhashable type: 'list'

dict.clear 清空字典元素

字典方法 dict.clear(),Python 官方文档描述如下:

help(dict.clear)
Help on method_descriptor:

clear(...)
    D.clear() -> None.  Remove all items from D.

清空字典元素。

d = {'a':1, 'b':2}
d.clear()
d
{}

该方法是一个过程,返回值为 None。

d = {'a':1, 'b':2}
print(d.clear())
None

dict.setdefault 获取或插入元素

字典方法 dict.setdefault(),Python 官方文档描述如下:

help(dict.setdefault)
Help on method_descriptor:

setdefault(self, key, default=None, /)
    Insert key with a value of default if key is not in the dictionary.
    
    Return the value for key if key is in the dictionary, else default.

如果字典存在键 key,则返回它的值;如果 key 不存在,则插入 key,以 default(默认 None)作为它的值,并返回。

d = {'a':1, 'b':2}
d.setdefault('a')
1
d.setdefault('c', 3)
3
d
{'a': 1, 'b': 2, 'c': 3}
d.setdefault('c', 4) # 如果存在,并不会再次设置
3
d
{'a': 1, 'b': 2, 'c': 3}

dict.update 更新字典

字典方法 dict.update(),Python 官方文档描述如下:

help(dict.update)
Help on method_descriptor:

update(...)
    D.update([E, ]**F) -> None.  Update D from dict/iterable E and F.
    If E is present and has a .keys() method, then does:  for k in E: D[k] = E[k]
    If E is present and lacks a .keys() method, then does:  for k, v in E: D[k] = v
    In either case, this is followed by: for k in F:  D[k] = F[k]

更新字典,键相同,则覆盖原有的值,不同,则增加 键值对 元素。有下列几种情况:

  • 以字典更新字典:
d = {'a':1, 'b':2}
d.update({'a':10, 'c':3})
d
{'a': 10, 'b': 2, 'c': 3}
  • 以可迭代对象更新字典:
d = {'a':1, 'b':2}
d.update([('a',10),['c',3]])
d
{'a': 10, 'b': 2, 'c': 3}
  • 以关键字参数更新字典:
d = {'a':1, 'b':2}
d.update(a=10, c=3)
d
{'a': 10, 'b': 2, 'c': 3}

字典遍历

字典是可迭代对象,可以遍历字典的元素。由于字典元素是 键值对,遍历比较特殊。

如果未指定遍历对象,默认遍历字典的键。很多函数将字典作为可迭代对象处理时,也是如此。

d = {'a':1, 'c':3, 'b':2}
for k in d:
    print(k)
a
c
b
list(d)
['a', 'c', 'b']
sorted(d)
['a', 'b', 'c']
max(d)
'c'

可以利用字典的视图指定遍历字典的键(默认就是遍历键,可以省略),值 或是 键值对。

d = {'a':1, 'c':3, 'b':2}
for k in d.keys(): # 可以不用视图
    print(k)
a
c
b
d = {'a':1, 'c':3, 'b':2}
for k in d.values(): 
    print(k)
1
3
2
d = {'a':1, 'c':3, 'b':2}
for k,v in d.items(): 
    print(k,v)
a 1
c 3
b 2
# 按值排序后再遍历
d = {'a':1, 'c':3, 'b':2}
for k,v in sorted(d.items(),key=lambda x:x[1]): 
    print(k,v)
a 1
b 2
c 3

字典操作符

字典不可以使用比较大小的操作符 <, <=, >=>,会引发 TypeError。

{1:1} < {2:2}
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-1-8ea82599b938> in <module>
----> 1 {1:1} < {2:2}


TypeError: '<' not supported between instances of 'dict' and 'dict'

字典比较相等时,当且仅当 键值对 都相等时才相等。

{1:1.0, 2:2} == {2:2, True:1}
True
{1:'1', 2:2} == {2:2, 1:1}
False

字典可以使用 ** 操作符进行拆包,拆包后置于新字典中,可以用来更新字典;或拆包后作为关键字参数传递给函数。

{'a':1, 'b':2, **{'a':10}}
{'a': 10, 'b': 2}
d = {'a':1, 'b':2}
'a={a},b={b}'.format(**d)
'a=1,b=2'

Python 3.9 新版,实现了两个操作符 ||=

{'a':1} | {'b':2}
{'a': 1, 'b': 2}

合并 d 和 other 中的键和值来创建一个新的字典,两者必须都是字典。当 d 和 other 有相同键时,other 的值优先。

{'a':1} | {'a':5, 'b':2}
{'a': 5, 'b': 2}

用 other 的键和值更新字典 d ,other 可以是映射(mapping)或可迭代对象(iterable)的键值对。当 d 和 other 有相同键时,other 的值优先。

生成器表达式和推导式

生成器表达式和列表推导式非常相似,区别就是将方括号换成了小括号。

生成器表达式返回的是一个生成器迭代器 generator。表达式会产生一系列值,可使用内置函数 next() 逐一获取,或使用 for 循环遍历。

生成器迭代器中的元素,仅供一次性使用,而且可以要多少取多少,它会记住你取了多少,取到了哪里。

(i*i for i in range(3))
<generator object <genexpr> at 0x00000271080FBC00>
g = (i*i for i in range(5))
type(g)
generator
next(g), next(g)
(0, 1)
for i in g:
    print(i)
4
9
16

这种表达式被设计用于生成器将立即被外层函数所使用的情况。生成器表达式相比完整的生成器更紧凑但较不灵活,相比等效的列表推导式则更为节省内存。

sum(i*i for i in range(5))
30
list(i*i for i in range(5))
[0, 1, 4, 9, 16]

除了列表推导式和生成器表达式,集合和字典也可使用类似的推导式。

{i*2 for i in 'abac'}
{'aa', 'bb', 'cc'}
{i:j for i,j in zip('abc',(1,2,3))}
{'a': 1, 'b': 2, 'c': 3}
jupyter附件

空值 None

空值 None,此对象会由没有显式地设置返回值的函数返回。None 是个内置名称,空对象求值,什么也不会发生。

None
help(None)
Help on NoneType object:

class NoneType(object)
 |  Methods defined here:
 |  
 |  __bool__(self, /)
 |      self != 0
 |  
 |  __repr__(self, /)
 |      Return repr(self).
 |  
 |  ----------------------------------------------------------------------
 |  Static methods defined here:
 |  
 |  __new__(*args, **kwargs) from builtins.type
 |      Create and return a new object.  See help(type) for accurate signature.
# 没有返回值的列表方法
a = [].append(1)
a
# 将 a 打印出来,将打印内置名称 None
print(a)
None

其他内置类型

内置类型除了数字类型,布尔类型,序列类型,集合类型,映射类型,迭代器类型,解释器支持的还有一些其他种类的对象。这些对象大都仅支持一两种操作。

模块

模块唯一的特殊操作是属性访问: m.name,这里 m 为一个模块而 name 为定义在 m 的符号表中的一个属性的名称。模块属性可以被赋值。import 语句严格来说也是对模块对象的一种操作。关于模块有详细的知识点介绍。

type(math)
module
import math
math.pi
3.141592653589793

函数

函数对象是通过函数定义创建的。对函数对象的唯一操作是调用它: func(argument-list)。

实际上存在两种不同的函数对象:内置函数和用户自定义函数。两者支持同样的操作(调用函数),但实现方式不同,因此对象类型也不同。关于函数有详细的知识点介绍。

print('abc') # 调用函数
abc
type(print)
builtin_function_or_method
def f():pass # 自定义函数
type(f)
function

方法

方法是在类中定义,使用属性表示法来调用的函数。关于方法有详细的知识点介绍。

type(list.append)
method_descriptor
type([].append)
builtin_function_or_method

代码对象

代码对象可由内置的 compile() 函数返回,也可通过从函数对象的 __code__ 属性从中提取。

可将代码对象(而非源码字符串)传给 exec()eval() 内置函数来执行或求值。

def f():
    print('代码对象')
code = f.__code__
type(code)
code
exec(code)
代码对象

类型对象

类型对象表示各种对象类型。对象的类型可通过内置函数 type() 来获取。类型没有特殊的操作。标准库模块 types 定义了所有标准内置类型的名称。

type(int), type(list)
(type, type)
type(1), type([])
(int, list)

操作符概述

操作符概述

操作符可分为运算符(operators)和分隔符(delimiters)。

运算符有:

  • 数字运算符

+-***///%,详细用法见 数字运算

  • 整数按位运算符

<<>>&|^~,详细用法见 整数及其位运算

  • 比较运算符

<><=>===!=innot inisis not,详细用法见 比较运算符

  • 布尔运算符

andornot,详细用法见 布尔值及布尔运算

  • 赋值运算符 :=

Python 3.8 新增,将右边的表达式赋值给左边的变量, 同时返回表达式的值。

(a := 1+1)
2

运算符还分为一元运算符和二元运算符,一元运算符运算一个对象(操作数),二元运算符运算两个。一元运算符有 +(正数),-(负数),~(整数按位取反)和 not(布尔运算 非)。if ... else ... 有时称作三元运算符。

-1, +2, ~3, not 4
(-1, 2, -4, False)
1 + 2 - 3 * 4 # 二元运算符加、减和乘
-9
True if 2 > 3 else False
False

分隔符有:

()[]{},:.;=+=-=*=/=//=%=&=|=^=>>=<<=**=

部分分隔符用法举例:

圆括号绑定表达式,或元组显示,方括号用来对序列进行索引、切片取值或列表显示,花括号字典显示或集合显示

3 * (1 + 2) * (1,)
(1, 1, 1, 1, 1, 1, 1, 1, 1)
[1,2,3,4][1:3]
[2, 3]
{'列表':[], '集合':{1,2,3}}
{'列表': [], '集合': {1, 2, 3}}

上述分隔符 = 为赋值操作符,之后的操作符为增强赋值操作符,将名称绑定(或重新绑定)到特定值,以及修改属性或可变对象的成员项。 详见 赋值语句

假设将值 value1 绑定到名称 name,name = value1,则 name += value2 的结果相当于 name = name + value2,其他操作符类似。

举例如下:

a = 1
a += 1
a
2
b = 17
b |= 5
b
21

除了运算符和分隔符,还有 '"\# 字符,作为其他字符的组成部分时具有特殊含义,或是对词法分析器有重要意义。

'"\,详见 字符串概述转义字符

# 通常用来注释代码,但不能包含在字符串中,注释在语法分析中会被忽略:

# 这是注释
'''
# hello world
# hello python
'''
'\n# hello world\n# hello python\n'

操作符除了常规的用法,操作不同的数据类型,相应的数据类型可能定义了特定的操作规则;在特定的应用场景,也有着特殊的用法。详见 操作符特殊用法

比较运算符

比较运算符有 <><=>===!=innot inisis not

运算符 <>>=<===!= 比较两个对象的值,<>>=<= 称为次序比较;==!= 称为一致性比较。

由于所有类型都是 object 的(直接或间接)子类型,它们都从 object 继承了默认的比较行为,一致性比较是默认的比较行为,因此可在任何对象之间进行。

False == 0 != 'a' != int != {}
True

对象的值在 Python 中是一个相当抽象的概念:

  • 对象的值并没有一个规范的访问方法;
  • 对象的值并不要求具有特定的构建方式,例如由其全部数据属性组成等;
  • 比较运算符实现了一个特定的对象的值概念,可以认为正是通过实现对象比较,间接地定义了对象的值。

主要内置类型的比较行为:

1,数字类型内部可跨类型比较,按数学(算法)规则正确进行比较且不会有精度损失。但复数不支持次序比较。

True == 1 == 1.0
True
3.14 < 3
False
0 >= 0j
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-6-df80cad0b1a4> in <module>
----> 1 0 >= 0j


TypeError: '>=' not supported between instances of 'int' and 'complex'

2,字符串使用其字符的 Unicode 码位数字值依次进行比较,某个字符比较出大小即停止。如果一个字符串包含另一个,较短的排前面。

ord('a'), ord('b'), ord('c')
(97, 98, 99)
'a' == 'a', 'a' == 'c'
(True, False)
'ab' < 'ac'
True
'abc' > 'ab'
True

3,序列(列表,元组,range 实例)只能进行类型内部比较。跨类型一致性比较结果将是不相等,跨类型次序比较将引发 TypeError。range 不支持次序比较。

比较时按顺序对相应元素进行逐个比较,某个元素比较出大小即停止。如果一个序列元素包含另一个,较短的排前面。

(1,2) == [1,2]
False
[1,2] == [1,2]
True
[1,4] > [1,3,7]
True
[1,4] < [1,4,7]
True
(1,2) >= [1,2]
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-22-cb12354bca8e> in <module>
----> 1 (1,2) >= [1,2]


TypeError: '>=' not supported between instances of 'tuple' and 'list'

4, 两个字典若要相等,必须当且仅当它们具有相同的 键值对。次序比较将引发 TypeError。

{1:1} == {True:1.0}
True
{1:1} <= {True:1.0}
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-24-a13e7674e543> in <module>
----> 1 {1:1} <= {True:1.0}


TypeError: '<=' not supported between instances of 'dict' and 'dict'

5,集合 (set 或 frozenset 的实例) 可进行类型内部和跨类型的比较。

它们将比较运算符定义为子集和超集检测,具体详见集合知识点。这类关系没有定义完全排序。相应地,集合不适宜作为依赖于完全排序的函数的参数。

{1,2} == frozenset([1,2,1])
True
{3,1} > {1,2}
False
sorted([{3},{1},{2}])
[{3}, {1}, {2}]

运算符 innot in 用于成员检测。如果 x 是 s 的成员则 x in s 求值为 True,否则为 False。x not in s 返回 x in s 取反后的值。

所有内置序列和集合类型以及字典都支持此运算,对于字典来说 in 检测其是否有给定的键。

'a' in 'abc'
True
'abc' in 'abbc'
False
1 in [1,2]
True
[1] in [1,2]
False
'a' in {'a':1}
True

运算符 isis not 用于检测对象的标识号:当且仅当 x 和 y 是同一对象时 x is y 为真。一个对象的标识号可使用 id() 函数来确定。x is not y 会产生相反的逻辑值。

默认的一致性比较是基于对象的标识号。x is y 就意味着 x == y

a = 1
b = 1
a is b, a == b
(True, True)
a = (1,3)
b = (1,3)
a is b, a == b
(False, True)

操作符优先级

Python 表达式语句中操作符的优先顺序从最低优先级(最后绑定)到最高优先级(最先绑定)如下:

  • := 赋值表达式
  • lambda lambda 表达式
  • if -- else 条件表达式
  • or 布尔逻辑或 OR
  • and 布尔逻辑与 AND
  • not x 布尔逻辑非 NOT
  • in, not in, is, is not, <, <=, >, >=, !=, == 比较运算
  • | 按位或 OR
  • ^ 按位异或 XOR
  • & 按位与 AND
  • <<, >> 移位
  • +, - 加和减
  • *, /, //, % 乘,矩阵乘,除,整除,取余
  • +x, -x, ~x 正,负,按位非 NOT
  • ** 乘方(右边的先绑定)
  • await x await 表达式
  • x[index], x[index:index], x(arguments...), x.attribute 抽取,切片,调用,属性引用
  • (expressions...), [expressions...], {key: value...}, {expressions...} 绑定或加圆括号的表达式,列表显示,字典显示,集合显示

一个表达式语句中可以有多个上述操作符,最终返回一个值(包括返回值为 None 的函数调用)。

而赋值语句中的赋值操作符 =+=-=*=/=//=%=&=|=^=>>=<<=**=,在操作符中优先级最低,它右边表达式的值计算完之后,才最后绑定到左边的名称。

举例如下:

value = int('2')**(-1) == 2/(3+1)
value
True
int('2')**(-1) == 2/(3+1)
True
[].append(1) is None and not 1 - 1
True
value = 1 if 'a' in 'a b c'.split()[1:3] else 2
value
2
value **= 3 + 2 * 1
value
32

操作符 = 可以用来连续赋值:

a = b = c = 1
a,b,c
(1, 1, 1)

操作符特殊用法

容器类的内置类型,通常对操作符定义了自己特有的的操作行为。

  • * 操作符,除了数字运算的 乘,还可以用来将可迭代对象拆包。拆包之后置于元组,列表,集合中,或作为参数传递给函数:
{*{'a':1, 'b':2}}
{'a', 'b'}
print(*'abc', sep='-')
a-b-c
  • ++=**= 可用来对序列类型进行拼接,重复拼接或拼接并赋值(range 类型除外)。由于列表是可变对象,+=*= 在操作列表时,是用来更新列表。具体详见 列表操作符
'd-' + 'abc'*2
'd-abcabc'
a = [1]*2
a += [2]
a
[1, 1, 2]
  • 操作符 **||= 可以用来操作字典。具体详见 字典操作符

  • 操作符 ><>=<= 可对集合进行子集或超集比较;|&-^ 可求集合的并集,交集,差集,对称差集;|=&=-=^= 可用来更新集合。具体详见集合知识点。

  • 操作符 % 可以用来格式化字符串,而在格式化迷你语言中,定义了非常多的有特殊意义操作符。具体详见 str.format 格式化 和 字符串操作符

表达式语句

语句分为简单语句和复合语句,简单语句由一个单独的逻辑行构成。多条简单语句可以存在于同一行内并以分号分隔。表达式语句属于简单语句。

表达式语句用于计算和写入值(大多是在交互模式下),或者调用一个过程 (过程就是不返回有意义结果的函数。在 Python 中,过程的返回值为 None)。

表达式语句会对指定的表达式进行求值。

在交互模式下,它会通过内置的 repr() 函数转换为一个字符串,该结果字符串将以单独一行的形式写入标准输出(例外情况是如果结果为 None,则该过程调用不产生任何输出)。

以下均是一个表达式语句(可包含多个表达式):

'python'
'python'
1
1
(a := 1) # 3.8 新功能,赋值表达式
1
a
1
'a\n',f'b{1}' # 两个表达式
('a\n', 'b1')
1 + 2 * 3 / 5, True and False # 两个表达式
(2.2, False)
lambda x: x**2
<function __main__.<lambda>(x)>
0 if 2>3 else (1 if 5<6 else 2)
1
[1,2,3] + [4]
[1, 2, 3, 4]
[].append(1) # 返回 None
[].append(1) is None
True
# 返回值为 None,输出并不是表达式的值
print('非表达式的值')  is None
非表达式的值


True
sum(i for i in [3,2,5])
10
list(zip('abc',[1,2,3]))[1:]
[('b', 2), ('c', 3)]

用分号分隔多个表达式语句,输出最后一个的值。

1; print('非表达式的值'); 1 + 2 * 3 / 5, True and False
非表达式的值


(2.2, False)

赋值语句

赋值语句用于将名称绑定或重新绑定到特定值,以及修改属性或可变对象的成员项。

赋值语句使用赋值操作符和增强赋值操作符。详见 操作符概述。

# 将值 1 绑定到变量 a
a = 1
print('a =', a)
# 变量 a 重新绑定值 'a'
a = 'a'
print('a =', a)
a = 1
a = a

同时进行多个值多个名称绑定,值和名称数量要相等:

# 一个名称时,右边为元组
a = 1,2,3,4
a
(1, 2, 3, 4)
# 多个名称
a,b,c,d = 1,[2,3],4,5
f'a={a},b={b},c={c},d={d}'
'a=1,b=[2, 3],c=4,d=5'
# 可迭代对象拆包,则需要更多名称
a,b,c,d,e = 1,*[2,3],4,5
f'a={a},b={b},c={c},d={d},e={e}'
'a=1,b=2,c=3,d=4,e=5'

赋值一般左右两边一一对应 “同时” 赋值,但赋值给变量的多项集时可能不一样(例如列表的某一项):

x = [0,1]
i = 0
i, x[i] = 1, 2 
f'x={x},i={i}'
'x=[0, 2],i=1'

修改属性或可变对象的成员项:

# 新增属性
class A:
    x = 1
a = A()

# 右边的 a.x 是访问类属性
# 左边的 a.x 是新增实例属性, 值为类属性值 + 1
a.x = a.x + 1 
a.x, A.x
(2, 1)
# 修改列表项
a = [1,2,3]
a[0] = [0,1]
a
[[0, 1], 2, 3]
# 修改或新增字典项
d = {'a':1}
d['a'] = 10
d['b'] = 2
d
{'a': 10, 'b': 2}

赋值操作符 =,可以进行连续赋值,绑定多个名称,但赋值语句非表达式语句,不能被求值,因此不能被括号包围或分隔:

a = b = c = 1
a,b,c
(1, 1, 1)
a = (b = c = 1)
  File "<ipython-input-40-5dae496e91ce>", line 1
    a = (b = c = 1)
           ^
SyntaxError: invalid syntax

不可变容器的增强赋值操作,是重新绑定对象;可变容器的增强赋值操作是增、删、改成员项:

s = '123'
print(id(s),s)
# 因为不可变,相当于新建了一个字符串 '1234' 重新绑定 s
s += '4' 
print(id(s),s)
2371959191728 123
2371959179632 1234
l = [1,2,3]
print(id(l),l)
# 可变,相当于在原列表 l 中增加元素 4
l += [4]
print(id(l),l)
2371958877312 [1, 2, 3]
2371958877312 [1, 2, 3, 4]
l = [1,2,3]
print(id(l),l)
l *= 0 # 清空列表
print(id(l),l)
2371958290048 [1, 2, 3]
2371958290048 []
s = {1,2,3}
print(id(s),s)
s |= {0}
print(id(s),s)
2371958464800 {1, 2, 3}
2371958464800 {0, 1, 2, 3}

带标注的赋值语句。单个语句中将变量或属性标注和可选的赋值语句合为一体。标注对提高代码的可读性非常有用,看标注而不需要看代码上下文就大概知道代码的使用。

# 标注函数参数的类型及默认值
def f(n:int=1):
    print('Hi'*n)
f(2)
HiHi
name: str = '小张'
name
'小张'

if

if 语句用于有条件的执行。语法如下:

if assignment_expression:
    suite
elif assignment_expression: # 可选子句
    suite
... # 可以多个 elif
else: #可选子句
    suite

对于简单语句,可以写为一行,但不推荐。

它通过对表达式逐个求值直至找到一个真值。然后执行该 if 语句或子句体下的代码,从而 if 语句的其他部分不会被执行或求值。

如果所有表达式均为假值,else 子句体如果存在就会被执行。

for i in range(5):
    if i % 2 == 0:print(i)
0
2
4
for i in range(5):
    if i % 2 == 0:
        print(i)
0
2
4
i,j = 0,1
if i < 0: 
    print(i) # 不执行
elif i == 0:
    print(i) # 执行,下面的则不再执行
elif j == 1:
    print(j)
else:
    print(i,j)
0
i,j = 0,1
if i < 0: 
    print(i) # 不执行
elif i == 1:
    print(i) # 不执行
elif j == 0:
    print(j) # 不执行
else:
    print(i,j) # 执行
0 1

多个 if 语句连用,则分别判断,互不影响:

i,j = 0,1
if i < 0: 
    print(i) # 不执行
else:
    print(i,j) # 执行
if i == 0:
    print(i) # 执行
if j == 1:
    print(j) # 执行
else:
    print(i,j) # 不执行
0 1
0
1

for

for 语句用于对可迭代对象中的元素进行迭代。语法如下:

for target_list in expression_list:
    suite
else: # 可选子句
    suite

对于简单语句可以写为一行,但不推荐。

表达式 expression_list 被求值一次,它应该产生一个可迭代对象。系统将为 expression_list 的结果创建一个迭代器,然后每一项会按标准赋值规则(详见见 赋值语句)被依次赋值给 target_list,每赋值一次执行一次语句下的代码。

当所有项被耗尽时,else 子句如果存在将会被执行,并终止循环。

for i in range(3):print(i)
0
1
2
for i in zip('123','abc'):
    print(i)
('1', 'a')
('2', 'b')
('3', 'c')
for i,j in zip('123','abc'):
    print(f'{i}->{j}')
else:
    print('end')
1->a
2->b
3->c
end

for 循环会对 target_list 中的变量进行赋值。这将覆盖之前对这些变量的所有赋值,包括在 for 循环体中的赋值。

变量在循环结束时不会被删除,但如果序列为空,则它们根本不会被循环所赋值。

i = 'a'
for i in range(3):
    print(i)
print(i)
0
1
2
2
for i in range(0):
    print(i)
print(i)
2

while

while 语句用于在表达式保持为真的情况下重复地执行。语法如下:

while assignment_expression:
    suite
else: # 可选子句
    suite

对于简单语句可以写为一行,但不推荐。

这将重复地检验表达式,如果其值为真就执行其下的代码;表达式值为假则如果 else 子句存在就会被执行并终止循环。

i = 0
while i < 3: print(i); i += 1
0
1
2
i = 0
while i < 3: 
    print(i)
    i += 1
else: # i 为 3 时执行
    print(f'i={i}')
    print('end')
0
1
2
i=3
end

break

break 在语法上只会出现于 for 或 while 循环所嵌套的代码。

它会终结最近的外层循环,如果循环有可选的 else 子句,也会跳过该子句。

如果一个 for 循环被 break 所终结,该循环的控制变量会保持其当前值。

当 break 将控制流传出一个带有 finally 子句的 try 语句时,该 finally 子句会先被执行然后再真正离开该循环。

for i in range(6):
    for j in range(6):
        if i**2 == j:
            print(f'i={i},j={j}')
print(f'i={i},j={j}')            
i=0,j=0
i=1,j=1
i=2,j=4
i=5,j=5
for i in range(6):
    for j in range(6):
        if i**2 == j:
            print(f'i={i},j={j}')
        break
print(f'i={i},j={j}')  
i=0,j=0
i=5,j=0
for i in range(6):
    for j in range(6):
        if i**2 == j:
            print(f'i={i},j={j}')
        break # 控制内层循环
    else: # 不会执行
        print(f'i={i},j={j}')  
i=0,j=0
for i in range(6):
    for j in range(6):
        if i**2 == j:
            print(f'i={i},j={j}')
        break
else: # 属于外层循环,会执行
    print(f'i={i},j={j}')  
i=0,j=0
i=5,j=0
for i in range(5):
    try:
        print(f'3/i={3/i}')
    except ZeroDivisionError as e:
        print(e)
    finally:
        print(f'i={i}')
division by zero
i=0
3/i=3.0
i=1
3/i=1.5
i=2
3/i=1.0
i=3
3/i=0.75
i=4
# 引发异常直接跳过 break
# 无异常则继续执行完 finally 才终止循环
for i in range(5):
    try:
        print(f'3/i={3/i}')
        break
    except ZeroDivisionError as e:
        print(e)
    finally:
        print(f'i={i}')
division by zero
i=0
3/i=3.0
i=1

continue

continue 在语法上只会出现于 for 或 while 循环所嵌套的代码中。

它会继续执行最近的外层循环的下一个轮次,或者在没有下一轮次时转往 else 子句执行。

当 continue 将控制流传出一个带有 finally 子句的 try 语句时,该 finally 子句会先被执行然后再真正开始循环的下一个轮次。

for i in range(3):
    for j in range(3):
        if j == 1:
            print(f'i={i},j={j}')
        continue
i=0,j=1
i=1,j=1
i=2,j=1
for i in range(3):
    for j in range(3):
        if j == 2:
            print(f'i={i},j={j}')
        continue
    else: # 属于内层循环,每次内层循环结束都执行
        print('end')
i=0,j=2
end
i=1,j=2
end
i=2,j=2
end
for i in range(3):
    for j in range(3):
        if j == 2:
            print(f'i={i},j={j}')
        continue
else: # 属于外层循环,外层循环结束才执行
    print('end')
i=0,j=2
i=1,j=2
i=2,j=2
end
# 引发异常直接跳过 continue
# 无异常则继续执行完 finally 才继续下一次循环
for i in range(3):
    try:
        print(f'3/i={3/i}')
        continue
    except ZeroDivisionError as e:
        print(e)
    finally:
        print(f'i={i}')
    print(i) # 无异常时被跳过
else:
    print('end')
division by zero
i=0
0
3/i=3.0
i=1
3/i=1.5
i=2
end

del

del 语句用来删除名称的绑定,删除对象的属性引用 或 删除可抽取、切片的可变容器的元素。

删除名称绑定:

# 将值 (1,2) 绑定到名称 a, b, c
a = b = c = (1,2)
# 删除名称 b, c
del b,c
a
(1, 2)
b # 名称 b 已被删除
---------------------------------------------------------------------------

NameError                                 Traceback (most recent call last)

<ipython-input-27-9185c0ba25e7> in <module>
----> 1 b # 名称 b 已被删除


NameError: name 'b' is not defined
class A:
    x = y = 1
A.x, A.y
(1, 1)
del A.y
print(A.x)
A.y # 属性名称 y 已被删除,不可引用值 1
1


---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-29-8ed1b3e470b1> in <module>
      1 del A.y
      2 print(A.x)
----> 3 A.y # 属性名称 y 已被删除,不可引用值 1


AttributeError: type object 'A' has no attribute 'y'
# 删除字典元素
d = {'a':1,'b':2}
del d['a'] # 删除键为 'a' 的元素
print(d)
d['a']
{'b': 2}


---------------------------------------------------------------------------

KeyError                                  Traceback (most recent call last)

<ipython-input-30-5d91a843f82e> in <module>
      3 del d['a'] # 删除键为 'a' 的元素
      4 print(d)
----> 5 d['a']


KeyError: 'a'
# 删除列表切片
_list = [1,2,3,4]
del _list[::2]
_list
[2, 4]
# 删除列表切片
_list = [1,2,3,4]
del _list[:] # 相当于 _list[:] = []
_list
[]

pass

pass 是一个空操作 —— 当它被执行时,什么都不发生。

它适合当语法上需要一条语句但并不需要执行任何代码时用来临时占位,例如:

pass
# 什么也不做的函数
def f():pass

# 没有任何自定义属性的类
class A:pass

def

def 语句是定义函数的语句。语法如下:

@assignment_expression
def funcname(parameter_list) -> expression:
    suite

其中的装饰器 @assignment_expression,形参 parameter_list 和标注 -> expression 是可选项。

函数定义是一条可执行语句。它执行时会将函数名称 funcname 绑定到一个函数对象(函数可执行代码的包装器)。

例如,用必选项定义一个什么也不做的函数如下:

def f():
    pass

f
<function __main__.f()>

一个函数定义可以被一个或多个装饰器表达式所包装。

装饰器必须是可调用对象,它会以该函数对象作为唯一参数被发起调用。

其返回值将被绑定到函数名称。多个装饰器会以嵌套方式被应用。

@str
@type
def f():pass
f
"<class 'function'>"

大致相当于:

def f():pass
f = str(type(f))
f
"<class 'function'>"

函数形参 parameter_list 详见 函数形参

函数标注 -> expression 可以是任何表达式,标注对提高代码的可读性非常有用,看标注而不需要看代码上下文就大概知道代码的使用。例如:

# 标注函数的参数和返回值类型
def f(arg:int) -> list:
    return list(str(arg))
f(123)
['1', '2', '3']

return

return 在语法上只会出现于函数定义所嵌套的代码,不会出现于类定义所嵌套的代码。

如果提供了表达式,它将被求值,否则以 None 替代(类似省略 return 语句结果)。

return 会离开当前函数调用,并以表达式的值 (或 None) 作为返回值。

当 return 将控制流传出一个带有 finally 子句的 try 语句时,该 finally 子句会先被执行然后再真正离开该函数。

def f2():
    x =1
print(f2())
None
def f1():
    x = 1
    return
print(f1())
None
# return 结束函数调用
def f(x):
    return x**2
    print('end') # retrun 结束函数调用,不会被执行
f(2)
4
# finally 总是被执行再结束函数调用
def f(x):
    try:
        return 3/x
    except ZeroDivisionError as e:
        print(e)
    finally:
        return x, x**2
f(0),f(2)
division by zero


((0, 0), (2, 4))

yield

yield 语句,仅在定义 生成器函数 时使用,并且仅被用于生成器函数的函数体内部。语法如下:

yield from expression

from 和表达式 expression 是可选的,没有表达式默认是 None。

yield 语句整体也是一个可被求值的表达式语句,初始值也是默认 None,可通过 send 方法设置 yield 表达式的值。

在函数定义中使用 yield 使得该定义创建的是生成器函数而非普通函数。当一个生成器函数被调用的时候,它返回一个生成器迭代器。

yield from 相当于将一个可迭代对象 “拆包”,然后逐项被生成器迭代时使用。

# 创建一个简单的生成器函数
def f():
    yield
    
print(f)
# 调用它获得一个生成器
print(f())
# next() 函数迭代生成器获取表达式的值
print(next(f()))
<function f at 0x00000157028A9598>
<generator object f at 0x000001570286CB88>
None
# 获取并设置 yield 语句的值
def f(n):
    x = yield n
    print(x)
    
g = f(1)
print(next(g))
# 迭代结束,打印出 yield 语句 x 的初始值为 None
print(next(g,'end'))
1
None
end
# 可通过 send 方法设置当前 yield 表达式的值
# 并返回生成器产生的下一个值
def f(n):
    x = yield n
    print(f'yield 表达式的值为:{x}')
    n += 1
    yield n
    
g = f(0)
next(g), g.send(10)
yield 表达式的值为:10


(0, 1)
def f(*args):
    yield from args

g = f(1,2,3)
next(g),next(g),next(g),next(g,'end')
(1, 2, 3, 'end')
def f(arg):
    yield from arg

g = f('123')
next(g),next(g),next(g),next(g,'end')
('1', '2', '3', 'end')

class 语句

class 语句用来定义类,语法如下:

@assignment_expression
class classname(argument_list):
    suite

其中的装饰器 @assignment_expression,基类参数及圆括号 (argument_list) 是可选项。

类定义是一条可执行语句。它执行时会将类名称 classname 绑定到一个新建的类对象。

没有继承基类参数 argument_list 的类默认继承自基类 object。下列是一个必选参数定义的类,默认继承自 object:

# 创建一个类名为 A 的类
class A: pass

A.__bases__ # 查看基类
(object,)
# 创建一个类 B 继承自 int 和 A
class B(int, A):
    pass

B.__bases__
(int, __main__.A)

类也可以被装饰,就像装饰函数一样,装饰器表达式的求值规则与函数装饰器相同(详见 def 定义函数)。结果随后会被绑定到类名称。

@str
@type
class C: pass

C
"<class 'type'>"

大致相当于:

class C: pass
C = str(type(C))
C
"<class 'type'>"

try

try 语句可为一组语句指定异常处理器和/或清理代码。语法结构有两种如下。

  • 有 try 和 except 子句(可多个),以及可选的 else 和 finally 子句:
try:
    suite
except expression as identifier:
    suite
else: # 可选
    suite
finally: # 可选
    suite
  • 只有 try 和 finally 子句:
try:
    suite
finally:
    suite

except 子句之后的表达式(通常为异常)expression,关键字 as 以及指定的别名 identifier 都是可选的。

当 try 子句中没有发生异常时,没有异常处理器会被执行。当 try 子句中发生异常时,将启动对异常处理器的搜索。此搜索会依次检查 except 子句,直至找到与该异常相匹配的子句。

except 子句可指定一个或多个异常,如果与发生的异常 “兼容” 则该子句将匹配该异常。

  • 指定的异常如果是发生的异常所属的类或基类,则该子句将匹配该异常;
  • 指定的异常可以置于一个元组,其中包含有与发生的异常 “兼容” 的异常,该子句将匹配该异常。

当一个异常完全未被处理时,解释器会终止程序的执行。

for i in range(3):
    try:
        print(3/i)
    except (ZeroDivisionError, AssertionError) as e:
        print(e)
division by zero
3.0
1.5
for i in range(3):
    try:
        print(3/i)
    except ZeroDivisionError:
        print(f'i={i}引发异常')
i=0引发异常
3.0
1.5
for i in range(3):
    print(3/i)
---------------------------------------------------------------------------

ZeroDivisionError                         Traceback (most recent call last)

<ipython-input-5-ddbcfc1a1b1b> in <module>
      1 for i in range(3):
----> 2     print(3/i)


ZeroDivisionError: division by zero

如果存在无表达式的 except 子句,它必须是最后一个,它将匹配任何异常:

try:
    3/0
except NameError as n:
    print(n)
except:
    pass

如果没有 except 子句与异常相匹配,则会在周边代码和发起调用栈上继续搜索异常处理器,除非存在一个finally 子句正好引发了另一个异常。新引发的异常将导致旧异常的丢失:

def f():
    try:
        return 3/0
    except NameError as n:
        print(n)
try:
    f()
except ZeroDivisionError as z:
    print(z)
division by zero
def f():
    try:
        return 3/0
    except NameError as n:
        print(n)
    finally:
        name1
        
try:
    f()
except ZeroDivisionError as z: # 该异常已丢失
    print(z)
except NameError as n:
    print(n)
name 'name1' is not defined

使用 as 将匹配的异常赋值给一个别名,才能在 except 子句之后引用它,并且它将在 except 子句结束时被清除:

for i in range(3):
    try:
        print(3/i)
    except (ZeroDivisionError, AssertionError) as e:
        print(e)
print(e)
division by zero
3.0
1.5


---------------------------------------------------------------------------

NameError                                 Traceback (most recent call last)

<ipython-input-16-a87e190baccb> in <module>
      4     except (ZeroDivisionError, AssertionError) as e:
      5         print(e)
----> 6 print(e)


NameError: name 'e' is not defined

如果控制流离开 try 子句体时没有引发异常,并且没有执行 return, continue 或 break 语句,可选的 else 子句将被执行:

try:
    print('开始')
except:
    print('捕获')
else:
    print('结束')
开始
结束
while True:    
    try:
        print('开始')
        break
    except:
        print('捕获')
    else:
        print('结束')
开始

如果存在 finally,它用来指定一个 “清理” 处理程序。try,except 或 else 子句中发生任何未处理的异常,会被临时保存。finally 始终被执行,被保存的异常,它会在 finally 子句执行后被重新引发:

try:
    print(3/0)
except:
    name2
else: # 未被执行
    range(3)[5]
finally:
    print('end')
end


---------------------------------------------------------------------------

ZeroDivisionError                         Traceback (most recent call last)

<ipython-input-24-7d6fe0d94043> in <module>
      1 try:
----> 2     print(3/0)
      3 except:


ZeroDivisionError: division by zero


During handling of the above exception, another exception occurred:


NameError                                 Traceback (most recent call last)

<ipython-input-24-7d6fe0d94043> in <module>
      2     print(3/0)
      3 except:
----> 4     name2
      5 else: # 未被执行
      6     range(3)[5]


NameError: name 'name2' is not defined

如果 finally 子句引发了另一个异常,被保存的异常会被设为新异常的上下文。如果 finally 子句执行了 return, break 或 continue 语句,则被保存的异常会被丢弃:

while True:    
    try:
        print('开始')
    except:
        print('捕获')
    else:
        range(3)[5]
    finally:
        print(3/0)
开始


---------------------------------------------------------------------------

IndexError                                Traceback (most recent call last)

<ipython-input-25-1f272bfbd667> in <module>
      6     else:
----> 7         range(3)[5]
      8     finally:


IndexError: range object index out of range


During handling of the above exception, another exception occurred:


ZeroDivisionError                         Traceback (most recent call last)

<ipython-input-25-1f272bfbd667> in <module>
      7         range(3)[5]
      8     finally:
----> 9         print(3/0)


ZeroDivisionError: division by zero
while True:    
    try:
        print('开始')
    except:
        print('捕获')
    else:
        range(3)[5]
    finally:
        break
开始

当 return, break 或 continue 语句在 finally 语句之前被执行时,finally 子语句也会 ‘在离开时’ 被执行:

while True:    
    try:
        print('开始')
    except:
        print('捕获')
    else:
        break
    finally:
        print('结束')
开始
结束
def f():    
    try:
        return '开始'
    finally:
        print('结束')
f() # 先执行 finally 再离开函数调用
结束


'开始'

raise

raise 语句用来引发异常。语法如下:

raise expression from expression

如果不带表达式,raise 会重新引发当前作用域内最后一个激活的异常。如果当前作用域内没有激活的异常,将会引发 RuntimeError 来提示错误。

raise
---------------------------------------------------------------------------

RuntimeError                              Traceback (most recent call last)

<ipython-input-1-9c9a2cba73bf> in <module>
----> 1 raise


RuntimeError: No active exception to reraise

raise 会将第一个表达式求值为异常对象。它必须为 BaseException 的子类或实例。如果它是一个类,当需要时会通过不带参数地实例化该类来获得异常的实例。

type(ZeroDivisionError)
type
raise ZeroDivisionError # 无提示信息
---------------------------------------------------------------------------

ZeroDivisionError                         Traceback (most recent call last)

<ipython-input-2-798b08d1683c> in <module>
----> 1 raise ZeroDivisionError # 无提示信息


ZeroDivisionError: 
raise ZeroDivisionError('分母不能为 0') # 自定义提示信息
---------------------------------------------------------------------------

ZeroDivisionError                         Traceback (most recent call last)

<ipython-input-17-950b4accf1f2> in <module>
----> 1 raise ZeroDivisionError('分母不能为 0') # 自定义提示信息


ZeroDivisionError: 分母不能为 0

from 子句用于异常串连:如果有该子句,则第二个表达式必须为另一个异常类或实例,它将被关联到所引发的异常:

raise IndexError("索引错误") from NameError('名称错误')
---------------------------------------------------------------------------

NameError                                 Traceback (most recent call last)

NameError: 名称错误


The above exception was the direct cause of the following exception:


IndexError                                Traceback (most recent call last)

<ipython-input-18-124f83b49e6f> in <module>
----> 1 raise IndexError("索引错误") from NameError('名称错误')


IndexError: 索引错误
try:
    print(1 / 0)
except Exception as e:
    raise RuntimeError("Something bad happened") from e
---------------------------------------------------------------------------

ZeroDivisionError                         Traceback (most recent call last)

<ipython-input-13-83aaca0b7e7f> in <module>
      1 try:
----> 2     print(1 / 0)
      3 except Exception as e:


ZeroDivisionError: division by zero


The above exception was the direct cause of the following exception:


RuntimeError                              Traceback (most recent call last)

<ipython-input-13-83aaca0b7e7f> in <module>
      2     print(1 / 0)
      3 except Exception as e:
----> 4     raise RuntimeError("Something bad happened") from e


RuntimeError: Something bad happened

如果一个异常在异常处理器或 finally 中被引发,类似的机制会隐式地发挥作用:

try:
    print(1 / 0)
except:
    raise RuntimeError("Something bad happened")
---------------------------------------------------------------------------

ZeroDivisionError                         Traceback (most recent call last)

<ipython-input-16-5576c5c08e42> in <module>
      1 try:
----> 2     print(1 / 0)
      3 except:


ZeroDivisionError: division by zero


During handling of the above exception, another exception occurred:


RuntimeError                              Traceback (most recent call last)

<ipython-input-16-5576c5c08e42> in <module>
      2     print(1 / 0)
      3 except:
----> 4     raise RuntimeError("Something bad happened")


RuntimeError: Something bad happened
try:
    print(1 / 0)
finally:
    raise RuntimeError("Something bad happened")
---------------------------------------------------------------------------

ZeroDivisionError                         Traceback (most recent call last)

<ipython-input-15-8b172672db5a> in <module>
      1 try:
----> 2     print(1 / 0)
      3 finally:


ZeroDivisionError: division by zero


During handling of the above exception, another exception occurred:


RuntimeError                              Traceback (most recent call last)

<ipython-input-15-8b172672db5a> in <module>
      2     print(1 / 0)
      3 finally:
----> 4     raise RuntimeError("Something bad happened")


RuntimeError: Something bad happened

with

with 语句用于包装代码块的执行,代码块带有使用上下文管理器定义的函数或方法。语法如下:

with expression as target, expression as target, ...:
    suite

带有一个表达式 expression 的 with 语句的执行过程如下:

  1. 对上下文表达式求值以获得一个上下文管理器。
  2. 载入上下文管理器的 __enter__() 以便后续使用。
  3. 载入上下文管理器的 __exit__() 以便后续使用。
  4. 发起调用上下文管理器的 __enter__() 方法。
  5. 如果 with 语句中包含目标 target,来自 __enter__() 的返回值将被赋值给它。
  6. 执行语句体。
  7. 发起调用上下文管理器的 __exit__() 方法。

with 语句会保证如果 __enter__() 方法返回时未发生错误,则 __exit__() 将总是被调用。因此,如果在对目标赋值期间发生错误,则会将其视为在语句体内部发生的错误。

如果语句体的退出是由异常导致的,则其类型、值和回溯信息将被作为参数传递给 __exit__()。否则的话,将提供三个 None 参数(相当于无异常地退出)。

如果语句体的退出是由异常导致的,并且来自 __exit__() 方法的返回值为真,则该异常会被抑制,并会继续执行 with 语句之后的语句。如果返回值为假,则该异常会被重新引发(__exit__() 方法不应该重新引发被传入的异常,这是调用者的责任)。

如果语句体由于异常以外的任何原因退出,则来自 __exit__() 的返回值会被忽略,并会在该类退出正常的发生位置继续执行。

以下代码:

with EXPRESSION as TARGET:
    SUITE

在语义上等价于:

manager = (EXPRESSION)
enter = type(manager).__enter__
exit = type(manager).__exit__
value = enter(manager)
hit_except = False # False 表示正常执行

try:
    TARGET = value
    SUITE
except:
    hit_except = True # 发生了异常
    # 忽略或抑制异常,继续退出;或退出并引发异常
    if not exit(manager, *sys.exc_info()):
        raise
finally:
    if not hit_except: # 正常执行
        # 正常退出
        exit(manager, None, None, None)

可见使用 with 语句,无论有没有发生异常,都会 “清理” 程序(保存和恢复各种全局状态,锁定和解锁资源,关闭打开的文件等等)。

有多个表达式,则会视作存在多个 with 语句嵌套来处理多个上下文管理器:

with A() as a, B() as b:
    SUITE

# 在语义上等价于:
with A() as a:
    with B() as b:
        SUITE

with 语句常用来打开文件而不需要显式地关闭文件:

with open('../11_built-in_function/test.txt',
          'r', encoding='utf-8') as f:
    print(f.read())
xue.cn

自学是门手艺
# 相当于
f = open('../11_built-in_function/test.txt',
         'r', encoding='utf-8')
print(f.read())
f.close()
xue.cn

自学是门手艺

assert

assert 语句是在程序中插入调试性断言的简便方式。在表达式条件为 False 的时候触发异常。

简单形式为:assert expression

assert 1 + 1 == 2
assert 1 + 1 != 2
---------------------------------------------------------------------------

AssertionError                            Traceback (most recent call last)

<ipython-input-2-5cd89e6dd50b> in <module>
----> 1 assert 1 + 1 != 2


AssertionError: 

扩展形式为:assert expression1, expression2。expression2 通常是提示信息。

assert 1 + 1 != 2, '计算错误'
---------------------------------------------------------------------------

AssertionError                            Traceback (most recent call last)

<ipython-input-7-3b85a53ff241> in <module>
----> 1 assert 1 + 1 != 2, '计算错误'


AssertionError: 计算错误
for i in range(5):
    try:
        assert i % 2 == 0, f'{i}是奇数'
        print(i)
    except AssertionError as a:
        print(a)
0
1是奇数
2
3是奇数
4

import

import 语句用于从模块中导入子模块,类,函数等。语法大致有三种:

# 第一种
import module as name, module as name, ...

# 第二种
# import 之后可以置于一个元组
from module import identifier as name, identifier as name, ...

# 第三种
from module import *

模块 module 可以是高层级到低层级用属性表示法引用的模块,例如 pandas.core.series

as 及其后的别名 name 是可选的。

语句可以导入单个或多个对象,用逗号分隔实际是多个子句。

第一种导入方法,如果成功获取到模块,则可以通过以下方式之一在 import 语句所在命名空间中使用它:

  • 如果被导入模块是最高层级模块,模块名被绑定;
  • 如果导入的模块不是最高层级的模块,则该模块的最高层级模块名被绑定,该模块必须使用完整限定名访问;
  • 如果有 as,则 as 之后的别名被绑定,模块名不绑定。

如果没有指定模块,引发 ModuleNotFoundError。

import pandas
pandas
<module 'pandas' from 'F:\\anaconda\\lib\\site-packages\\pandas\\__init__.py'>
del pandas
import pandas.core.series
pandas, pandas.core, pandas.core.series
(<module 'pandas' from 'F:\\anaconda\\lib\\site-packages\\pandas\\__init__.py'>,
 <module 'pandas.core' from 'F:\\anaconda\\lib\\site-packages\\pandas\\core\\__init__.py'>,
 <module 'pandas.core.series' from 'F:\\anaconda\\lib\\site-packages\\pandas\\core\\series.py'>)
series # 必须完成限定名访问
---------------------------------------------------------------------------

NameError                                 Traceback (most recent call last)

<ipython-input-6-2859777d6f2b> in <module>
----> 1 series


NameError: name 'series' is not defined
del pandas
import pandas.core.series as pds
pds
<module 'pandas.core.series' from 'F:\\anaconda\\lib\\site-packages\\pandas\\core\\series.py'>
pandas # 只能使用别名访问
---------------------------------------------------------------------------

NameError                                 Traceback (most recent call last)

<ipython-input-8-609b6d5922fb> in <module>
----> 1 pandas


NameError: name 'pandas' is not defined
def f():
    import pandas # 局部命名空间导入
pandas # 全局命名空间不能访问
---------------------------------------------------------------------------

NameError                                 Traceback (most recent call last)

<ipython-input-10-0b76d1984901> in <module>
      1 def f():
      2     import pandas # 局部命名空间导入
----> 3 pandas


NameError: name 'pandas' is not defined

第二种导入方法,如果成功获取到模块,from 之后的模块名不会被绑定,对于 import 子句导入的属性或子模块,如果有 as 子句,则只能使用其指定的别名使用它,否则使用该属性或子模块的名称 identifier 使用它。

如果属性或子模块不存在,或不能导入,引发 ImportError。

from pandas.core import base
base
<module 'pandas.core.base' from 'F:\\anaconda\\lib\\site-packages\\pandas\\core\\base.py'>
pandas.core # 只导入了该模块下的子模块 base
---------------------------------------------------------------------------

NameError                                 Traceback (most recent call last)

<ipython-input-2-37462de79a89> in <module>
----> 1 pandas.core


NameError: name 'pandas' is not defined
# 导入多个可置于元组中
from random import (random as r1,
                    randint as r2,)
r1, r2
(<function Random.random()>,
 <bound method Random.randint of <random.Random object at 0x000001E531A14580>>)
# pandas 下没有 base,导入错误
from pandas import base 
---------------------------------------------------------------------------

ImportError                               Traceback (most recent call last)

<ipython-input-7-7903c4949085> in <module>
----> 1 from pandas import base


ImportError: cannot import name 'base' from 'pandas' (F:\anaconda\lib\site-packages\pandas\__init__.py)

第三种导入方法,则在模块中定义的全部公有名称都将绑定到 import 语句所在的命名空间。

公有名称是由在模块的命名空间中检测一个名为 __all__ 的变量来确定的;如果 __all__ 没有被定义,则公有名称的集合将包含模块的命名空间中找到的所有不以下划线字符 _ 打头的名称。

from random import *
randint
<bound method Random.randint of <random.Random object at 0x0000025E1DCC8180>>

当指定要导入哪个模块时,你不必指定模块的绝对名称。当一个模块或包被包含在另一个包之中时,可以在同一个最高层级包中进行相对导入,而不必提及包名称。

通过在 from 之后指定的模块或包中使用前缀点号,你可以在不指定确切名称的情况下指明在当前包层级结构中要上溯多少级。一个前缀点号表示是执行导入的模块所在的当前包,两个点号表示上溯一个包层级。三个点号表示上溯两级,依此类推。例如:

package/
    __init__.py
    subpackage1/
        __init__.py
        moduleX.py
        moduleY.py
    subpackage2/
        __init__.py
        moduleZ.py
    moduleA.py

假设当前位置是 subpackage1/moduleX.py,则:

from .moduleY import spam
from . import moduleY
from ..subpackage1 import moduleY
from ..subpackage2.moduleZ import eggs
from ..moduleA import foo

都是有效的。

global

global 语句作用于整个当前代码块,它后面所列出的标识符将被解读为全局变量。

在 global 语句中列出的名称不得在同一代码块内该 global 语句之前的位置中使用。

当前的实现虽然并未强制要求,但在 global 语句中列出的名称不得被定义为正式形参,不也得出现于 for 循环的控制目标、class 定义、函数定义、import 语句 或 变量标注之中。

举例如下:

def f():
    a = 0
    
f() # 调用函数,对 a 赋值
a # a 是局部变量,不可访问
---------------------------------------------------------------------------

NameError                                 Traceback (most recent call last)

<ipython-input-5-251a24e05273> in <module>
      3 
      4 f() # 调用函数,对 a 赋值
----> 5 a # a 是局部变量,不可访问


NameError: name 'a' is not defined
def f():
    global a # 将 a 声明为全局变量
    a = 0
f() # 调用函数,对 a 赋值
print(a) # a 已经是全局变量
del a
0
def f():
    a = 1 # 同一代码块中,不可在 global 前使用
    global a # 将 a 声明为全局变量
    a = 0
  File "<ipython-input-9-51bc7826eb42>", line 3
    global a # 将 a 声明为全局变量
    ^
SyntaxError: name 'a' is assigned to before global declaration
a = 1 # 与 global 不在一个代码块
def f():
    global a, b # 将 a, b 声明为全局变量
    a = 0 # a 被重新赋值
    b = 1
f() # 调用函数,对 b 赋值,对 a 重新赋值
print(a,b)
del a,b
0 1

nonlocal

nonlocal 语句会使得所列出的名称指向在它之前已经存在的,和它最近并且在包含它的作用域中绑定除全局变量以外的变量。

这种功能很重要,因为绑定的默认行为是先搜索局部命名空间。这个语句允许被封装的代码重新绑定局部作用域以外且非全局(模块)作用域当中的变量。

举例如下:

a = '全局'
def f():
    a = 'f' # f 中已经存在的 a, 包含 f2
    
    def f1():
        a = 'f1' # f1 中的局部变量
        
    def f2():
        nonlocal a # 和他最近且包含的是 'f'
        a = 'f2'
        
    def f3():
        global a
        a = 'f3'
    # 调用 f1 不改变 a = 'f'    
    f1() 
    print(a)
    # 调用 f2, nonlocal 将 a = 'f' 重新绑定为 a = 'f2'
    f2() 
    print(a)
    # 调用 f3, global 将 a 声明为全局变量,
    # 并将 a = '全局' 重新绑定为 a = 'f3' 
    # 但在 f 这个局部中,a 仍然是 'f2'
    f3()
    print(a)

f() # 调用 f 使绑定都生效
print(a)
f
f2
f2
f3
# 不存在不可以绑定
def f():
    nonlocal a 
    a = 1
f()
  File "<ipython-input-3-3706e217f701>", line 2
    nonlocal a
    ^
SyntaxError: no binding for nonlocal 'a' found
# 不是包含它的作用域,不可以绑定
def f():
    def f1():
        a = 0
    f1()
    nonlocal a 
    a = 1
f()
  File "<ipython-input-4-a036260d029b>", line 5
    nonlocal a
    ^
SyntaxError: no binding for nonlocal 'a' found
# 全局变量,不可以绑定
a = 0
def f():
    nonlocal a 
    a = 1
f()
  File "<ipython-input-5-87297c0b0eeb>", line 4
    nonlocal a
    ^
SyntaxError: no binding for nonlocal 'a' found

abs() 数字取绝对值

内置函数 abs(),Python 官方文档描述如下:

help(abs)
Help on built-in function abs in module builtins:

abs(x, /)
    Return the absolute value of the argument.

返回一个数的绝对值,参数可以是整数、浮点数或任何实现了 __abs__() 的对象。如果参数是一 个复数,则返回它的模。

abs(-1)
1
abs(-3.14)
3.14
abs(3+4j)
5.0

all() 所有元素布尔值为真?

内置函数 all(),Python 官方文档描述如下:

help(all)
Help on built-in function all in module builtins:

all(iterable, /)
    Return True if bool(x) is True for all values x in the iterable.
    
    If the iterable is empty, return True.

如果可迭代对象(iterable)的所有元素均为真值(或可迭代对象为空)则返回 True 。

all('0123') # 字符串 '0' 是真值
True
all([0,1,2,3])
False
all({})
True
all({1:[], 2:0 })
True

any() 有一个元素布尔值为真?

内置函数 any(),Python 官方文档描述如下:

help(any)
Help on built-in function any in module builtins:

any(iterable, /)
    Return True if bool(x) is True for any x in the iterable.
    
    If the iterable is empty, return False.

如果可迭代对象(iterable)的任一元素为真值则返回 True。如果可迭代对象为空,返回 False。

any([0,1])
True
any((None, [], range(1,1)))
False

ascii() 返回对象的可打印字符串

内置函数 ascii(),Python 官方文档描述如下:

help(ascii)
Help on built-in function ascii in module builtins:

ascii(obj, /)
    Return an ASCII-only representation of an object.
    
    As repr(), return a string containing a printable representation of an
    object, but escape the non-ASCII characters in the string returned by
    repr() using \\x, \\u or \\U escapes. This generates a string similar
    to that returned by repr() in Python 2.

就像函数 repr(),返回一个对象可打印的字符串,但是非 ASCII 编码的字符,会使用 \x、\u 和 \U 来转义。

ascii(123)
'123'
ascii(None)
'None'
ascii('python')
"'python'"
ascii('嗨')
"'\\u55e8'"
repr('嗨')
"'嗨'"
'\u55e8' # 16 位十六进制数 55e8 码位的字符
'嗨'

bin() 整数的二进制形式

内置函数 bin(),Python 官方文档描述如下:

help(bin)
Help on built-in function bin in module builtins:

bin(number, /)
    Return the binary representation of an integer.
    
    >>> bin(2796202)
    '0b1010101010101010101010'

返回给定整数的二进制表示形式的字符串。

bin(123)
'0b1111011'
0b1111011
123

bool 返回对象的布尔值

内置函数(类) bool,Python 官方文档描述如下:

help(bool)
Help on class bool in module builtins:

class bool(int)
 |  bool(x) -> bool
 |  
 |  Returns True when the argument x is true, False otherwise.
 |  The builtins True and False are the only two instances of the class bool.
 |  The class bool is a subclass of the class int, and cannot be subclassed.
 |  
 |  Method resolution order:
 |      bool
 |      int
 |      object
 |  

返回对象 x 的布尔值。省略 x 则返回 False。对象的真值、假值规则如下:

一个对象在默认情况下均被视为真值,除非当该对象被调用时其所属类定义了 __bool__() 方法且返回 False 或是定义了 __len__() 方法且返回零。

下面基本完整地列出了会被视为假值的内置对象:

  • 被定义为假值的常量: None 和 False。
  • 任何数值类型的零: 0, 0.0, 0j, Decimal(0), Fraction(0, 1)
  • 空的序列和多项集: ‘’, (), [], {}, set(), range(0)
type(bool)
type
bool()
False
bool(None)
False
bool('0')
True
bool(' ')
True

bytes 创建 bytes 对象

内置函数(类)bytes,Python 官方文档描述如下:

help(bytes)
Help on class bytes in module builtins:

class bytes(object)
 |  bytes(iterable_of_ints) -> bytes
 |  bytes(string, encoding[, errors]) -> bytes
 |  bytes(bytes_or_buffer) -> immutable copy of bytes_or_buffer
 |  bytes(int) -> bytes object of size given by the parameter initialized with null bytes
 |  bytes() -> empty bytes object
 |  
 |  Construct an immutable array of bytes from:
 |    - an iterable yielding integers in range(256)
 |    - a text string encoded using the specified encoding
 |    - any object implementing the buffer API.
 |    - an integer
 |  
 |  Methods defined here:
 |  

返回一个新的二进制序列 bytes 对象。参数可以是:

  • 0~255 的整数组成的可迭代类型
  • 字符串,并指定编码格式 encoding
  • 与缓冲区接口一致的对象
  • 整数
  • 或者不传参数
type(bytes)
type
bytes([1,2,3])
b'\x01\x02\x03'
bytes('嗨', 'utf-8')
b'\xe5\x97\xa8'
bytes(3)
b'\x00\x00\x00'
bytes()
b''

callable() 是可调用对象?

内置函数 callable(),Python 官方文档描述如下:

help(callable)
Help on built-in function callable in module builtins:

callable(obj, /)
    Return whether the object is callable (i.e., some kind of function).
    
    Note that classes are callable, as are instances of classes with a
    __call__() method.

如果 obj 是可调用对象就返回 True,否则返回 False。如果返回 True,调用仍可能失败,但如果返回 False,则调用将肯定不会成功。

函数、方法、类以及实现了 __call__() 方法的类的实例是可调用的。

callable(1)
False
callable(int)
True
class Myint(int):
    def __call__(self):
        pass
num = Myint(1)
num
1
callable(num)
True
callable(lambda: 1)
True

chr() 返回 Unicode 码位值对应字符

内置函数 chr(),Python 官方文档描述如下:

help(chr)
Help on built-in function chr in module builtins:

chr(i, /)
    Return a Unicode string of one character with ordinal i; 0 <= i <= 0x10ffff.

返回 Unicode 码位对应的字符的字符串格式。码位范围是 0~1114111(16 进制表示是 0x10FFFF),超过这个范围,会触发 ValueError 异常。该函数是 ord() 的逆函数。

chr(97)
'a'
ord('a')
97
chr(1114111)
'\U0010ffff'
chr(1114112)
---------------------------------------------------------------------------

ValueError                                Traceback (most recent call last)

<ipython-input-5-4857faf08086> in <module>
----> 1 chr(1114112)


ValueError: chr() arg not in range(0x110000)

classmethod 封装函数为类方法

内置函数(类)classmethod,Python 官方文档描述如下:

help(classmethod)
Help on class classmethod in module builtins:

class classmethod(object)
 |  classmethod(function) -> method
 |  
 |  Convert a function to be a class method.
 |  
 |  A class method receives the class as implicit first argument,
 |  just like an instance method receives the instance.
 |  To declare a class method, use this idiom:
 |  
 |    class C:
 |        @classmethod
 |        def f(cls, arg1, arg2, ...):
 |            ...
 |  

把一个函数封装成类方法。一个类方法把类自己作为第一个实参,就像一个实例方法把实例自己作为第一个实参。

可将函数作为参数来声明类方法,但请用习惯的装饰器形式(@classmethod)来声明类方法。

type(classmethod)
type
class A:
    print_itself = classmethod(print)

A.print_itself()
<class '__main__.A'>
class A:
    @classmethod
    def print_itself(cls):
        print(cls)

A.print_itself()
<class '__main__.A'>

compile() 创建代码对象

内置函数 compile(),Python 官方文档描述如下:

help(compile)
Help on built-in function compile in module builtins:

compile(source, filename, mode, flags=0, dont_inherit=False, optimize=-1)
    Compile source into a code object that can be executed by exec() or eval().
    
    The source code may represent a Python module, statement or expression.
    The filename will be used for run-time error messages.
    The mode must be 'exec' to compile a module, 'single' to compile a
    single (interactive) statement, or 'eval' to compile an expression.
    The flags argument, if present, controls which future statements influence
    the compilation of the code.
    The dont_inherit argument, if true, stops the compilation inheriting
    the effects of any future statements in effect in the code calling
    compile; if absent or false these statements do influence the compilation,
    in addition to any features explicitly specified.

将 source 编译成代码或 AST 对象。代码对象可以被 exec() 或 eval() 执行。

参数说明:

  • source,要编译的资源,可以是字符串、字节或 AST 对象。
  • filename,源所来自的文件的名称。如果代码不需要从文件中读取,可以传入一些可辨识的值(经常会使用字符串)。
  • mode,指定了编译代码必须用的模式。如果 source 是语句序列,可以是 ’exec’;如果是单一表达式,可以是 ’eval’;如果是单个交互式语句,可以是 ‘single’。
  • flags 和 dont-inherit,控制在编译 source 时要用到哪个 future 语句。
  • optimize,指定编译器的优化级别;默认值 -1 选择与解释器的 -O 选项相同的优化级别。
source = 'for i in range(3):print(i)'
code = compile(source,'null','exec')
code
<code object <module> at 0x000001E999B07780, file "null", line 1>
exec(code)
0
1
2
eval(code)
0
1
2

complex 创建复数

内置函数(类)complex,Python 官方文档描述如下:

help(complex)
Help on class complex in module builtins:

class complex(object)
 |  complex(real=0, imag=0)
 |  
 |  Create a complex number from a real part and an optional imaginary part.
 |  
 |  This is equivalent to (real + imag*1j) where imag defaults to 0.
 |  
 |  Methods defined here:
 |  
 |  __abs__(self, /)
 |      abs(self)
 |  
 |  __add__(self, value, /)
 |      Return self+value.
 |  
 |  __bool__(self, /)
 |      self != 0
 |  
 |  __divmod__(self, value, /)
 |      Return divmod(self, value).
 |  
 |  __eq__(self, value, /)
 |      Return self==value.
 |  
 |  __float__(self, /)
 |      float(self)
 |  
 |  __floordiv__(self, value, /)
 |      Return self//value.
 |  
 |  __format__(...)
 |      complex.__format__() -> str
 |      
 |      Convert to a string according to format_spec.
 |  
 |  __ge__(self, value, /)
 |      Return self>=value.
 |  
 |  __getattribute__(self, name, /)
 |      Return getattr(self, name).
 |  
 |  __getnewargs__(...)
 |  
 |  __gt__(self, value, /)
 |      Return self>value.
 |  
 |  __hash__(self, /)
 |      Return hash(self).
 |  
 |  __int__(self, /)
 |      int(self)
 |  
 |  __le__(self, value, /)
 |      Return self<=value.
 |  
 |  __lt__(self, value, /)
 |      Return self<value.
 |  
 |  __mod__(self, value, /)
 |      Return self%value.
 |  
 |  __mul__(self, value, /)
 |      Return self*value.
 |  
 |  __ne__(self, value, /)
 |      Return self!=value.
 |  
 |  __neg__(self, /)
 |      -self
 |  
 |  __pos__(self, /)
 |      +self
 |  
 |  __pow__(self, value, mod=None, /)
 |      Return pow(self, value, mod).
 |  
 |  __radd__(self, value, /)
 |      Return value+self.
 |  
 |  __rdivmod__(self, value, /)
 |      Return divmod(value, self).
 |  
 |  __repr__(self, /)
 |      Return repr(self).
 |  
 |  __rfloordiv__(self, value, /)
 |      Return value//self.
 |  
 |  __rmod__(self, value, /)
 |      Return value%self.
 |  
 |  __rmul__(self, value, /)
 |      Return value*self.
 |  
 |  __rpow__(self, value, mod=None, /)
 |      Return pow(value, self, mod).
 |  
 |  __rsub__(self, value, /)
 |      Return value-self.
 |  
 |  __rtruediv__(self, value, /)
 |      Return value/self.
 |  
 |  __sub__(self, value, /)
 |      Return self-value.
 |  
 |  __truediv__(self, value, /)
 |      Return self/value.
 |  
 |  conjugate(...)
 |      complex.conjugate() -> complex
 |      
 |      Return the complex conjugate of its argument. (3-4j).conjugate() == 3+4j.
 |  
 |  ----------------------------------------------------------------------
 |  Static methods defined here:
 |  
 |  __new__(*args, **kwargs) from builtins.type
 |      Create and return a new object.  See help(type) for accurate signature.
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |  
 |  imag
 |      the imaginary part of a complex number
 |  
 |  real
 |      the real part of a complex number

返回值为 real + imag*1j 的复数,或将字符串或数字转换为复数。

  • 如果第一个形参是字符串,则它被解释为一个复数,并且函数调用时必须没有第二个形参。
  • 第二个形参不能是字符串。
  • 每个实参都可以是任意的数值类型(包括复数)。
  • 如果省略了 imag,则默认值为零,构造函数会像 int 和 float 一样进行数值转换。
  • 如果两个实参都省略,则返回 0j。
  • 当从字符串转换时,字符串在 + 或 - 的周围必须不能有空格。
type(complex)
type
complex('1')
(1+0j)
complex('1+2j')
(1+2j)
complex(1j, 2j)
(-2+1j)
complex(1j)
1j
complex()
0j
complex('1 + 2j')
---------------------------------------------------------------------------

ValueError                                Traceback (most recent call last)

<ipython-input-7-fd0fa4b53d7c> in <module>
----> 1 complex('1 + 2j')


ValueError: complex() arg is a malformed string

delattr() 删除对象属性

内置函数 delattr(),Python 官方文档描述如下:

help(delattr)
Help on built-in function delattr in module builtins:

delattr(obj, name, /)
    Deletes the named attribute from the given object.
    
    delattr(x, 'y') is equivalent to ``del x.y''

实参是一个对象和一个字符串。该字符串必须是对象的某个属性。如果对象允许,该函数将删除指定的属性。delattr(x, 'y') 等价于 del x.y

class A:
    y = 0

x = A
x.y
0
delattr(x,'y')
x.y
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-12-3552434a3e61> in <module>
----> 1 x.y


AttributeError: type object 'A' has no attribute 'y'

dict 创建字典

内置函数(类)dict,Python 官方文档描述如下:

help(dict)
Help on class dict in module builtins:

class dict(object)
 |  dict() -> new empty dictionary
 |  dict(mapping) -> new dictionary initialized from a mapping object's
 |      (key, value) pairs
 |  dict(iterable) -> new dictionary initialized as if via:
 |      d = {}
 |      for k, v in iterable:
 |          d[k] = v
 |  dict(**kwargs) -> new dictionary initialized with the name=value pairs
 |      in the keyword argument list.  For example:  dict(one=1, two=2)
 |  

创建一个新字典。参数说明:

  • 不传参数创建空字典;
  • 传递一个映射对象;
  • 传递一个可迭代对象;
  • 传递关键字参数。
type(dict)
type
dict()
{}
dict({'a':1})
{'a': 1}
d = zip('abc',[1,2,3])
dict(d)
{'a': 1, 'b': 2, 'c': 3}
dict([('a', 1), ('b', 2)])
{'a': 1, 'b': 2}
dict(a=1,b=2)
{'a': 1, 'b': 2}

dir() 返回对象属性列表

内置函数 dir(),Python 官方文档描述如下:

help(dir)
Help on built-in function dir in module builtins:

dir(...)
    dir([object]) -> list of strings
    
    If called without an argument, return the names in the current scope.
    Else, return an alphabetized list of names comprising (some of) the attributes
    of the given object, and of attributes reachable from it.
    If the object supplies a method named __dir__, it will be used; otherwise
    the default dir() logic is used and returns:
      for a module object: the module's attributes.
      for a class object:  its attributes, and recursively the attributes
        of its bases.
      for any other object: its attributes, its class's attributes, and
        recursively the attributes of its class's base classes.

如果没有实参,则返回当前作用域中的名称列表。如果有实参,它会尝试返回该对象的有效属性列表。

dir()
['In',
 'Out',
 '_',
 '__',
 '___',
 '__builtin__',
 '__builtins__',
 '__doc__',
 '__loader__',
 '__name__',
 '__package__',
 '__spec__',
 '_dh',
 '_i',
 '_i1',
 '_i2',
 '_ih',
 '_ii',
 '_iii',
 '_oh',
 'exit',
 'get_ipython',
 'quit']

如果对象有一个名为 __dir__() 的方法,那么该方法将被调用,并且必须返回一个属性列表。这允许实现自定义 dir() 来报告它的属性。

class A:
    def __dir__(self):
        return ['area', 'perimeter', 'location']
a = A()
dir(a)
['area', 'location', 'perimeter']

如果对象不提供 __dir__(),默认的 dir() 机制对不同类型的对象行为不同,它会试图返回最相关而不是最全的信息:

  • 如果对象是模块对象,则列表包含模块的属性名称;
  • 如果对象是类对象,则列表包含它们的属性名称,并且递归查找所有基类的属性;
  • 如果对象是实例对象,则列表包含实例的属性名称,它的类的属性名称,并且递归查找它的类的所有基类的属性。
import string
dir(string)
['Formatter',
 'Template',
 '_ChainMap',
 '_TemplateMetaclass',
 '__all__',
 '__builtins__',
 '__cached__',
 '__doc__',
 '__file__',
 '__loader__',
 '__name__',
 '__package__',
 '__spec__',
 '_re',
 '_string',
 'ascii_letters',
 'ascii_lowercase',
 'ascii_uppercase',
 'capwords',
 'digits',
 'hexdigits',
 'octdigits',
 'printable',
 'punctuation',
 'whitespace']
dir(int)
['__abs__',
 '__add__',
 '__and__',
 '__bool__',
 '__ceil__',
 '__class__',
 '__delattr__',
 '__dir__',
 '__divmod__',
 '__doc__',
 '__eq__',
 '__float__',
 '__floor__',
 '__floordiv__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__getnewargs__',
 '__gt__',
 '__hash__',
 '__index__',
 '__init__',
 '__init_subclass__',
 '__int__',
 '__invert__',
 '__le__',
 '__lshift__',
 '__lt__',
 '__mod__',
 '__mul__',
 '__ne__',
 '__neg__',
 '__new__',
 '__or__',
 '__pos__',
 '__pow__',
 '__radd__',
 '__rand__',
 '__rdivmod__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__rfloordiv__',
 '__rlshift__',
 '__rmod__',
 '__rmul__',
 '__ror__',
 '__round__',
 '__rpow__',
 '__rrshift__',
 '__rshift__',
 '__rsub__',
 '__rtruediv__',
 '__rxor__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__sub__',
 '__subclasshook__',
 '__truediv__',
 '__trunc__',
 '__xor__',
 'bit_length',
 'conjugate',
 'denominator',
 'from_bytes',
 'imag',
 'numerator',
 'real',
 'to_bytes']
dir(1)
['__abs__',
 '__add__',
 '__and__',
 '__bool__',
 '__ceil__',
 '__class__',
 '__delattr__',
 '__dir__',
 '__divmod__',
 '__doc__',
 '__eq__',
 '__float__',
 '__floor__',
 '__floordiv__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__getnewargs__',
 '__gt__',
 '__hash__',
 '__index__',
 '__init__',
 '__init_subclass__',
 '__int__',
 '__invert__',
 '__le__',
 '__lshift__',
 '__lt__',
 '__mod__',
 '__mul__',
 '__ne__',
 '__neg__',
 '__new__',
 '__or__',
 '__pos__',
 '__pow__',
 '__radd__',
 '__rand__',
 '__rdivmod__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__rfloordiv__',
 '__rlshift__',
 '__rmod__',
 '__rmul__',
 '__ror__',
 '__round__',
 '__rpow__',
 '__rrshift__',
 '__rshift__',
 '__rsub__',
 '__rtruediv__',
 '__rxor__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__sub__',
 '__subclasshook__',
 '__truediv__',
 '__trunc__',
 '__xor__',
 'bit_length',
 'conjugate',
 'denominator',
 'from_bytes',
 'imag',
 'numerator',
 'real',
 'to_bytes']

divmod() 求两个数的商和余

内置函数 divmod(),Python 官方文档描述如下:

help(divmod)
Help on built-in function divmod in module builtins:

divmod(x, y, /)
    Return the tuple (x//y, x%y).  Invariant: div*y + mod == x.

它将两个(非复数)数字作为实参,返回两个数字的(商,余数)元组。对于混合操作数类型,适用二元算术运算符的规则。

divmod(1,2)
(0, 1)
divmod(3.14, 2)
(1.0, 1.1400000000000001)

enumerate 枚举

内置函数(类)enumerate,Python 官方文档描述如下:

help(enumerate)
Help on class enumerate in module builtins:

class enumerate(object)
 |  enumerate(iterable, start=0)
 |  
 |  Return an enumerate object.
 |  
 |    iterable
 |      an object supporting iteration
 |  
 |  The enumerate object yields pairs containing a count (from start, which
 |  defaults to zero) and a value yielded by the iterable argument.
 |  
 |  enumerate is useful for obtaining an indexed list:
 |      (0, seq[0]), (1, seq[1]), (2, seq[2]), ...
 |  

返回一个可迭代对象(iterable)的枚举对象。枚举对象是一个迭代器,迭代出来是一个个元组,里面包含一个计数值(从 start 开始,默认为 0)和通过迭代 iterable 获得的值。

type(enumerate)
type
e = enumerate({'a':1,'b':2,'c':3})
e
<enumerate at 0x200ccb71240>
next(e)
(0, 'a')
for i in e:
    print(i)
(1, 'b')
(2, 'c')
e = enumerate('abc', 1)
list(e)
[(1, 'a'), (2, 'b'), (3, 'c')]

eval() 解析字符串或代码并求值

内置函数 eval(),Python 官方文档描述如下:

help(eval)
Help on built-in function eval in module builtins:

eval(source, globals=None, locals=None, /)
    Evaluate the given source in the context of globals and locals.
    
    The source may be a string representing a Python expression
    or a code object as returned by compile().
    The globals must be a dictionary and locals can be any mapping,
    defaulting to the current globals and locals.
    If only globals is given, locals defaults to it.

source 参数接受字符串(会作为一个 Python 表达式)或代码对象(可通过 compile() 创建),然后解析并求值,返回求值结果。

eval('{1 + 1}')
{2}
s = 'for i in range(3):print(i)'
code = compile(s,'','exec')
eval(code)
0
1
2
eval("__import__('math').sqrt(3**2+4**2)")
5.0

参数 globals 和 locals 作为 source 的全局和局部命名空间。如果省略 locals 字典则其默认值为 globals 字典。如果两个字典同时省略,则表达式执行时会使用 eval() 被调用的环境中的全局和局部名称。

如果 globals 字典存在且不包含以 __builtins__ 为键的值,则会在解析 source 之前插入以此为键的对内置模块 builtins 的引用。这意味着 source 通常具有对标准 builtins 模块的完全访问权限且受限的环境会被传播。

globals 实参必须是一个字典。locals 可以是任何映射对象。

x = 3
def f():
    y = 4
    code = "__import__('math').sqrt(x**2+y**2)"
    z = eval(code,{'x':5},{'y':12})
    print(f'x={x}, y={y}, z={z}')
f()
x=3, y=4, z=13.0
x = 3
def f():
    y = 4
    code = "__import__('math').sqrt(x**2+y**2)"
    z = eval(code,{'x':5,'y':12})
    print(f'x={x}, y={y}, z={z}')
f()
x=3, y=4, z=13.0
x = 3
def f():
    y = 4
    code = "__import__('math').sqrt(x**2+y**2)"
    z = eval(code)
    print(f'x={x}, y={y}, z={z}')
f()
x=3, y=4, z=5.0

exec() 解析字符串或代码并求值

内置函数 exec(),Python 官方文档描述如下:

help(exec)
Help on built-in function exec in module builtins:

exec(source, globals=None, locals=None, /)
    Execute the given source in the context of globals and locals.
    
    The source may be a string representing one or more Python statements
    or a code object as returned by compile().
    The globals must be a dictionary and locals can be any mapping,
    defaulting to the current globals and locals.
    If only globals is given, locals defaults to it.

这个函数支持动态执行 Python 代码。source 必须是字符串或者代码对象。

如果是字符串,那么该字符串将被解析为一系列 Python 语句并执行(除非发生语法错误)。

如果是代码对象,它将被直接执行。

该函数返回值为 None。

print(exec('{1 + 1}'))
None
s = 'for i in range(3):print(i)'
code = compile(s,'','exec')
exec(code)
0
1
2

参数 globals 和 locals 作为 source 的全局和局部命名空间。如果省略 locals 字典则其默认值为 globals 字典。如果两个字典同时省略,则表达式执行时会使用 eval() 被调用的环境中的全局和局部名称。

如果 globals 字典存在且不包含以 __builtins__ 为键的值,则将为该键插入对内建 builtins 模块字典的引用。因此,在将执行的代码传递给 exec() 之前,可以通过将自己的 __builtins__ 字典插入到 globals 中来控制可以使用哪些内置代码。

globals 实参必须是一个字典。locals 可以是任何映射对象。

x = 1
def f():
    x = 2
    code = "for i in range(x):print(i)"
    exec(code,{'x':3},{'x':4})
f()
0
1
2
3
x = 1
def f():
    x = 2
    code = "for i in range(x):print(i)"
    exec(code,{'x':3})
f()
0
1
2
x = 1
def f():
    x = 2
    code = "for i in range(x):print(i)"
    exec(code)
f()
0
1

filter 真值元素筛选

内置函数(类)filter,Python 官方文档描述如下:

help(filter)
Help on class filter in module builtins:

class filter(object)
 |  filter(function or None, iterable) --> filter object
 |  
 |  Return an iterator yielding those items of iterable for which function(item)
 |  is true. If function is None, return the items that are true.
 |  

返回可迭代对象(iterable)中那些传递给函数 function 计算之后,布尔值仍然为真的元素组成的迭代器。

如果 function 是 None,则会假设它是一个身份函数,即 iterable 中所有布尔值为假的元素会被移除。

type(filter)
type
f = filter(None,[0,1,2,1])
list(f)
[1, 2, 1]
f = filter(lambda x: x-1, [0,1,2,1])
list(f)
[0, 2]
list(filter(None,'0120'))
['0', '1', '2', '0']
list(filter(int,'0120'))
['1', '2']

float 创建浮点数

内置函数(类)float,Python 官方文档描述如下:

help(float)
Help on class float in module builtins:

class float(object)
 |  float(x=0, /)
 |  
 |  Convert a string or number to a floating point number, if possible.
 |  

返回从数字或字符串 x 生成的浮点数。

如果实参是字符串:

  • 它必须是包含十进制数字的字符串;
  • 通常是 Python 整数或浮点数的字符串形式;
  • 也可以是 ‘NaN’(非数字)、表示正负无穷大的字符串(“Infinity” 或 “inf”)。字母大小写随意;
  • 字符串前后可以有空白字符。

如果实参是整数或浮点数,则返回具有相同值(在 Python 浮点精度范围内)的浮点数。如果实参在 Python 浮点精度范围外,则会触发OverflowError。

如果没有实参,则返回 0.0 。

type(float)
type
float()
0.0
float(1)
1.0
float(-  1.0)
-1.0
float(' -1.0 \n')
-1.0
float(' 01_2.1_4 ')
12.14
float('0001')
1.0
float('3.14e02')
314.0
float('-naN')
nan
float('-inf')
-inf

format() 格式化

内置函数 format(),Python 官方文档描述如下:

help(format)
Help on built-in function format in module builtins:

format(value, format_spec='', /)
    Return value.__format__(format_spec)
    
    format_spec defaults to the empty string.
    See the Format Specification Mini-Language section of help('FORMATTING') for
    details.

将 value 转换为 format_spec 控制的 “格式化” 表示。format_spec 的解释取决于 value 实参的类型,但是大多数内置类型使用标准格式化语法:格式化迷你语言。详见 str.format 格式化。

默认的 format_spec 是一个空字符串,它通常和调用 str(value) 的结果相同。

format('嗨','>10')
'         嗨'
format('嗨','~^10')
'~~~~嗨~~~~~'
format(1,'05')
'00001'
format(3.14,'.3f')
'3.140'
format(123456789,'_')
'123_456_789'
format(123456789,'.2e')
'1.23e+08'
format(123456789)
'123456789'

frozenset 创建不可变集合

内置函数(类)frozenset,Python 官方文档描述如下:

help(frozenset)
Help on class frozenset in module builtins:

class frozenset(object)
 |  frozenset() -> empty frozenset object
 |  frozenset(iterable) -> frozenset object
 |  
 |  Build an immutable unordered collection of unique elements.
 |  

将可迭代对象转换为集合,返回一个新的 frozenset 集合对象。可迭代对象为空,或不传参数,得到一个空集合。

type(frozenset)
type
frozenset()
frozenset()
frozenset([])
frozenset()
frozenset('0123')
frozenset({'0', '1', '2', '3'})
frozenset({'a':1,'b':2})
frozenset({'a', 'b'})

getattr() 获取对象的属性

内置函数 getattr(),Python 官方文档描述如下:

help(getattr)
Help on built-in function getattr in module builtins:

getattr(...)
    getattr(object, name[, default]) -> value
    
    Get a named attribute from an object; getattr(x, 'y') is equivalent to x.y.
    When a default argument is given, it is returned when the attribute doesn't
    exist; without it, an exception is raised in that case.

返回对象给定的属性名指向的值。name 必须是字符串。如果该字符串是对象的属性名称之一,则返回该属性的值。例如,getattr(x, 'y') 等同于 x.y。如果指定的属性不存在,且提供了 default 值,则返回它,否则触发 AttributeError。

getattr(1,'imag')
0
getattr(1,'bool',True)
True
getattr(1,'bool')
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-6-524bb2b35e58> in <module>
----> 1 getattr(1,'bool')


AttributeError: 'int' object has no attribute 'bool'
class A:
    y = 1
x = A()
x.y
1
getattr(x,'y')
1

globals() 返回全局变量字典

内置函数 globals(),Python 官方文档描述如下:

help(globals)
Help on built-in function globals in module builtins:

globals()
    Return the dictionary containing the current scope's global variables.
    
    NOTE: Updates to this dictionary *will* affect name lookups in the current
    global scope and vice-versa.

返回包含当前作用域的全局变量字典。这总是当前模块的字典(在函数或方法中,不是调用它的模块,而是定义它的模块)。

更新此字典 影响当前全局范围内的名称查找,反之亦然。

globals() 和 locals() 函数各自返回当前的全局和本地字典,因此可以将它们传递给 eval() 或 exec() 来使用。

globals()
{'__name__': '__main__',
 '__doc__': 'Automatically created module for IPython interactive environment',
 '__package__': None,
 '__loader__': None,
 '__spec__': None,
 '__builtin__': <module 'builtins' (built-in)>,
 '__builtins__': <module 'builtins' (built-in)>,
 '_ih': ['', 'help(globals)', 'globals()'],
 '_oh': {},
 '_dh': ['D:\\Jupyter\\xuecn_books\\books\\xue_python_kp\\11_built-in_function'],
 'In': ['', 'help(globals)', 'globals()'],
 'Out': {},
 'get_ipython': <bound method InteractiveShell.get_ipython of <ipykernel.zmqshell.ZMQInteractiveShell object at 0x000001E15E70D748>>,
 'exit': <IPython.core.autocall.ZMQExitAutocall at 0x1e160f63978>,
 'quit': <IPython.core.autocall.ZMQExitAutocall at 0x1e160f63978>,
 '_': '',
 '__': '',
 '___': '',
 '_i': 'help(globals)',
 '_ii': '',
 '_iii': '',
 '_i1': 'help(globals)',
 '_i2': 'globals()'}

hasattr() 是对象的属性吗?

内置函数 hasattr(),Python 官方文档描述如下:

help(hasattr)
Help on built-in function hasattr in module builtins:

hasattr(obj, name, /)
    Return whether the object has an attribute with the given name.
    
    This is done by calling getattr(obj, name) and catching AttributeError.

该函数实参是一个对象和一个字符串。如果字符串是对象的属性之一的名称,则返回 True,否则返回 False。

hasattr('abc', 'join')
True
class A:
    y = 1

hasattr(A, 'y')
True

hash() 返回对象的哈希值

内置函数 hash(),Python 官方文档描述如下:

help(hash)
Help on built-in function hash in module builtins:

hash(obj, /)
    Return the hash value for the given object.
    
    Two objects that compare equal must also have the same hash value, but the
    reverse is not necessarily true.

返回对象的哈希值(如果它有的话)。哈希值是整数。它们在集合或字典查找元素时用来快速比较集合的元素或字典的键。相同大小的数字有相同的哈希值。

可哈希对象必须具有相同的哈希值比较结果才会相同。

hash(1) == hash(1.0) == hash(True)
True
1 == 1.0 == True
True
hash('abc')
2812132477407752679
hash((1,2,3))
529344067295497451
hash([1,2,3])
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-5-35e31e935e9e> in <module>
----> 1 hash([1,2,3])


TypeError: unhashable type: 'list'
hash((1,2))

help 启动帮助系统

内置函数(帮助系统)help,Python 官方文档描述如下:

help(help)
Help on _Helper in module _sitebuiltins object:

class _Helper(builtins.object)
 |  Define the builtin 'help'.
 |  
 |  This is a wrapper around pydoc.help that provides a helpful message
 |  when 'help' is typed at the Python interactive prompt.
 |  
 |  Calling help() at the Python prompt starts an interactive help session.
 |  Calling help(thing) prints help for the python object 'thing'.
 |  
 |  Methods defined here:
 |  
 |  __call__(self, *args, **kwds)
 |      Call self as a function.
 |  
 |  __repr__(self)
 |      Return repr(self).
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |  
 |  __dict__
 |      dictionary for instance variables (if defined)
 |  
 |  __weakref__
 |      list of weak references to the object (if defined)

启动内置的帮助系统(此函数主要在交互式中使用)。

如果没有实参,解释器控制台里会启动交互式帮助系统。

如果实参是一个字符串,则在模块、函数、类、方法、关键字或文档主题中搜索该字符串,并在控制台上打印帮助信息。

如果实参是其他任意对象,则会生成该对象的帮助页。

type(help)
_sitebuiltins._Helper
help()
Welcome to Python 3.8's help utility!

If this is your first time using Python, you should definitely check out
the tutorial on the Internet at https://docs.python.org/3.8/tutorial/.

Enter the name of any module, keyword, or topic to get help on writing
Python programs and using Python modules.  To quit this help utility and
return to the interpreter, just type "quit".

To get a list of available modules, keywords, symbols, or topics, type
"modules", "keywords", "symbols", or "topics".  Each module also comes
with a one-line summary of what it does; to list the modules whose name
or summary contain a given string such as "spam", type "modules spam".



help>  print


Help on built-in function print in module builtins:

print(...)
    print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False)
    
    Prints the values to a stream, or to sys.stdout by default.
    Optional keyword arguments:
    file:  a file-like object (stream); defaults to the current sys.stdout.
    sep:   string inserted between values, default a space.
    end:   string appended after the last value, default a newline.
    flush: whether to forcibly flush the stream.



help>  q



You are now leaving help and returning to the Python interpreter.
If you want to ask for help on a particular object directly from the
interpreter, you can type "help(object)".  Executing "help('string')"
has the same effect as typing a particular string at the help> prompt.
help('list.append')
Help on method_descriptor in list:

list.append = append(self, object, /)
    Append object to the end of the list.
help(list.append)
Help on method_descriptor:

append(self, object, /)
    Append object to the end of the list.

hex() 整数的十六进制形式

内置函数 hex(),Python 官方文档描述如下:

help(hex)
Help on built-in function hex in module builtins:

hex(number, /)
    Return the hexadecimal representation of an integer.
    
    >>> hex(12648430)
    '0xc0ffee'

将整数转换为以 0x 为前缀的小写十六进制整数的字符串形式。

hex(123)
'0x7b'
0x7b
123

id() 返回对象的唯一标识

内置函数 id(),Python 官方文档描述如下:

help(id)
Help on built-in function id in module builtins:

id(obj, /)
    Return the identity of an object.
    
    This is guaranteed to be unique among simultaneously existing objects.
    (CPython uses the object's memory address.)

返回对象的唯一标识。该标识是一个整数,在此对象的生命周期中保证是唯一且恒定的。

CPython 中该标识是对象的内存地址。

id(1), id(1.0)
(140736642126656, 2785998726512)
1 == 1.0
True
# 两个变量引用了同一个值为 1 的对象
a = 1
b = int('01')
id(a), id(b)
(140736642126656, 140736642126656)
# 两个值为 1000 的不同对象
a = 1000
b = 1000
id(a), id(b)
(2785998745552, 2785998745360)
# 可变对象改变值,还是同一个对象
_list = [1,2,3]
print(id(_list),_list)
del _list[:]
print(id(_list),_list)
2785999307336 [1, 2, 3]
2785999307336 []

input() 接受输入返回字符串

内置函数 input(),Python 官方文档描述如下:

help(input)
Help on method raw_input in module ipykernel.kernelbase:

raw_input(prompt='') method of ipykernel.ipkernel.IPythonKernel instance
    Forward raw_input to frontends
    
    Raises
    ------
    StdinNotImplentedError if active frontend doesn't support stdin.

如果存在 prompt 实参,则作为提示信息输出。接下来,该函数将输入转换为字符串并返回。无输入则返回空字符串。

input('输入提示:')
输入提示: 1+1


'1+1'
input('输入提示:')
输入提示: 


''

int 创建整数

内置函数(类)int,Python 官方文档描述如下:

help(int)
Help on class int in module builtins:

class int(object)
 |  int([x]) -> integer
 |  int(x, base=10) -> integer
 |  
 |  Convert a number or string to an integer, or return 0 if no arguments
 |  are given.  If x is a number, return x.__int__().  For floating point
 |  numbers, this truncates towards zero.
 |  
 |  If x is not a number or if base is given, then x must be a string,
 |  bytes, or bytearray instance representing an integer literal in the
 |  given base.  The literal can be preceded by '+' or '-' and be surrounded
 |  by whitespace.  The base defaults to 10.  Valid bases are 0 and 2-36.
 |  Base 0 means to interpret the base from the string as an integer literal.
 |  >>> int('0b100', base=0)
 |  4
 |  
 |  Built-in subclasses:
 |      bool
 |  

将一个数字,字符串或字节串转换为整数。参数说明:

  • 不给参数返回整数 0。
  • 参数 x 为数字时,不能有参数 base,且数字不能是复数。浮点数将取整。
  • 参数 x 为字符串或字节串,参数 base 可选,默认按十进制转换,否则按照 base 指定进制转换。
  • base 取值范围为 0 和 2~36。
  • base 取 0 将按照参数 x 的字面量来精确解释。取其他数字则需符合相应进制规则。
  • 字符串或字节串不能是浮点数形式;前面可以有正负号;前后可以有空格,中间则不能有空格。
type(int)
type
int()
0
int(3.18e01), int(10), int(0x10)
(31, 10, 16)
int(' -10 '), int(b' +10')
(-10, 10)
int('10',2), int('10',8), int('z',36)
(2, 8, 35)
int('001'), int('0b10',0)
(1, 2)
int('001',0) # 001 不是合法的整数
---------------------------------------------------------------------------

ValueError                                Traceback (most recent call last)

<ipython-input-12-1cf9048a8c3e> in <module>
----> 1 int('001',0)


ValueError: invalid literal for int() with base 0: '001'
int('9', 8) # 8 进制没有 9
---------------------------------------------------------------------------

ValueError                                Traceback (most recent call last)

<ipython-input-13-3558097bd025> in <module>
----> 1 int('9', 8)


ValueError: invalid literal for int() with base 8: '9'
int('3.14') # 不能是浮点数形式
---------------------------------------------------------------------------

ValueError                                Traceback (most recent call last)

<ipython-input-14-1456603af047> in <module>
----> 1 int('3.14')


ValueError: invalid literal for int() with base 10: '3.14'

isinstance() 是给定类的实例?

内置函数 isinstance(),Python 官方文档描述如下:

help(isinstance)
Help on built-in function isinstance in module builtins:

isinstance(obj, class_or_tuple, /)
    Return whether an object is an instance of a class or of a subclass thereof.
    
    A tuple, as in ``isinstance(x, (A, B, ...))``, may be given as the target to
    check against. This is equivalent to ``isinstance(x, A) or isinstance(x, B)
    or ...`` etc.

如果对象 obj 是给定类的实例或者是其 (直接、间接或虚拟) 子类的实例则返回 True,不是则返回 False。给定的不是类则引发 TypeError 异常。

给定类可以以元组形式传参,obj 是其中任何一个类型的实例就返回 True。

isinstance(1, int)
True
isinstance('abc', (float, complex))
False
# bool 是 int 的子类型,但不是实例
isinstance(bool, int)
False
# True 是 int 的子类的实例
isinstance(True, int)
True
# bool 的实例只有 True 和 False
isinstance(1, bool)
False
# 所有的对象都是 object 的实例
isinstance(object, object)
True
import random # 模块

class A:pass # 自定义类

isinstance(1, object),\
isinstance(int, object),\
isinstance(list, object),\
isinstance(random, object),\
isinstance(A, object)
(True, True, True, True, True)

issubclass() 是给定类的子类吗?

内置函数 issubclass(),Python 官方文档描述如下:

help(issubclass)
Help on built-in function issubclass in module builtins:

issubclass(cls, class_or_tuple, /)
    Return whether 'cls' is a derived from another class or is the same class.
    
    A tuple, as in ``issubclass(x, (A, B, ...))``, may be given as the target to
    check against. This is equivalent to ``issubclass(x, A) or issubclass(x, B)
    or ...`` etc.

如果类 cls 是给定类的 (直接、间接或虚拟) 子类则返回 True,不是则返回 False。给定的不是类则引发 TypeError 异常。

给定类可以以元组形式传参,cls 是其中任何一个类的子类就返回 True。

issubclass(1, int) # 1 不是类
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-2-257e7a8dbb04> in <module>
----> 1 issubclass(1, int)


TypeError: issubclass() arg 1 must be a class
issubclass(bool, int)
True
issubclass(bool, (set, str, list))
False
# 所有的类都是 object 的子类
class A:pass
issubclass(A, object),\
issubclass(str, object),\
issubclass(object, object)
(True, True, True)

iter() 转迭代器

内置函数 iter(),Python 官方文档描述如下:

help(iter)
Help on built-in function iter in module builtins:

iter(...)
    iter(iterable) -> iterator
    iter(callable, sentinel) -> iterator
    
    Get an iterator from an object.  In the first form, the argument must
    supply its own iterator, or be a sequence.
    In the second form, the callable is called until it returns the sentinel.

将一个可迭代对象(iterable)或可调用对象(callable)转换为一个迭代器。

当参数是可调用对象时,需要提供参数 sentinel,生成的迭代器,每次 迭代时都会不带实参地调用 callable,返回 sentinel 时则触 发 StopIteration。

a = iter('abcd')
a
<str_iterator at 0x1c7eea4f910>
next(a),next(a),next(a),next(a)
('a', 'b', 'c', 'd')
a = iter(int, 1)
for i in range(3):
    print(next(a))
0
0
0
a = iter(int, 0)
next(a)
---------------------------------------------------------------------------

StopIteration                             Traceback (most recent call last)

<ipython-input-21-694e44f6d78c> in <module>
      1 a = iter(int, 0)
----> 2 next(a)


StopIteration: 

len() 返回元素个数

内置函数 len(),Python 官方文档描述如下:

help(len)
Help on built-in function len in module builtins:

len(obj, /)
    Return the number of items in a container.

返回对象的长度(元素个数)。实参可以是序列(如 str、bytes、tuple、list 或 range 等的实例),集合(set 或 frozenset 的实例),或字典(dict 的实例)等。

len('123')
3
len('嗨')
1
len('嗨'.encode())
3
len([1,2,3])
3
len({'a':1,'b':2})
2

list 创建列表

内置函数(类)list,Python 官方文档描述如下:

help(list)
Help on class list in module builtins:

class list(object)
 |  list(iterable=(), /)
 |  
 |  Built-in mutable sequence.
 |  
 |  If no argument is given, the constructor creates a new empty list.
 |  The argument must be an iterable if specified.
 |  

将一个可迭代对象转为列表。不传参数将得到空列表。

type(list)
type
list()
[]
list('123')
['1', '2', '3']
list({'a':1,'b':2})
['a', 'b']

locals() 返回局部变量的字典

内置函数 locals(),Python 官方文档描述如下:

help(locals)
Help on built-in function locals in module builtins:

locals()
    Return a dictionary containing the current scope's local variables.
    
    NOTE: Whether or not updates to this dictionary will affect name lookups in
    the local scope and vice-versa is *implementation dependent* and not
    covered by any backwards compatibility guarantees.

返回包含当前作用域的局部变量的字典。在模块层级上,locals() 和 globals() 是同一个字典。

globals() 和 locals() 函数各自返回当前的全局和本地字典,因此可以将它们传递给 eval() 或 exec() 来使用。

locals()
{'__name__': '__main__',
 '__doc__': 'Automatically created module for IPython interactive environment',
 '__package__': None,
 '__loader__': None,
 '__spec__': None,
 '__builtin__': <module 'builtins' (built-in)>,
 '__builtins__': <module 'builtins' (built-in)>,
 '_ih': ['', 'help(locals)', 'locals()'],
 '_oh': {},
 '_dh': ['D:\\Jupyter\\xuecn_books\\books\\xue_python_kp\\11_built-in_function'],
 'In': ['', 'help(locals)', 'locals()'],
 'Out': {},
 'get_ipython': <bound method InteractiveShell.get_ipython of <ipykernel.zmqshell.ZMQInteractiveShell object at 0x0000023E24AE89B0>>,
 'exit': <IPython.core.autocall.ZMQExitAutocall at 0x23e27368898>,
 'quit': <IPython.core.autocall.ZMQExitAutocall at 0x23e27368898>,
 '_': '',
 '__': '',
 '___': '',
 '_i': 'help(locals)',
 '_ii': '',
 '_iii': '',
 '_i1': 'help(locals)',
 '_i2': 'locals()'}
def f():
    a = 1
    print(locals())
f()
{'a': 1}

map 以给定函数转换元素

内置函数(类)map,Python 官方文档描述如下:

help(map)
Help on class map in module builtins:

class map(object)
 |  map(func, *iterables) --> map object
 |  
 |  Make an iterator that computes the function using arguments from
 |  each of the iterables.  Stops when the shortest iterable is exhausted.
 |  
 |  Methods defined here:
 |  
 |  __getattribute__(self, name, /)
 |      Return getattr(self, name).
 |  
 |  __iter__(self, /)
 |      Implement iter(self).
 |  
 |  __next__(self, /)
 |      Implement next(self).
 |  
 |  __reduce__(...)
 |      Return state information for pickling.
 |  
 |  ----------------------------------------------------------------------
 |  Static methods defined here:
 |  
 |  __new__(*args, **kwargs) from builtins.type
 |      Create and return a new object.  See help(type) for accurate signature.

返回一个将函数 func 应用于 iterable 中每一项并输出其结果的迭代器。

如果传入了额外的 iterable 参数,func 必须接受相同个数的实参并被应用于从所有可迭代对象中并行获取的项。

当有多个可迭代对象时,最短的可迭代对象耗尽则整个迭代就将结束。

type(map)
type
a = map(int, '1234')
a
<map at 0x16048be3828>
list(a)
[1, 2, 3, 4]
m = map(int,'abc',(16,16))
list(m)
[10, 11]
def f(x,y):
    d = {}
    d[x] = y
    return d

m = map(f,'abc',(1,2))
list(m)
[{'a': 1}, {'b': 2}]

max() 求最大项

内置函数 max(),Python 官方文档描述如下:

help(max)
Help on built-in function max in module builtins:

max(...)
    max(iterable, *[, default=obj, key=func]) -> value
    max(arg1, arg2, *args, *[, key=func]) -> value
    
    With a single iterable argument, return its biggest item. The
    default keyword-only argument specifies an object to return if
    the provided iterable is empty.
    With two or more arguments, return the largest argument.

返回可迭代对象中最大的元素,或多个实参中最大的项。参数说明:

  • 如果只提供了一个位置参数,它必须是可迭代对象(iterable),返回 iterable 中最大的元素,iterable 为空,返回 default。
  • 如果提供了两个及以上的位置参数,则返回最大的位置参数。
  • 如果有多个最大元素,则此函数将返回第一个找到的。
  • 参数 key(可选)指定排序函数,将排序的项都经此函数计算,按计算值取最大的项。
max('3142')
'4'
max([], default=0)
0
max(2,4,3,4)
4
max([2,1],[2,1,1])
[2, 1, 1]
max(('a','ab','bcd'),key=len)
'bcd'

min() 求最小项

内置函数 min(),Python 官方文档描述如下:

help(min)
Help on built-in function min in module builtins:

min(...)
    min(iterable, *[, default=obj, key=func]) -> value
    min(arg1, arg2, *args, *[, key=func]) -> value
    
    With a single iterable argument, return its smallest item. The
    default keyword-only argument specifies an object to return if
    the provided iterable is empty.
    With two or more arguments, return the smallest argument.

返回可迭代对象中最小的元素,或多个实参中最小的项。参数说明:

  • 如果只提供了一个位置参数,它必须是可迭代对象(iterable),返回 iterable 中最小的元素,iterable 为空,返回 default。
  • 如果提供了两个及以上的位置参数,则返回最小的位置参数。
  • 如果有多个最小元素,则此函数将返回第一个找到的。
  • 参数 key(可选)指定排序函数,将排序的项都经此函数计算,按计算值取最小的项。
min('3142')
'1'
min([], default=0)
0
min(2,3,2,4)
2
min([2,1],[2,1,1])
[2, 1]
min(('a','ab','bcd'),key=len)
'a'

next() 返回迭代器下一个元素

内置函数 next(),Python 官方文档描述如下:

help(next)
Help on built-in function next in module builtins:

next(...)
    next(iterator[, default])
    
    Return the next item from the iterator. If default is given and the iterator
    is exhausted, it is returned instead of raising StopIteration.

返回迭代器(iterator)的下一个元素。如果迭代器耗尽,则返回给定的 default,如果没有默认值则触发 StopIteration。

i = iter('123')
next(i,'迭代结束')
'1'
next(i,'迭代结束')
'2'
next(i,'迭代结束')
'3'
next(i,'迭代结束')
'迭代结束'
next(i,'迭代结束')
'迭代结束'

object 所有类的基类

内置函数(类)object,Python 官方文档描述如下:

help(object)
Help on class object in module builtins:

class object
 |  The most base type

当被调用时,它不接受任何参数,并返回一个新的无特性实例,并且不能给定任何实例属性。

object 是所有类的基类。它具有所有 Python 类实例的通用方法。

type(object)
type
object()
<object at 0x1de8bc75170>
dir(object)
['__class__',
 '__delattr__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__gt__',
 '__hash__',
 '__init__',
 '__init_subclass__',
 '__le__',
 '__lt__',
 '__ne__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__']

oct() 整数的八进制形式

内置函数 oct(),Python 官方文档描述如下:

help(oct)
Help on built-in function oct in module builtins:

oct(number, /)
    Return the octal representation of an integer.
    
    >>> oct(342391)
    '0o1234567'

将一个整数转换为八进制整数的字符串形式。

oct(123)
'0o173'
0o173
123
oct(0x12)
'0o22'
0x12, 0o22
(18, 18)

open() 打开文件

内置函数 open(),Python 官方文档描述如下:

help(open)
Help on built-in function open in module io:

open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
    Open file and return a stream.  Raise OSError upon failure.
    
    file is either a text or byte string giving the name (and the path
    if the file isn't in the current working directory) of the file to
    be opened or an integer file descriptor of the file to be
    wrapped. (If a file descriptor is given, it is closed when the
    returned I/O object is closed, unless closefd is set to False.)
    
    mode is an optional string that specifies the mode in which the file
    is opened. It defaults to 'r' which means open for reading in text
    mode.  Other common values are 'w' for writing (truncating the file if
    it already exists), 'x' for creating and writing to a new file, and
    'a' for appending (which on some Unix systems, means that all writes
    append to the end of the file regardless of the current seek position).
    In text mode, if encoding is not specified the encoding used is platform
    dependent: locale.getpreferredencoding(False) is called to get the
    current locale encoding. (For reading and writing raw bytes use binary
    mode and leave encoding unspecified.) The available modes are:
    
    ========= ===============================================================
    Character Meaning
    --------- ---------------------------------------------------------------
    'r'       open for reading (default)
    'w'       open for writing, truncating the file first
    'x'       create a new file and open it for writing
    'a'       open for writing, appending to the end of the file if it exists
    'b'       binary mode
    't'       text mode (default)
    '+'       open a disk file for updating (reading and writing)
    'U'       universal newline mode (deprecated)
    ========= ===============================================================
    
    The default mode is 'rt' (open for reading text). For binary random
    access, the mode 'w+b' opens and truncates the file to 0 bytes, while
    'r+b' opens the file without truncation. The 'x' mode implies 'w' and
    raises an `FileExistsError` if the file already exists.
    
    Python distinguishes between files opened in binary and text modes,
    even when the underlying operating system doesn't. Files opened in
    binary mode (appending 'b' to the mode argument) return contents as
    bytes objects without any decoding. In text mode (the default, or when
    't' is appended to the mode argument), the contents of the file are
    returned as strings, the bytes having been first decoded using a
    platform-dependent encoding or using the specified encoding if given.
    
    'U' mode is deprecated and will raise an exception in future versions
    of Python.  It has no effect in Python 3.  Use newline to control
    universal newlines mode.
    
    buffering is an optional integer used to set the buffering policy.
    Pass 0 to switch buffering off (only allowed in binary mode), 1 to select
    line buffering (only usable in text mode), and an integer > 1 to indicate
    the size of a fixed-size chunk buffer.  When no buffering argument is
    given, the default buffering policy works as follows:
    
    * Binary files are buffered in fixed-size chunks; the size of the buffer
      is chosen using a heuristic trying to determine the underlying device's
      "block size" and falling back on `io.DEFAULT_BUFFER_SIZE`.
      On many systems, the buffer will typically be 4096 or 8192 bytes long.
    
    * "Interactive" text files (files for which isatty() returns True)
      use line buffering.  Other text files use the policy described above
      for binary files.
    
    encoding is the name of the encoding used to decode or encode the
    file. This should only be used in text mode. The default encoding is
    platform dependent, but any encoding supported by Python can be
    passed.  See the codecs module for the list of supported encodings.
    
    errors is an optional string that specifies how encoding errors are to
    be handled---this argument should not be used in binary mode. Pass
    'strict' to raise a ValueError exception if there is an encoding error
    (the default of None has the same effect), or pass 'ignore' to ignore
    errors. (Note that ignoring encoding errors can lead to data loss.)
    See the documentation for codecs.register or run 'help(codecs.Codec)'
    for a list of the permitted encoding error strings.
    
    newline controls how universal newlines works (it only applies to text
    mode). It can be None, '', '\n', '\r', and '\r\n'.  It works as
    follows:
    
    * On input, if newline is None, universal newlines mode is
      enabled. Lines in the input can end in '\n', '\r', or '\r\n', and
      these are translated into '\n' before being returned to the
      caller. If it is '', universal newline mode is enabled, but line
      endings are returned to the caller untranslated. If it has any of
      the other legal values, input lines are only terminated by the given
      string, and the line ending is returned to the caller untranslated.
    
    * On output, if newline is None, any '\n' characters written are
      translated to the system default line separator, os.linesep. If
      newline is '' or '\n', no translation takes place. If newline is any
      of the other legal values, any '\n' characters written are translated
      to the given string.
    
    If closefd is False, the underlying file descriptor will be kept open
    when the file is closed. This does not work when a file name is given
    and must be True in that case.
    
    A custom opener can be used by passing a callable as *opener*. The
    underlying file descriptor for the file object is then obtained by
    calling *opener* with (*file*, *flags*). *opener* must return an open
    file descriptor (passing os.open as *opener* results in functionality
    similar to passing None).
    
    open() returns a file object whose type depends on the mode, and
    through which the standard file operations such as reading and writing
    are performed. When open() is used to open a file in a text mode ('w',
    'r', 'wt', 'rt', etc.), it returns a TextIOWrapper. When used to open
    a file in a binary mode, the returned class varies: in read binary
    mode, it returns a BufferedReader; in write binary and append binary
    modes, it returns a BufferedWriter, and in read/write mode, it returns
    a BufferedRandom.
    
    It is also possible to use a string or bytearray as a file for both
    reading and writing. For strings StringIO can be used like a file
    opened in a text mode, and for bytes a BytesIO can be used like a file
    opened in a binary mode.

打开文件 file 并返回对应的文件对象(file object)。

参数说明:

1,file 是将要打开的文件的路径(绝对路径或者当前工作目录的相对路径),也可以是要被封装的整数类型文件描述符。(如果是文件描述符,它会随着返回的 I/O 对象关闭而关闭,除非 closefd 被设为 False )。

2,mode 是一个可选字符串,用于指定打开文件的模式。默认值是 ‘r’ ,这意味着它以文本模式打开并读取。在文本模式,如果 encoding 没有指定,则根据平台来决定使用的编码(要读取和写入原始字节,请使用二进制模式并不要指定 encoding)。可用的模式有:

  • ‘r’ 读取(默认)
  • ‘w’ 写入,并先截断文件(会删除原内容),文件不存在则创建
  • ‘x’ 排它性创建,如果文件已存在则失败
  • ‘a’ 写入,如果文件存在则在末尾追加,不存在则创建
  • ‘b’ 二进制模式
  • ’t’ 文本模式(默认)
  • ‘+’ 打开用于更新(读取与写入)

模式 ‘r’,‘w’,‘x’,‘a’ 可以单独使用,也可与 ‘b’ 或 ‘+’,或两者同时组合使用。‘b’,’t’ 和 ‘+’ 不能单独使用。‘b’ 和 ’t’ 互斥,’t’ 默认省略。

默认模式为 ‘r’ (打开用于读取文本,与 ‘rt’ 同义)。模式 ‘w+’ 与 ‘w+b’ 打开文件并清空内容。模式 ‘r+’ 与 ‘r+b’ 打开文件并不清空内容。

以二进制模式打开的文件返回的内容为字节串,不进行任何解码。在文本模式下打开时,文件内容返回为字符串,首先使用指定的 encoding (如果给定)或者使用平台默认的的字节编码解码。

3,buffering 是一个可选的整数,用于设置缓冲策略。

4,encoding 是用于解码或编码文件的编码的名称。这应该只在文本模式下使用。

5,errors 是一个可选的字符串参数,用于指定如何处理编码和解码错误。这不能在二进制模式下使用。

6,newline 控制通用换行模式如何生效(它仅适用于文本模式)。

7,如果 closefd 是 False 并且打开文件给出了文件描述符而不是文件名,那么当文件关闭时,底层文件描述符将保持打开状态。如果给出文件名则 closefd 必须为 True (默认值),否则将引发错误。

8,可以通过传递可调用的 opener 来使用自定义开启器。

with open('test.txt') as f:
    print(f)
<_io.TextIOWrapper name='test.txt' mode='r' encoding='cp936'>
with open('test.txt',encoding='utf-8') as f:
    print(f.read())
xue.cn

自学是门手艺
with open('test.txt','rb') as f:
    print(f.read())
b'xue.cn\r\n\r\n\xe8\x87\xaa\xe5\xad\xa6\xe6\x98\xaf\xe9\x97\xa8\xe6\x89\x8b\xe8\x89\xba'

ord() 返回单个字符 Unicode 码位值

内置函数 ord(),Python 官方文档描述如下:

help(ord)
Help on built-in function ord in module builtins:

ord(c, /)
    Return the Unicode code point for a one-character string.

返回单个字符 Unicode 码点的整数。这是 chr() 的逆函数。

ord('a')
97
chr(97)
'a'

pow() 幂运算并取余

内置函数 pow(),Python 官方文档描述如下:

help(pow)
Help on built-in function pow in module builtins:

pow(base, exp, mod=None)
    Equivalent to base**exp with 2 arguments or base**exp % mod with 3 arguments
    
    Some types, such as ints, are able to use a more efficient algorithm when
    invoked using the three argument form.

返回 base 的 exp 次幂;如果 mod 存在,则返回 base 的 exp 次幂对 mod 取余(比 pow(base, exp) % mod 更高效)。

对于混用的操作数类型,则将应用双目算术运算符的类型强制转换规则。 对于 int 操作数,结果具有与操作数相同的类型(强制转换后),除非第二个参数为负值;在这种情况下,所有参数将被转换为浮点数并输出浮点数结果。

对于 int 操作数 base 和 exp,如果给出 mod,则 mod 必须为整数类型并且 mod 必须不为零。如果给出 mod 并且 exp 为负值,则 base 必须相对于 mod 不可整除。在这种情况下,将会返回 pow(inv_base, -exp, mod),其中 inv_base 为 base 的倒数对 mod 取余。

在 3.8 版更改: 对于 int 操作数,三参数形式的 pow 现在允许第二个参数为负值,即可以计算倒数的余数;允许关键字参数。

pow(2, 3, 4)
0
pow(2.0, 3)
8.0
pow(2, -1)
0.5
pow(38, -1, 97) #  38 的倒数对 97 取余为 23
23
23 * 38 % 97 == 1
True
pow(38, -2, 97), pow(23, 2, 97)
(44, 44)

print()

内置函数 print(),Python 官方文档描述如下:

help(print)
Help on built-in function print in module builtins:

print(...)
    print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False)
    
    Prints the values to a stream, or to sys.stdout by default.
    Optional keyword arguments:
    file:  a file-like object (stream); defaults to the current sys.stdout.
    sep:   string inserted between values, default a space.
    end:   string appended after the last value, default a newline.
    flush: whether to forcibly flush the stream.

将 value … 打印到 file 指定的文本流,以 sep 分隔并在末尾加上 end。sep, end, file 和 flush 如果存在,它们必须以关键字参数的形式给出。

所有非关键字参数都会被转换为字符串,就像是执行了 str() 一样,并会被写入到流。sep 和 end 都必须为字符串。sep 默认为一个空格 ’ ‘,end 默认为换行 ‘\n’。

如果没有给出 value …,则 print() 将只打印 end。

file 参数必须是一个具有 write(string) 方法的对象。如果参数不指定,则将使用解释器用于标准输出的文件对象 sys.stdout。

输出是否被缓存通常决定于 file,但如果 flush 关键字参数为真值,流会被强制刷新。

该函数返回值为 None。

print(1+1)
print('a','b')
2
a b
print('a',1,int, sep='-', end='end')
a-1-<class 'int'>end
print(end='end')
end
p = print('end')
print(p)
end
None

property 返回 property 属性

内置函数(类)property,Python 官方文档描述如下:

help(property)
Help on class property in module builtins:

class property(object)
 |  property(fget=None, fset=None, fdel=None, doc=None)
 |  
 |  Property attribute.
 |  
 |    fget
 |      function to be used for getting an attribute value
 |    fset
 |      function to be used for setting an attribute value
 |    fdel
 |      function to be used for del'ing an attribute
 |    doc
 |      docstring
 |  
 |  Typical use is to define a managed attribute x:
 |  
 |  class C(object):
 |      def getx(self): return self._x
 |      def setx(self, value): self._x = value
 |      def delx(self): del self._x
 |      x = property(getx, setx, delx, "I'm the 'x' property.")
 |  
 |  Decorators make defining new properties or modifying existing ones easy:
 |  
 |  class C(object):
 |      @property
 |      def x(self):
 |          "I am the 'x' property."
 |          return self._x
 |      @x.setter
 |      def x(self, value):
 |          self._x = value
 |      @x.deleter
 |      def x(self):
 |          del self._x
 |  

返回 property 属性。

fget 是获取属性值的函数。fset 是用于设置属性值的函数。fdel 是用于删除属性值的函数,doc 为属性对象创建文档字符串。

type(property)
type
dir(property)
['__class__',
 '__delattr__',
 '__delete__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__get__',
 '__getattribute__',
 '__gt__',
 '__hash__',
 '__init__',
 '__init_subclass__',
 '__isabstractmethod__',
 '__le__',
 '__lt__',
 '__ne__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__set__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 'deleter',
 'fdel',
 'fget',
 'fset',
 'getter',
 'setter']

一个典型的用法是定义一个托管属性 x:

# 列一
class C:
    def __init__(self,value):
        self._x = value
    
    def getx(self): 
        return self._x
    
    def setx(self, value): 
        self._x = value
        
    def delx(self): 
        del self._x
        
    x = property(getx, setx, delx, "I'm the 'x' property.")

如果 c 是 C 的实例,c.x 将调用 getter,c.x = value 将调用 setter,del c.x 将调用 deleter。

如果给出,doc 将成为该 property 属性的文档字符串。否则该 property 将拷贝 fget 的文档字符串(如果存在)。

c = C(1)
c.x
1
c.x = 2
c.x
2
del c.x
c.x
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-16-d32ce31f0255> in <module>
      1 del c.x
----> 2 c.x


<ipython-input-10-eaaa1d84111b> in getx(self)
      4 
      5     def getx(self):
----> 6         return self._x
      7 
      8     def setx(self, value):


AttributeError: 'C' object has no attribute '_x'

这令使用 property() 作为装饰器来创建只读的特征属性可以很容易地实现:

class Parrot:
    def __init__(self):
        self._voltage = 100000
    @property
    def voltage(self):
        """Get the current voltage."""
        return self._voltage

以上 @property 装饰器会将 voltage() 方法转化为一个具有相同名称的只读属性的 getter,并将 voltage 的文档字符串设置为 ‘Get the current voltage.’

p = Parrot()
p.voltage
100000
p.voltage = 100
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-20-50ba7917c8b5> in <module>
----> 1 p.voltage = 100


AttributeError: can't set attribute

特征属性对象具有 getter, setter 以及 deleter 方法,它们可用作装饰器来创建该特征属性的副本,并将相应的访问函数设为所装饰的函数。

class C:
    def __init__(self,value):
        self._x = value
        
    @property
    def x(self):
        """I'm the 'x' property."""
        return self._x
    
    @x.setter
    def x(self, value):
        self._x = value
        
    @x.deleter
    def x(self):
        del self._x

上述代码与 例一 完全等价。注意一定要给附加函数与原始的特征属性相同的名称。

c = C(1)
c.x
1
c.x = 2
c.x
2
del c.x
c.x
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-7-d32ce31f0255> in <module>
      1 del c.x
----> 2 c.x


<ipython-input-4-356a299284e7> in x(self)
      6     def x(self):
      7         """I'm the 'x' property."""
----> 8         return self._x
      9 
     10     @x.setter


AttributeError: 'C' object has no attribute '_x'

range 创建 range 序列

内置函数(类)range,Python 官方文档描述如下:

help(range)
Help on class range in module builtins:

class range(object)
 |  range(stop) -> range object
 |  range(start, stop[, step]) -> range object
 |  
 |  Return an object that produces a sequence of integers from start (inclusive)
 |  to stop (exclusive) by step.  range(i, j) produces i, i+1, i+2, ..., j-1.
 |  start defaults to 0, and stop is omitted!  range(4) produces 0, 1, 2, 3.
 |  These are exactly the valid indices for a list of 4 elements.
 |  When step is given, it specifies the increment (or decrement).
 |  
 |  Methods defined here:
 |  
 |  __bool__(self, /)
 |      self != 0
 |  
 |  __contains__(self, key, /)
 |      Return key in self.
 |  
 |  __eq__(self, value, /)
 |      Return self==value.
 |  
 |  __ge__(self, value, /)
 |      Return self>=value.
 |  
 |  __getattribute__(self, name, /)
 |      Return getattr(self, name).
 |  
 |  __getitem__(self, key, /)
 |      Return self[key].
 |  
 |  __gt__(self, value, /)
 |      Return self>value.
 |  
 |  __hash__(self, /)
 |      Return hash(self).
 |  
 |  __iter__(self, /)
 |      Implement iter(self).
 |  
 |  __le__(self, value, /)
 |      Return self<=value.
 |  
 |  __len__(self, /)
 |      Return len(self).
 |  
 |  __lt__(self, value, /)
 |      Return self<value.
 |  
 |  __ne__(self, value, /)
 |      Return self!=value.
 |  
 |  __reduce__(...)
 |      Helper for pickle.
 |  
 |  __repr__(self, /)
 |      Return repr(self).
 |  
 |  __reversed__(...)
 |      Return a reverse iterator.
 |  
 |  count(...)
 |      rangeobject.count(value) -> integer -- return number of occurrences of value
 |  
 |  index(...)
 |      rangeobject.index(value, [start, [stop]]) -> integer -- return index of value.
 |      Raise ValueError if the value is not present.
 |  
 |  ----------------------------------------------------------------------
 |  Static methods defined here:
 |  
 |  __new__(*args, **kwargs) from builtins.type
 |      Create and return a new object.  See help(type) for accurate signature.
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |  
 |  start
 |  
 |  step
 |  
 |  stop

虽然被称为函数,但 range 实际上是一个不可变的序列类型,参见 range 对象

type(range)
type
list(range(3))
[0, 1, 2]
list(range(-5))
[]
list(range(1,5,2))
[1, 3]
list(range(0,-5,-1))
[0, -1, -2, -3, -4]

repr() 返回对象的可打印字符串

内置函数 repr(),Python 官方文档描述如下:

help(repr)
Help on built-in function repr in module builtins:

repr(obj, /)
    Return the canonical string representation of the object.
    
    For many object types, including most builtins, eval(repr(obj)) == obj.

返回包含一个对象的可打印表示形式的字符串。

对于许多类型来说,该函数会尝试返回的字符串将会与该对象被传递给 eval() 时所生成的对象具有相同的值,在其他情况下表示形式会是一个括在尖括号中的字符串,其中包含对象类型的名称与通常包括对象名称和地址的附加信息。

repr(1+1)
'2'
repr(int)
"<class 'int'>"
str('python\n')
'python\n'
repr('python\n')
"'python\\n'"

reversed 序列逆置

内置函数(类)reversed,Python 官方文档描述如下:

help(reversed)
Help on class reversed in module builtins:

class reversed(object)
 |  reversed(sequence, /)
 |  
 |  Return a reverse iterator over the values of the given sequence.
 |  
 |  Methods defined here:
 |  
 |  __getattribute__(self, name, /)
 |      Return getattr(self, name).
 |  
 |  __iter__(self, /)
 |      Implement iter(self).
 |  
 |  __length_hint__(...)
 |      Private method returning an estimate of len(list(it)).
 |  
 |  __next__(self, /)
 |      Implement next(self).
 |  
 |  __reduce__(...)
 |      Return state information for pickling.
 |  
 |  __setstate__(...)
 |      Set state information for unpickling.
 |  
 |  ----------------------------------------------------------------------
 |  Static methods defined here:
 |  
 |  __new__(*args, **kwargs) from builtins.type
 |      Create and return a new object.  See help(type) for accurate signature.

返回给定的序列逆置之后的迭代器。

type(reversed)
type
reversed('1234')
<reversed at 0x14b684fe460>
list(reversed('1234'))
['4', '3', '2', '1']

因为字典顺序会确保为插入顺序,字典和字典视图都是可逆的。3.8 新版可以返回一个逆序获取字典键的迭代器。

d = reversed({'a':1,'b':2,'c':3})
list(d)
['c', 'b', 'a']

round() 数字舍入

内置函数 round(),Python 官方文档描述如下:

help(round)
Help on built-in function round in module builtins:

round(number, ndigits=None)
    Round a number to a given precision in decimal digits.
    
    The return value is an integer if ndigits is omitted or None.  Otherwise
    the return value has the same type as the number.  ndigits may be negative.

返回 number 舍入到小数点后 ndigits 位精度的值。如果 ndigits 被省略或为 None,则返回最接近输入值的整数。

对于支持 round() 的内置类型,值会被舍入到最接近的 10 的负 ndigits 次幂的倍数;如果与两个倍数的距离相等,则选择偶数。

任何整数值都可作为有效的 ndigits (正数、零或负数)。如果 ndigits 被省略或为 None 则返回值将为整数。否则返回值与 number 的类型相同。

由于大多数十进制小数实际上都不能以浮点数精确地表示。返回值可能会不是预期的四舍五入结果。

round(3.14)
3
round(3.14, 3)
3.14
round(3.14, 0)
3.0
round(13.14,-1)
10.0
round(2.5), round(-2.5)
(2, -2)
round(1.5), round(-1.5)
(2, -2)
round(2.675, 2), round(2.665, 2)
(2.67, 2.67)

set 创建集合

内置函数(类)set,Python 官方文档描述如下:

help(set)
Help on class set in module builtins:

class set(object)
 |  set() -> new empty set object
 |  set(iterable) -> new set object
 |  
 |  Build an unordered collection of unique elements.
 |  

将一个可迭代对象转换为集合。可迭代对象为空,或不传参数,将得到空集合。

type(set)
type
set([])
set()
set()
set()
set('1231')
{'1', '2', '3'}
set({'1':1, '2':2, '3':3})
{'1', '2', '3'}

setattr() 设置或新增属性

内置函数 setattr(),Python 官方文档描述如下:

help(setattr)
Help on built-in function setattr in module builtins:

setattr(obj, name, value, /)
    Sets the named attribute on the given object to the specified value.
    
    setattr(x, 'y', v) is equivalent to ``x.y = v''

参数为一个对象、一个字符串和一个任意值。字符串指定一个现有属性或者新增属性。函数会将值赋给该属性,只要对象允许这种操作。

class A:
    y = 1
x = A()
x.y
1
# 为实例 x 新增属性
setattr(x, 'y', 10)
x.y
10
A.y
1
# 修改类 A 的 y 属性
setattr(A, 'y', 100)
A.y
100
x.y
10

slice 创建切片对象

内置函数(类)slice,Python 官方文档描述如下:

help(slice)
Help on class slice in module builtins:

class slice(object)
 |  slice(stop)
 |  slice(start, stop[, step])
 |  
 |  Create a slice object.  This is used for extended slicing (e.g. a[0:10:2]).
 |  

返回一个表示由 range(start, stop, step) 指定索引集的 slice 对象。其中 start 和 step 参数默认为 None。

切片对象具有仅会返回对应参数值(或其默认值)的只读数据属性 start, stop 和 step。它们没有其他的显式功能。

type(slice)
type
s = slice(3)
s
slice(None, 3, None)
a = list(range(5))
a[s]
[0, 1, 2]
slice(2,8,2)
slice(2, 8, 2)
list(range(10)[slice(2,8,2)])
[2, 4, 6]

sorted() 返回排序列表

内置函数 sorted(),Python 官方文档描述如下:

help(sorted)
Help on built-in function sorted in module builtins:

sorted(iterable, /, *, key=None, reverse=False)
    Return a new list containing all items from the iterable in ascending order.
    
    A custom key function can be supplied to customize the sort order, and the
    reverse flag can be set to request the result in descending order.

根据 iterable 中的项返回一个新的已排序列表。

具有两个可选参数,它们都必须指定为关键字参数。

key 指定带有单个参数的函数,应用于 iterable 中的每个元素,将计算结果用来对原 iterable 排序。默认值为 None (直接比较)。

reverse 为一个布尔值。如果设为 True,则每个列表元素将按反向顺序比较进行排序。

sorted() 排序确保是稳定的。如果一个排序确保不会改变比较结果相等的元素的相对顺序就称其为稳定的 — 这有利于进行多重排序。

sorted({'b':1,'a':3,'c':2})
['a', 'b', 'c']
d = [{'age':18},{'age':30},{'age':26}]
sorted(d,key=lambda x:x['age'])
[{'age': 18}, {'age': 26}, {'age': 30}]
sorted([1.5,'2.0','1.5',3.14], key=float)
[1.5, '1.5', '2.0', 3.14]
sorted('3123', reverse=True)
['3', '3', '2', '1']

staticmethod 封装函数为静态方法

内置函数(类)staticmethod,Python 官方文档描述如下:

help(staticmethod)
Help on class staticmethod in module builtins:

class staticmethod(object)
 |  staticmethod(function) -> method
 |  
 |  Convert a function to be a static method.
 |  
 |  A static method does not receive an implicit first argument.
 |  To declare a static method, use this idiom:
 |  
 |       class C:
 |           @staticmethod
 |           def f(arg1, arg2, ...):
 |               ...
 |  
 |  It can be called either on the class (e.g. C.f()) or on an instance
 |  (e.g. C().f()).  The instance is ignored except for its class.
 |  
 |  Static methods in Python are similar to those found in Java or C++.
 |  For a more advanced concept, see the classmethod builtin.
 |  

将函数转换为静态方法。

静态方法不会接收隐式的第一个参数。可以传递一个函数作为参数定义为静态方法,也可以使用装饰器的形式将一个自定义函数定义为静态方法。

type(staticmethod)
type
class A:
    in_print = staticmethod(print)
a = A()
a.in_print('静态方法')
静态方法
class A:
    @staticmethod
    def in_print(value):
        print(value)
a = A()
a.in_print('静态方法')
静态方法

str 创建字符串

内置函数(类)str,Python 官方文档描述如下:

help(str)
Help on class str in module builtins:

class str(object)
 |  str(object='') -> str
 |  str(bytes_or_buffer[, encoding[, errors]]) -> str
 |  
 |  Create a new string object from the given object. If encoding or
 |  errors is specified, then the object must expose a data buffer
 |  that will be decoded using the given encoding and error handler.
 |  Otherwise, returns the result of object.__str__() (if defined)
 |  or repr(object).
 |  encoding defaults to sys.getdefaultencoding().
 |  errors defaults to 'strict'.
 |  

返回对象 object 的字符串形式。如果未提供 object 则返回空字符串。

如果 encoding 或 errors 至少给出其中之一,则 bytes_or_buffer 应该是一个 bytes-like object (例如字节串或字节数组)。在此情况下,如果是一个字节串 (或字节数组) 对象,则 str(bytes, encoding, errors) 等价于 bytes.decode(encoding, errors)。否则的话,会在调用 bytes.decode() 之前获取缓冲区对象下层的 bytes 对象。

将一个 bytes 对象传入 str() 而不给出 encoding 或 errors 参数,将直接转 bytes 对象为字符串。

type(str)
type
str()
''
str(int)
"<class 'int'>"
str([1,2,3])
'[1, 2, 3]'
str(b'\xe8\x87\xaa\xe5\xad\xa6',encoding='utf-8')
'自学'
b'\xe8\x87\xaa\xe5\xad\xa6'.decode(encoding='utf-8')
'自学'
str(b'\xe8\x87\xaa\xe5\xad\xa6')
"b'\\xe8\\x87\\xaa\\xe5\\xad\\xa6'"

sum() 数字求和或序列拼接

内置函数 sum(),Python 官方文档描述如下:

help(sum)
Help on built-in function sum in module builtins:

sum(iterable, start=0, /)
    Return the sum of a 'start' value (default: 0) plus an iterable of numbers
    
    When the iterable is empty, return the start value.
    This function is intended specifically for use with numeric values and may
    reject non-numeric types.

通常对一个以数字为元素的可迭代对象求和并返回和。如果指定 start 参数,和需要加上 start。

start 不能为字符串,拼接字符串 sum() 不支持,更好更快的方式是 str.join() 方法。

sum() 还支持其他序列(列表和元组)。

sum([1,2,3])
6
sum(range(4), 10)
16
sum([(1,2),(3,4)], (5,))
(5, 1, 2, 3, 4)
sum([[1,2],[3]],[0])
[0, 1, 2, 3]

super 调用委托给父类或兄弟类

内置函数(类)super,Python 官方文档描述如下:

help(super)
Help on class super in module builtins:

class super(object)
 |  super() -> same as super(__class__, <first argument>)
 |  super(type) -> unbound super object
 |  super(type, obj) -> bound super object; requires isinstance(obj, type)
 |  super(type, type2) -> bound super object; requires issubclass(type2, type)
 |  Typical use to call a cooperative superclass method:
 |  class C(B):
 |      def meth(self, arg):
 |          super().meth(arg)
 |  This works for class methods too:
 |  class C(B):
 |      @classmethod
 |      def cmeth(cls, arg):
 |          super().cmeth(arg)
 |  

返回一个代理对象,它会将方法调用委托给类 type 的父类或兄弟类。这对于访问已在类中被重载的继承方法很有用。

  • super()super(__class__, <first argument>) 一样。
  • 如果省略第二个参数,则返回的超类对象是未绑定的。
  • 如果第二个参数为一个对象,则 isinstance(obj, type) 必须为真值。
  • 如果第二个参数为一个类型,则 issubclass(type2, type) 必须为真值(这适用于类方法)。

super 有两个典型用例:

  • 在具有单继承的类层级结构中,super 可用来引用父类而不必显式地指定它们的名称,从而令代码更易维护。
  • 第二个用例是在动态执行环境中支持协作多重继承。此用例为 Python 所独有。这使得实现 “菱形图” 成为可能,在这时会有多个基类实现相同的方法。好的设计强制要求这种方法在每个情况下具有相同的调用签名(因为调用顺序是在运行时确定的,也因为该顺序要适应类层级结构的更改,还因为该顺序可能包含在运行时之前未知的兄弟类)。

类或方法的 __mro__ 属性列出了 getattr() 和 super() 所共同使用的方法解析顺序(MRO)。该属性是动态的,可以在任何继承层级结构发生更新的时候被改变。

除了方法查找之外,super() 也可用于属性查找。一个可能的应用场合是在上级或同级类中调用描述器。

请注意 super() 是作为显式加点属性查找的绑定过程的一部分来实现的,例如 super().__getitem__(name)。它做到这一点是通过实现自己的 __getattribute__() 方法,这样就能以可预测的顺序搜索类,并且支持协作多重继承。

还要注意的是,除了零个参数的形式以外,super() 并不限于在方法内部使用。两个参数的形式明确指定参数并进行相应的引用。零个参数的形式仅适用于类定义内部,因为编译器需要填入必要的细节以正确地检索到被定义的类,还需要让普通方法访问当前实例。

方法调用委托给父类:

type(super)
type
class A:
    def add(self, x):
        y = x + x
        print(y)
            
class B(A):
    def add(self, x):
        super().add(x) # super() 等价于 super(B,self)
        
b = B()
b.add(5)
10

初始化委托给父类:

class A:
    def __init__(self):
        self.a = '父类A'
        print ('A')

    def print_msg(self,a):
        print (f'{a}来自A')

class B(A):
    def __init__(self):
        # super(B, self) 可写为 super()
        super(B, self).__init__() 
        print ('B')

    def print_msg(self,b):
        super().print_msg(b)
        print (f'{b}来自B')
        print (self.a)

b = B()
b.print_msg('HelloWorld')
A
B
HelloWorld来自A
HelloWorld来自B
父类A

tuple 创建元组

内置函数(类)tuple,Python 官方文档描述如下:

help(tuple)
Help on class tuple in module builtins:

class tuple(object)
 |  tuple(iterable=(), /)
 |  
 |  Built-in immutable sequence.
 |  
 |  If no argument is given, the constructor returns an empty tuple.
 |  If iterable is specified the tuple is initialized from iterable's items.
 |  
 |  If the argument is a tuple, the return value is the same object.
 |  

将可迭代对象转换为元组。可迭代对象为空或不传参数,返回空元组。

type(tuple)
type
tuple('')
()
tuple('123')
('1', '2', '3')

type 判断类型或创建类

内置函数(类)type,Python 官方文档描述如下:

help(type)
Help on class type in module builtins:

class type(object)
 |  type(object_or_name, bases, dict)
 |  type(object) -> the object's type
 |  type(name, bases, dict) -> a new type
 |  

传入一个参数时,返回对象的类型。推荐使用 isinstance() 内置函数来检测对象的类型,因为它会考虑子类的情况。

传入三个参数时,返回一个新的 type 对象。这在本质上是 class 语句的一种动态形式。name 参数是字符串即类名并且会成为 __name__ 属性;bases 元组列出基类并且会成为 __bases__ 属性;而 dict 字典为包含类主体定义的命名空间并且会被复制到一个标准字典成为 __dict__ 属性。

type(type)
type
type(int), type(object)
(type, type)
isinstance(int, object)
True
class A:
    a = 1
A.__name__, A.__bases__, A.__dict__
('A',
 (object,),
 mappingproxy({'__module__': '__main__',
               'a': 1,
               '__dict__': <attribute '__dict__' of 'A' objects>,
               '__weakref__': <attribute '__weakref__' of 'A' objects>,
               '__doc__': None}))
A = type('A', (object,), dict(a=1))
A.__name__, A.__bases__, A.__dict__
('A',
 (object,),
 mappingproxy({'a': 1,
               '__module__': '__main__',
               '__dict__': <attribute '__dict__' of 'A' objects>,
               '__weakref__': <attribute '__weakref__' of 'A' objects>,
               '__doc__': None}))

vars() 返回对象的变量字典

内置函数 vars(),Python 官方文档描述如下:

help(vars)
Help on built-in function vars in module builtins:

vars(...)
    vars([object]) -> dictionary
    
    Without arguments, equivalent to locals().
    With an argument, equivalent to object.__dict__.

返回模块、类、实例或任何其它具有 __dict__ 属性的对象的 __dict__ 属性。

模块和实例这样的对象具有可更新的 __dict__ 属性;但是,其它对象的 __dict__ 属性可能会设为限制写入(例如,类会使用types.MappingProxyType 来防止直接更新字典)。

不带参数时,vars() 的行为类似 locals()。请注意,locals 字典仅对于读取起作用,因为对 locals 字典的更新会被忽略。

如果指定了一个对象但它没有 __dict__ 属性则会引发TypeError 异常。

vars()
{'__name__': '__main__',
 '__doc__': 'Automatically created module for IPython interactive environment',
 '__package__': None,
 '__loader__': None,
 '__spec__': None,
 '__builtin__': <module 'builtins' (built-in)>,
 '__builtins__': <module 'builtins' (built-in)>,
 '_ih': ['', "get_ipython().run_line_magic('pinfo', 'vars')", 'vars()'],
 '_oh': {},
 '_dh': ['E:\\xue\\脚本\\kp_book\\11_built-in_function'],
 'In': ['', "get_ipython().run_line_magic('pinfo', 'vars')", 'vars()'],
 'Out': {},
 'get_ipython': <bound method InteractiveShell.get_ipython of <ipykernel.zmqshell.ZMQInteractiveShell object at 0x00000189A6512310>>,
 'exit': <IPython.core.autocall.ZMQExitAutocall at 0x189a65b32e0>,
 'quit': <IPython.core.autocall.ZMQExitAutocall at 0x189a65b32e0>,
 '_': '',
 '__': '',
 '___': '',
 '_i': 'vars?',
 '_ii': '',
 '_iii': '',
 '_i1': 'vars?',
 '_i2': 'vars()'}
def f():pass
vars(f)
{}
f.__dict__['a'] = 1
f.a
1
vars(1)
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-9-3391faf83557> in <module>
----> 1 vars(1)


TypeError: vars() argument must have __dict__ attribute

zip 重组可迭代对象

内置函数(类)zip,Python 官方文档描述如下:

help(zip)
Help on class zip in module builtins:

class zip(object)
 |  zip(iter1 [,iter2 [...]]) --> zip object
 |  
 |  Return a zip object whose .__next__() method returns a tuple where
 |  the i-th element comes from the i-th iterable argument.  The .__next__()
 |  method continues until the shortest iterable in the argument sequence
 |  is exhausted and then it raises StopIteration.
 |  
 |  Methods defined here:
 |  
 |  __getattribute__(self, name, /)
 |      Return getattr(self, name).
 |  
 |  __iter__(self, /)
 |      Implement iter(self).
 |  
 |  __next__(self, /)
 |      Implement next(self).
 |  
 |  __reduce__(...)
 |      Return state information for pickling.
 |  
 |  ----------------------------------------------------------------------
 |  Static methods defined here:
 |  
 |  __new__(*args, **kwargs) from builtins.type
 |      Create and return a new object.  See help(type) for accurate signature.

创建一个聚合了来自每个可迭代对象中的元素的迭代器。

返回一个元组的迭代器,其中的第 i 个元组包含来自每个可迭代对象的第 i 个元素。

当所输入可迭代对象中最短的一个被耗尽时,迭代器将停止迭代。

当只有一个可迭代对象参数时,它将返回一个单元组的迭代器。

不带参数时,它将返回一个空迭代器。

type(zip)
type
zip()
<zip at 0x2ac600edac8>
list(zip('123'))
[('1',), ('2',), ('3',)]
list(zip('123',{3,2,1}))
[('1', 1), ('2', 2), ('3', 3)]
list(zip([3,2,1],(1,2,3,4),'12'))
[(3, 1, '1'), (2, 2, '2')]
list(zip(*['abc','123']))
[('a', '1'), ('b', '2'), ('c', '3')]

函数概述

函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段。是可以向调用者返回某个值(至少是 None)的一组语句。

函数使代码的组织模块化,提供了代码的利用率。例如 print() 函数实现打印功能,input() 函数实现输入功能,需要它们的地方,都可以使用。

函数通过函数名称来使用它,传入零个或多个参数,并在函数体执行中被使用。

# 函数 abs() 返回一个数的绝对值
a = abs(-3.14)
a
3.14
# print() 函数打印传入的参数值,返回 None
n = print('hello, world')
n is None
hello, world


True

按照上述定义,类也被称作函数(例如内置函数 int,list 等)。

我们通常所说的函数,是指使用 def 语句和 lambda 表达式定义的函数。

而在类内部定义,使用属性表示法来调用的函数,我们习惯称作方法。

# int 类实例化返回整数对象
int('1')
1
isinstance(1, int)
True
# list 类的方法 append 将一个对象加入列表中,返回值为 None
a = []
b = list.append(a,'123')
a, b is None
(['123'], True)

函数有内置函数和内置方法,用户也可以自定义函数和方法。定义了一个函数,即创建了一个函数对象,可以通过函数名和必要的参数调用它。

def my_sum(lst):
    '将列表中的字符串数字转为数字求和' # 函数文档说明
    
    rlt = sum(map(float,lst)) # 调用内置函数求值
    return rlt # 返回结果

my_sum(['1','3.14',2]) # 调用自定义函数
6.140000000000001

函数定义

函数定义有两种方式,def 语句定义有名字的函数(详见 def 定义函数),lambda 表达式定义匿名函数(详见 lambda 函数)。

定义一个函数,即是创建了一个函数可执行代码的包装器,他将函数想要实现的功能包装起来。然后通过调用它来实现其功能。def 语句 详细介绍了定义函数的语法规则,下面看看如何将一个功能包装起来。

例如下列函数,实现了将列表中的 字符串整数 以及 整数 相加求和的功能,以后只要遇到这种情况,都可以用它来求和:

sumpro([1,'2','3'])
6
# def 定义
def sumpro(lst):
    return sum(int(i) for i in lst)
sumpro(['1 ',' 2',3])
6
# lambda 表达式定义
lambda lst: sum(int(i) for i in lst)
<function __main__.<lambda>(lst)>
lst = [['2','8'],['3','4']]
sorted(lst,key=lambda lst: sum(int(i) for i in lst))
[['3', '4'], ['2', '8']]

函数定义,也可以定义为实现功能,但没有返回值(默认返回 None)的过程。

例如下列函数,实现了将列表中的字符串整数都转换为整数:

def convert_to_numb(lst):
    for i in range(len(lst)):
        if type(lst[i]) != int:
            lst[i] = int(lst[i])
lst = ['1',2,'3']
n = convert_to_numb(lst)
lst, n is None
([1, 2, 3], True)

函数定义所使用的函数名称,不能与当前作用域中以定义的名称相同,这会屏蔽掉已存在的名称,或将自定义的函数对象重新赋值给了该名称。

str(123)
'123'
def str(x):
    return f'x={x}'
str(123) # 屏蔽掉了内置名称 str
'x=123'
f = 0
def f():
    pass
f # f 被重新赋值
<function __main__.f()>

函数形参

形参是函数定义中指定的参数名称。指定某个参数的形式,决定了该形参在函数调用时,可以接受实参的方式。关于实参详见 函数调用

因而形参分为五种:

  • 位置或关键字:指定一个可以作为 位置参数 传入也可以作为 关键字参数 传入的实参。这是默认的形参类型,但有默认值的形参必须置于无默认值的形参之后。
def f(a,b=None):
    print(f'a={a},b={b}')
# 位置实参传入
f(1,2)
# 关键字实参传入
f(b=2,a=1)
a=1,b=2
a=1,b=2
  • 仅限位置:指定一个只能通过位置传入的参数。仅限位置形参通过在函数定义的形参之后包含一个 / 字符来定义。/ 之前的参数为仅限位置形参,之后的形参为默认形参类型。有默认值的形参也必须置于无默认值的形参之后。
def f(a,b=None,/,c=None): # 因为 b 有默认值,c 必须要有默认值
    print(f'a={a},b={b},c={c}')
# 按位置传参调用
f(1,2,c=3)
# 关键字传参则不允许
f(a=1,b=2)
a=1,b=2,c=3


---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-6-52b3afdaad4c> in <module>
      4 f(1,2,c=3)
      5 # 关键字传参则不允许
----> 6 f(a=1,b=2)


TypeError: f() got some positional-only arguments passed as keyword arguments: 'a, b'
  • 仅限关键字:指定一个只能通过关键字传入的参数。仅限关键字形参可通过在函数定义的形参中包含单个 可变位置形参 或者在形参之前放一个 * 来定义。可变位置形参 或 * 之后的参数为仅限关键字形参。
def f(*a,b=None,c=None): # b 和 c 必须有默认值
    print(f'a={a},b={b},c={c}')
# 位置传参将被解读为可变位置参数
f(1,2,3)
# 关键字传参
f(1,b=2,c=3)
a=(1, 2, 3),b=None,c=None
a=(1,),b=2,c=3
def f(*,a,b=None,c):
    print(f'a={a},b={b},c={c}')
# 关键字传参
f(b=2,a=1,c=3)
# 位置传参不允许
f(1,2,3)
a=1,b=2,c=3


---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-1-9d584fa28622> in <module>
      4 f(b=2,a=1,c=3)
      5 # 位置传参不允许
----> 6 f(1,2,3)


TypeError: f() takes 0 positional arguments but 3 were given
  • 可变位置:指定一个可以接受任意数量的位置参数传入的参数。这种形参可通过在形参名称前加缀 * 来定义,并将接受到的参数封装成一个元组。该参数如果接受到了实参,它前面的参数必须为仅限位置参数。
def f(a,b=None,*c):
    print(f'a={a},b={b},c={c}')
# c 没有接受参数
f(1); f(b=2,a=1)
# c 接受到了参数
f(1,2,3,4,5)
a=1,b=None,c=()
a=1,b=2,c=()
a=1,b=2,c=(3, 4, 5)
  • 可变关键字:指定一个可以接受任意数量的关键字参数的参数。这种形参可通过在形参名称前加缀 ** 来定义,并将接受到的参数封装成一个字典。
def f(a,b=None,**c):
    print(f'a={a},b={b},c={c}')

f(1,2,c=3,d=4)
f(d=4,b=2,a=1,c=3)
a=1,b=2,c={'c': 3, 'd': 4}
a=1,b=2,c={'d': 4, 'c': 3}

带默认值的参数,可变位置参数和可变关键字参数,调用函数时可以不传参。

def f(a=1,*b,c=3,**d):
    print(f'a={a},b={b},c={c},d={d}')
f()
a=1,b=(),c=3,d={}

默认值只会执行一次,这条规则很重要。如果参数有默认值且为可变对象,则需要做必要的限制:

def f(a=[]):
    print(id(a))
    a.append(1)
    print(a)
f() # 多次调用会引用参数指向的同一个对象
f()
2399568560064
[1]
2399568560064
[1, 1]
# 可以拷贝一个副本
def f(a=[]):
    b = a.copy()
    b.append(1)
    print(b)
f()
f()
[1]
[1]
# 或者修改参数
def f(a=None):
    if a == None:
        a = []
        a.append(1)
    else:
        a.append(1)
    print(a)
    
f()
f([])
[1]
[1]

函数的形参可以使用标注,标注的语法是参数后面接一个冒号 :,然后接一个表达式(任意表达式),通常用来指明应该(不是必须)传递什么类型的参数等。标注提高了代码的可读性:

# a 标注为字符串类型,b 标注为整数,并设置默认值 2
def f(a:str,b:int=2):
    return a*b
f('Hi',3)
'HiHiHi'
def f(a:'字符串',b:'整数'=2):
    return a*b
f('Hi')
'HiHi'

函数返回值

函数返回值通过 return 语句 来实现,调用函数时,返回 return 语句之后表达式的值,没有 return 语句或 return 语句之后为空的函数,调用函数默认返回 None。

def f(x):
    return x**2

f(2)
4
def f():
    pass

f() is None
True

函数执行到 return 语句,则结束当前函数的调用,可以通过条件判断,返回特定结果:

def f(x=None):
    if x == None:
        return 0 # 使用默认值调用函数,接下来的代码将不被执行
    print(f'x={x}') 
    if x != None:
        return f'x²={x**2}'
    
f()
0
f(2)
x=2


'x²=4'

return 之后的表达式可以是多个表达式用逗号隔开(其实是一个元组),可用赋值语句分别接收返回值:

def f(x):
    return sum(x), max(x), min(x)
f([1,2,3,4])
(10, 4, 1)
sum_x, max_x, min_x = f([1,2,3,4])
sum_x, max_x, min_x
(10, 4, 1)

返回值可以是任何值。如果 return 之后的表达式中包含函数自身的调用,则该函数称为递归函数。详见 递归函数

# 返回函数自身
def f(x):
    print(x)
    return f

f(1)(2)(3)
1
2
3


<function __main__.f(x)>
# 返回函数自身的调用
def f(x):
    if x == 0:
        return 0
    else:
        print(x-1)
        return f(x-1)
    
f(3)
2
1
0


0

递归函数

函数 return 语句的表达式中包含函数自身的调用,则称该函数为递归函数。

递归函数必须设定退出条件,并且调用过程能够逐步达到退出条件,否则将引发 RecursionError。

例如定义一个计算阶乘的函数:

上述函数的退出条件是 x == 0,并且 return 语句中函数调用的参数是 x-1,第一层调用函数的参数 x 为 5,return 语句进入第二层调用,就变成了 x-1 为 4,依此类推,最终以 0 调用函数,达到了退出条件,但是值还没有最终返回,以退出条件下的返回值层层向上返回,最终得到结果。详情查看 递归函数

def f(x):
    if x == 0:
        return 1
    else:
        return f(x-1)*x
f(5)
120

函数文档

关键字 def 引入一个函数定义。构成函数体的语句从下一行开始,并且必须缩进。

函数体的第一个语句可以是字符串文字(可选的),这个字符串文字即是函数的文档字符串。有些工具使用文档字符串自动生成在线或印刷文档,或者让用户以交互式的形式浏览代码。在你编写的代码中包含文档字符串是一种很好的做法,所以要养成习惯。

文档字符串的内容和格式的约定:

  • 第一行应该是函数目的的简要概述。为简洁起见,它不应显式声明对象的名称或类型,因为这些可通过其他方式获得(除非名称恰好是描述函数操作的动词)。这一行应以大写字母开头,以句点结尾。

  • 如果文档字符串中有更多行,则第二行应为空白,从而在视觉上将摘要与其余描述分开。后面几行应该是一个或多个段落,描述对象的调用约定,它的副作用等。

可以使用函数的 __doc__ 属性或 help() 函数查看函数文档。

help(my_function)
Help on function my_function in module __main__:

my_function()
    Do nothing, but document it.
def my_function():
    'Do nothing, but document it.'
    pass

my_function.__doc__
'Do nothing, but document it.'
# 多行的函数文档
def my_func():
    """Do nothing, but document it.

    No, really, it doesn't do anything.
    """
    pass
print(my_func.__doc__)
Do nothing, but document it.

    No, really, it doesn't do anything.
help(my_func)
Help on function my_func in module __main__:

my_func()
    Do nothing, but document it.
    
    No, really, it doesn't do anything.
print(print.__doc__)
print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False)

Prints the values to a stream, or to sys.stdout by default.
Optional keyword arguments:
file:  a file-like object (stream); defaults to the current sys.stdout.
sep:   string inserted between values, default a space.
end:   string appended after the last value, default a newline.
flush: whether to forcibly flush the stream.

函数调用

函数名之后带一个圆括号,圆括号内,根据形参的类型(详见函数形参),给函数传递相应的实参,即可调用函数,执行函数体中的代码。

函数调用传递的实参分为:位置参数关键字参数

  • 位置参数: 调用函数时,不带标识符(例如 name=)直接按形参位置传递给函数的参数。位置参数可使用 * 将可迭代对象的元素拆包传入函数,但元素个数不能多于可接收位置参数的形参个数,除非有接收多个位置参数的可变位置形参。
def f(a,b=None,*c,d=None):
    print(f'a={a},b={b},c={c},d={d}')
    
f(*[1,2,3,4,5]) # d 只能接收关键字参数
a=1,b=2,c=(3, 4, 5),d=None
def f(a,b=None,*,c=None):
    print(f'a={a},b={b},c={c}')
    
f(1,2)
f(1,2,3) # c 只能接收关键字参数
a=1,b=2,c=None


---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-2-9cc318bcfef7> in <module>
      2     print(f'a={a},b={b},c={c}')
      3 f(1,2)
----> 4 f(1,2,3)


TypeError: f() takes from 1 to 2 positional arguments but 3 were given
  • 关键字参数: 函数调用中前面带有标识符(例如 name=)传递给函数的参数。关键字参数可以使用 ** 将字典里的元素传入函数,但元素个数不能多于可接收关键字参数的形参个数。关键字参数的标识符或字典的键,必须与可接收关键字参数的形参的名称相同,除非有可接收任意关键字参数的可变关键字形参。
def f(a,/,b=None,*,c=None):
    print(f'a={a},b={b},c={c}')
    
f(1,c=3,b=2) # a 仅限位置传参
f(a=1,c=3,b=2)
a=1,b=2,c=3


---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-7-3c39aaeac558> in <module>
      3 
      4 f(1,c=3,b=2) # a 仅限位置传参
----> 5 f(a=1,c=3,b=2)


TypeError: f() got some positional-only arguments passed as keyword arguments: 'a'
def f(a,/,b=None,**c):
    print(f'a={a},b={b},c={c}')

# a 仅限位置,b 没有对应的名称,因此全部传给 c
f(1,**{'a':1,'d':4,'c':3})
a=1,b=None,c={'a': 1, 'd': 4, 'c': 3}

位置参数必须在关键字参数前面:

def f(a,b=None,**c):
    print(f'a={a},b={b},c={c}')

f(b=2,1)
  File "<ipython-input-13-59f3b615f9f3>", line 4
    f(b=2,1)
          ^
SyntaxError: positional argument follows keyword argument

位置参数的位置不能传错,关键字参数的则可以任意位置:

def f(a,b=None,/,c=None,**d):
    print(f'a={a},b={b},c={c},d={d}')

f(1,2,c=3,d=4)
f(2,1,d=4,c=3,e=5)
a=1,b=2,c=3,d={'d': 4}
a=2,b=1,c=3,d={'d': 4, 'e': 5}

标注并不影响传参规则,但按照标注传参是更明智的做法:

def f(a:int,b:str='b')-> str:
    print(a*b)
    
f(2,'hi')
f(2,3)
hihi
6

lambda 函数

lambda 函数由 lambda 表达式创建,表达式的语法为:

 lambda parameters: expression

形参 parameters 是可选的,表达式 expression 会在函数调用时被求值并作为返回值返回。

表达式必须显示地确定为一个表达式,而不能像 return 语句那样返回多个表达式(默认为一个元组)。

lambda 表达式会创建一个没有名字的函数,函数不能包含语句或标注,可以像调用函数一样直接调用它。

(lambda x,y: x**y)(2,3)
8
lambda x,y: x**y
<function __main__.<lambda>(x, y)>
# 返回值必须显示地确定为一个表达式
f = lambda x: sum(x),max(x),min(x)
f([2,3,4])
---------------------------------------------------------------------------

NameError                                 Traceback (most recent call last)

<ipython-input-5-1652333b2bc4> in <module>
      1 # 必须显示地确定为一个表达式
----> 2 f = lambda x: sum(x),max(x),min(x)
      3 f([2,3,4])


NameError: name 'x' is not defined
f = lambda x: (sum(x),max(x),min(x))
f([2,3,4])
(9, 4, 2)

lambda 函数通常在以函数作为参数的高阶函数中使用,没有名称,用完即弃。

a = ['2  2', '2 1 ','2  3']
sorted(a,key=lambda x:''.join(x.split()))
['2 1 ', '2  2', '2  3']
list(map(lambda x:''.join(x.split()), a))
['22', '21', '23']

生成器函数

函数定义中使用了 yield 语句,该定义创建的函数是生成器函数。生成器函数通常也直接叫生成器。

当一个生成器函数被调用的时候,它返回一个迭代器,也称为生成器(全称是生成器迭代器,下面所说生成器均指生成器迭代器)。然后通过这个生成器来控制生成器函数的执行。

生成器是一个迭代器,也是一个可迭代对象。但一个生成器生成的 “元素” 只能被使用一次,原因如下:

  • 迭代生成器的时候,生成器函数开始执行,执行到 yield,然后执行被挂起,给生成器的调用者返回 yield 之后的表达式的值。挂起后,所有局部状态都被保留下来,包括局部变量的当前绑定,指令指针,内部求值栈和任何异常处理的状态。

  • 继续迭代生成器,生成器函数从挂起状态继续执行,执行到 yield,然后执行又被挂起,给生成器的调用者返回 yield 之后的表达式的值。

  • 生成器迭代完成时,引发 StopIteration。

在一个生成器函数中,return 语句表示生成器已完成并将导致 StopIteration 被引发。返回值(如果有的话)会被当作一个参数用来构建 StopIteration 并成为 StopIteration.value 属性。

next(g)
0
def f(n):
    yield n
g = f(0)
print(f)
print(g)
<function f at 0x000002480120ACA0>
<generator object f at 0x000002480122E040>
next(g) # 迭代结束
---------------------------------------------------------------------------

StopIteration                             Traceback (most recent call last)

<ipython-input-3-c91425ed1388> in <module>
----> 1 next(g) # 迭代结束


StopIteration: 
def f(n):
    yield n
    n += 1
    yield n
    n += 1
    yield n
    # 生成器已完成,后面的不被执行
    return 'end'
    n += 1
    yield n
    
g = f(0)

while True:
    try:
        print(next(g))
    except StopIteration as s:
        print(s.value) # StopIteration.value 属性
        break
# 迭代结束,不能再次迭代生成器
next(g)
0
1
2
end


---------------------------------------------------------------------------

StopIteration                             Traceback (most recent call last)

<ipython-input-10-3ebb4e469fc1> in <module>
     19         break
     20 # 迭代结束,不能再次迭代生成器
---> 21 next(g)


StopIteration: 

yield from 将可迭代对象中的每一项作为生成器的迭代项:

def f(*args):
    yield from args

g = f(1,2,3)
next(g),list(g)
(1, [2, 3])
def f(arg):
    yield from arg

g = f('123')
print(list(g))
# g 使用结束,再次使用什么也没有,创建了一个空列表
print(list(g))
# 如要再次使用可再创建一个生成器
list(f('123'))
['1', '2', '3']
[]


['1', '2', '3']

错误和异常

异常中断代码块的正常控制流程以便处理 错误其他异常条件 。它是 Python 中的一类对象。一个异常表示一个或一类错误。

异常会在错误被检测到的位置引发,它可以被 当前包围发生错误的代码块 或是 任何直接或间接调用发生错误的代码块的其他代码块 所处理。

Python 解析器会在检测到代码运行错误的时候引发异常。也可以通过 raise 语句显式地引发异常。

异常处理通过 try 语句 来指定。该语句的 finally 子句可被用来指定清理代码,它并不处理异常,而是无论之前的代码是否发生异常都会被执行。

Python 的错误处理采用的是 “终止” 模型:异常处理器可以找出发生了什么问题,并在外层继续执行,但它不能修复错误的根源并重试失败的操作(除非通过从顶层重新进入出错的代码片段)。

当一个异常完全未被处理时,解释器会终止程序的执行,或者返回交互模式。无论是哪种情况,它都会打印栈回溯信息,除非是当异常为 SystemExit 的时候。

异常是通过类实例来标识的。except 子句必须引用实例的类或是其所属的基类。实例可通过处理器被接收,并可携带有关异常条件的附加信息。

在 Python 中,所有异常必须为一个派生自 BaseException 的类的实例。

内置异常类可以被子类化以定义新的异常。鼓励从 Exception 类或它的某个子类而不是从 BaseException 来派生新的异常。

内置异常的类层级结构如下:

BaseException
+-- SystemExit
+-- KeyboardInterrupt
+-- GeneratorExit
+-- Exception
      +-- StopIteration
      +-- StopAsyncIteration
      +-- ArithmeticError
      | +-- FloatingPointError
      | +-- OverflowError
      | +-- ZeroDivisionError
      +-- AssertionError
      +-- AttributeError
      +-- BufferError
      +-- EOFError
      +-- ImportError
      | +-- ModuleNotFoundError
      +-- LookupError
      | +-- IndexError
      | +-- KeyError
      +-- MemoryError
      +-- NameError
      | +-- UnboundLocalError
      +-- OSError
      | +-- BlockingIOError
      | +-- ChildProcessError
      | +-- ConnectionError
      | | +-- BrokenPipeError
      | | +-- ConnectionAbortedError
      | | +-- ConnectionRefusedError
      | | +-- ConnectionResetError
      | +-- FileExistsError
      | +-- FileNotFoundError
      | +-- InterruptedError
      | +-- IsADirectoryError
      | +-- NotADirectoryError
      | +-- PermissionError
      | +-- ProcessLookupError
      | +-- TimeoutError
      +-- ReferenceError
      +-- RuntimeError
      | +-- NotImplementedError
      | +-- RecursionError
      +-- SyntaxError
      | +-- IndentationError
      | +-- TabError
      +-- SystemError
      +-- TypeError
      +-- ValueError
      | +-- UnicodeError
      | +-- UnicodeDecodeError
      | +-- UnicodeEncodeError
      | +-- UnicodeTranslateError
      +-- Warning
           +-- DeprecationWarning
           +-- PendingDeprecationWarning
           +-- RuntimeWarning
           +-- SyntaxWarning
           +-- UserWarning
           +-- FutureWarning
           +-- ImportWarning
           +-- UnicodeWarning
           +-- BytesWarning
           +-- ResourceWarning

处理异常

异常处理通过 try 语句(详细语法及使用规则见 try 语句)来实现。

通过 try 语句处理程序已经出现或可能出现异常,使代码能够继续执行,否则异常未被处理,程序终止执行。

例如下列代码要求用户输入有效的整数:

while True:
    try:
        x = int(input("Please enter a number: "))
        break
    except ValueError:
        print("Oops! That was no valid number. Try again...")
Please enter a number:  3.14


Oops! That was no valid number. Try again...


Please enter a number:  314

try 语句的工作原理如下:

  • 首先执行 try 子句;

  • 如果没有异常发生,则跳过 except 子句并完成 try 语句的执行;

  • 如果在执行 try 子句时发生了异常,则跳过该子句中剩下的部分。然后,如果异常的类型和 except 关键字后面的异常匹配,则执行 except 子句,然后继续执行 try 语句之后的代码;

  • 如果发生的异常和 except 子句中指定的异常不匹配,则将其传递到外部的 try 语句中;如果没有找到处理程序,则它是一个未处理异常,执行将停止并显示异常信息。

一个 try 语句可能有多个 except 子句,以指定不同异常的处理程序,但最多会执行一个处理程序。

处理程序只处理相应的 try 子句中发生的异常。如果发生的异常和 except 子句中的类是同一个类或者是它的基类,则异常和 except 子句中的类是兼容的。如果首先处理了基类,子类不再被处理(子类也被基类处理了):

class B(Exception):
    pass

class C(B):
    pass

class D(C):
    pass

for cls in [B, C, D]:
    try:
        raise cls()
    except D:
        print("D")
    except C:
        print("C")
    except B:
        print("B")
B
C
D
class B(Exception):
    pass

class C(B):
    pass

class D(C):
    pass

for cls in [B, C, D]:
    try:
        raise cls()
    except B: # 下列都是 B 的子类,不再被处理
        print("B")
    except C:
        print("C")
    except D:
        print("D")
B
B
B

最后的 except 子句可以省略异常名,以用作通配符。但请谨慎使用,因为以这种方式很容易掩盖真正的编程错误!它还可用于打印错误消息,然后重新引发异常:

import sys

try:
    3/0
except ValueError as v:
    print(v)
except:
    print("Unexpected error:", sys.exc_info()[0])
    raise
Unexpected error: <class 'ZeroDivisionError'>


---------------------------------------------------------------------------

ZeroDivisionError                         Traceback (most recent call last)

<ipython-input-13-f0b3e840c5ab> in <module>
      2 
      3 try:
----> 4     3/0
      5 except ValueError as v:
      6     print(v)


ZeroDivisionError: division by zero

可选的 else 子句,在使用时必须放在所有的 except 子句后面。对于在 try 子句不引发异常时必须执行的代码来说很有用:

try:
    f = open('../11_built-in_function/test.txt',
             'r',encoding='utf-8')
except OSError:
    print('cannot open')
else:
    print(len(f.readlines()), 'lines')
    f.close()
3 lines

异常处理程序不仅处理 try 子句中遇到的异常,还处理 try 子句中调用(即使是间接地)的函数内部发生的异常。

如果 try 子句中有多个可能的异常,只处理最先引发的:

def f():
    return 3/0

try:
    f() * int('a')
except ValueError as v:
    print(v)
except  ZeroDivisionError as z:
    print(z)
division by zero

抛出异常

raise 语句 允许强制发生指定的异常。

raise NameError('HiThere')
---------------------------------------------------------------------------

NameError                                 Traceback (most recent call last)

<ipython-input-1-72c183edb298> in <module>
----> 1 raise NameError('HiThere')


NameError: HiThere

如果你需要确定是否引发了异常但不打算处理它,则可以使用更简单的 raise 语句形式重新引发异常:

try:
    raise NameError('HiThere')
except NameError:
    print('An exception flew by!')
    raise
An exception flew by!


---------------------------------------------------------------------------

NameError                                 Traceback (most recent call last)

<ipython-input-2-bf6ef4926f8c> in <module>
      1 try:
----> 2     raise NameError('HiThere')
      3 except NameError:
      4     print('An exception flew by!')
      5     raise


NameError: HiThere

自定义异常

可以通过创建新的异常类来自定义代码执行错误引发的异常。许多标准模块定义了它们自己的异常,以报告它们定义的函数中可能出现的错误。

异常通常应该直接或间接地从 Exception 类派生。定义的异常类,可以执行任何其他类可以执行的任何操作,但通常保持简单,只提供一些属性,这 些属性允许处理程序为异常提取有关错误的信息。

在创建可能引发多个不同错误的模块时,通常的做法是为该模块定义的异常创建基类,并为不同错误条件创建特定异常类的子类。大多数异常都定义为名称以 Error 结尾,类似于标准异常的命名:

class Error(Exception):
    """Base class for exceptions in this module."""
    pass

class InputError(Error):
    """Exception raised for errors in the input.
    
    Attributes:
        expression -- input expression in which the error occurred
        message -- explanation of the error
    """
    
    def __init__(self, expression, message):
        self.expression = expression
        self.message = message
    
class TransitionError(Error):
    """Raised when an operation attempts a state transition that's not allowed.
    
    Attributes:
        previous -- state at beginning of transition
        next -- attempted new state
        message -- explanation of why the specific transition is not allowed
    """
    
    def __init__(self, previous, next, message):
        self.previous = previous
        self.next = next
        self.message = message
        
raise InputError(3/1,'分母不能为 1')
---------------------------------------------------------------------------

InputError                                Traceback (most recent call last)

<ipython-input-2-d0c6938bc1c4> in <module>
     29         self.message = message
     30 
---> 31 raise InputError(3/1,'分母不能为 1')


InputError: (3.0, '分母不能为 1')

finally 清理操作

try 语句有另一个可选子句 finally,用于定义必须在所有情况下执行的清理操作(详见 try 语句)。

例如,在 finally 子句中关闭打开的文件:

def read_file():
    try:
        f = open('../11_built-in_function/test.txt',
                encoding='utf-8')
        return f.read()
    except OSError:
        print('不能打开')
    finally:
        print('执行清理操作')
        f.close()
        
read_file() # 先执行关闭,在执行返回
执行清理操作


'xue.cn\n\n自学是门手艺'

如果 finally 子句中引发了新的异常,清理操作本身无效,则达不到清理目的:

def read_file():
    try:
        f = open('../11_built-in_function/test.txt',
                encoding='utf-8')
        return f.read()
    except OSError:
        print('不能打开')
    finally:
        print(执行清理操作) # 清理操作引发异常
        f.close()
        
read_file() 
---------------------------------------------------------------------------

NameError                                 Traceback (most recent call last)

<ipython-input-13-af7cdb6d88f0> in <module>
     10         f.close()
     11 
---> 12 read_file() # 先执行关闭,在执行返回


<ipython-input-13-af7cdb6d88f0> in read_file()
      7         print('不能打开')
      8     finally:
----> 9         print(执行清理操作) # 清理操作引发异常
     10         f.close()
     11 


NameError: name '执行清理操作' is not defined

某些对象(例如文件对象)定义了在不再需要该对象时,要执行的标准清理操作,无论使用该对象的操作是成功还是失败,清理操作都会被执行。此时使用 with 语句允许像文件这样的对象能够以一种确保它们得到及时和正确的清理的方式使用。

with 语句相当于将引发异常情况下的清理操作放到了 except 子句中,正常情况下的清理操作放到了 finally 子句中。详情见 with 语句

def read_file():
    with open('../11_built-in_function/test.txt',
                encoding='utf-8') as f:
        return f.read()
    
read_file()
'xue.cn\n\n自学是门手艺'

文件对象概述

文件对象是指对外提供面向文件 API 以使用下层资源的对象(带有 read() 或 write() 这样的方法),也被称作文件类对象

有三种类别的文件对象: 原始二进制文件, 缓冲二进制文件 以及 文本文件。三种类别下还有子类别,因创建方式的不同得到不同类别文件对象。它们的接口定义均在 io 模块中。创建文件对象的规范方式是使用 open() 函数

用户代码直接操作原始流的用法非常罕见。不过,可以通过在禁用缓冲的情况下以二进制模式打开文件来创建原始流:

f = open("test.txt","rb", buffering=0)
f
<_io.FileIO name='test.txt' mode='rb' closefd=True>

缓冲二进制流不执行编码、解码或换行转换。这种类型的流可以用于所有类型的非文本数据(例如图片,视频),并且还可以在需要手动控制文本数据的处理时使用。创建缓冲二进制流的最简单方法是使用 open(),并在模式中指定 ‘b’:

f = open("test.txt","rb")
f
<_io.BufferedReader name='test.txt'>

文本流生成 str 对象。这意味着,无论何时后台存储是由字节组成的,数据的编码和解码都是透明的,并且可以选择转换特定于平台的换行符。创建文本流的最简单方法是使用 open(),可以选择指定编码:

f = open("test.txt",encoding="utf-8")
f
<_io.TextIOWrapper name='test.txt' mode='r' encoding='utf-8'>

所有流对提供给它们的数据类型都很敏感。例如将 str 对象给二进制流的 write() 方法会引发 TypeError。

f = open("test.txt","ab")
f.write('写入内容')
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-19-a9efd264aca2> in <module>
      1 f = open("test.txt","ab")
----> 2 f.write('写入内容')


TypeError: a bytes-like object is required, not 'str'

内存中的流也可以作为文件对象使用:

import io
f = io.StringIO("some initial text data")
print(f)
f.read()
<_io.StringIO object at 0x000001BE348D2040>


'some initial text data'
f = io.BytesIO(b"some initial binary data: \x00\x01")
print(f)
f.read()
<_io.BytesIO object at 0x000001BE348D3310>


b'some initial binary data: \x00\x01'

open() 函数打开文件,创建流后,会使文件在一段不确定的时间内处于打开状态。这在简单脚本中不是问题,但对于较大的应用程序来说可能是个问题。此时用 close() 方法刷新并关闭流(或直接使用 with 语句打开)是明智的做法。

f = open("test.txt",encoding="utf-8")
print(f.read())
f.close()
为什么要掌握自学能力?
未来还很长。
jupyter附件

读取文件内容

打开方式不同,会创建不同类型的文件对象(流),不同类型的文件对象,可能有不同的属性或方法。

下列检查或读取内容的属性或方法,是多数文件对象通用的:

  • readable() 如果可以读取流,则返回 True 。否则为 False ,且 read() 将引发 OSError 错误。
f = open('test.txt')
f.readable()
True
  • read(size=-1) 从对象中读取 size 个字节并将其返回。作为一个便捷选项,如果 size 未指定或为 -1,则返回所有字节直到 EOF。流的位置与读取内容同步,不重设流的位置,内容只能被读取一次。

  • tell() 返回当前流的位置。

f = open('test.txt',encoding='utf-8')
f.read()
'为什么一定要掌握自学能力?\n未来的日子还很长,\n这世界进步得太快,\n没有自学能力,\n没有未来。'
f = open('test.txt',encoding='utf-8')
f.read(12)
'为什么一定要掌握自学能力'
f.tell()
36
f.read() # 再次读取,将读取剩下未读部分
'?\n未来的日子还很长,\n这世界进步得太快,\n没有自学能力,\n没有未来。'
f.read() # 已经没有内容
''
  • seek(offset, whence=0) 将流位置修改到给定的字节 offset。返回新的绝对位置。
f.seek(0, 0)
0
f.read() # 从新位置读取
'为什么一定要掌握自学能力?\n未来的日子还很长,\n这世界进步得太快,\n没有自学能力,\n没有未来。'
  • readline(size=-1) 从流中读取并返回一行。如果指定了 size,将至多读取 size 个字节。
f = open('test.txt',encoding='utf-8')
f.readline()
'为什么一定要掌握自学能力?\n'
f.readline(2) # 再次读取,将读取剩下未读部分
'未来'
  • readlines(hint=-1) 从流中读取并返回包含多行的列表。可以指定 hint 来控制要读取的行数。指定行数可以多于实际。
f = open('test.txt',encoding='utf-8')
f.readlines(1)
['为什么一定要掌握自学能力?\n']
f.readlines(10) # 再次读取,将读取剩下未读部分
['未来的日子还很长,\n', '这世界进步得太快,\n']
  • 使用 for line in file: ... 就足够对文件对象进行迭代了,可以不必调用 file.readlines()。
f = open('test.txt',encoding='utf-8')
for line in f:
    print(line)
为什么一定要掌握自学能力?

未来的日子还很长,

这世界进步得太快,

没有自学能力,

没有未来。
  • closed 如果流已关闭,则返回 True。
f.closed
False
  • close() 刷新并关闭此流。无论读写操作,最后都应该关闭流。如果文件已经关闭,则此方法无效。文件关闭后,对文件的任何操作(例如读取或写入)都会引发 ValueError 。为方便起见,允许多次调用此方法。但是,只有第一个调用才会生效。
f.close()
f.close()
f.closed
True

文件写入内容

打开方式不同,会创建不同类型的文件对象(流),不同类型的文件对象,可能有不同的属性或方法。

打开模式不同,写入内容的方式也会不同,详见 open() 打开文件

下列检查或写入内容的属性或方法,是多数文件对象通用的:

  • writable() 如果流支持写入则返回 True。如为 False,则 write() 将引发 OSError。
f = open('test.txt','a',encoding='utf-8')
f.writable()
True
f = open('test.txt')
f.writable()
f.write('这世界进步得太快,')
---------------------------------------------------------------------------

UnsupportedOperation                      Traceback (most recent call last)

<ipython-input-1-7bf449cf8958> in <module>
      1 f = open('test.txt')
      2 f.writable()
----> 3 f.write('这世界进步得太快,')


UnsupportedOperation: not writable
  • write() 将字符串或字节串写入到流并返回写入的字符或字节数。对于阻塞流(需要刷新才能将内容写入文件),写入内容在缓冲区(打开文件看不到写入的内容)。
f = open('test.txt','a+',encoding='utf-8')
f.write('\n这世界进步得太快,')
10
  • flush() 刷新流的写入缓冲区(打开文件将看到写入的内容)。这对只读和非阻塞流不起作用。
f.flush()
  • writelines(lines) 将行列表写入到流。不会添加行分隔符,因此通常所提供的每一行都带有末尾行分隔符。对于阻塞流,写入内容在缓冲区。
f = open('test.txt','a+',encoding='utf-8')
f.writelines(['\n没有自学能力,\n', '没有未来。'])
f.tell() # 流的位置在末尾
137
f.read() # 从末尾读取内容为空
''
f.seek(0, 0) # 重设流的位置为开头
0
f.read()
'为什么一定要掌握自学能力?\n未来的日子还很长,\n这世界进步得太快,\n没有自学能力,\n没有未来。'
  • closed 如果流已关闭,则返回 True。
f.closed
False
  • close() 刷新并关闭此流。无论读写操作,最后都应该关闭流。如果文件已经关闭,则此方法无效。文件关闭后,对文件的任何操作(例如读取或写入)都会引发 ValueError 。为方便起见,允许多次调用此方法。但是,只有第一个调用才会生效。
f.close()
f.close()
f.closed
True

模块概述

模块是 Python 代码的一种组织单位,也是一种对象。各模块具有独立的命名空间,可包含任意 Python 对象。

一个 .py 文件是一个模块;一个文件夹是一个模块(包);文件夹(包)中还可以再有 .py 文件(子模块)和文件夹(子包)。例如 内置模块 random,和第三方包 pandas。

文件夹中包含一个 __init__.py 文件的包是常规包;无 __init__.py 文件的是命名空间包,仅被用作子包的容器。

模块无论是用 Python、C 还是别的语言实现均可。

import random, pandas
type(random), type(pandas)
(module, module)

所有包都是模块,但并非所有模块都是包。或者换句话说,包只是一种特殊的模块。

可以使用属性 __packge__ 查看包名,如果只是模块不是包,该属性为空字符串。

__name__ 属性是模块的名字。

特别地,主模块(你正在运行代码的当前模块)的 __packge__ 属性总是 None;__name__ 属性总是 '__main__'__main__ 是一个在解释器启动时直接初始化的特殊模块),这可以控制当前模块能够执行,而导入到其他模块不能被执行的代码,然后用来测试当前模块。

random.__package__, pandas.__package__
('', 'pandas')
print(__package__)
None
random.__name__, pandas.__name__
('random', 'pandas')
__name__
'__main__'
a = 3 + 2 - 5
def f():
    print(a+1)
print(a)

if __name__ == '__main__':
    # 以下代码导入其他模块不会执行
    print(a == 0)
0
True

使用 import 语句将其他模块导入当前模块;使用属性表示法调用模块中的属性。

import pandas as pd
pd.core
<module 'pandas.core' from 'C:\\ProgramData\\Anaconda3\\lib\\site-packages\\pandas\\core\\__init__.py'>
pd.core.series.Series
pandas.core.series.Series

还可以以脚本的方式执行不属于包的模块(此时 __name__ 属性为 "__main__")。

import this
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!


<module 'this' from 'C:\\ProgramData\\Anaconda3\\lib\\this.py'>
# %run 是 jupyter 的魔法命令,在终端使用 python 命令
# F:\anaconda\lib\this.py 根据自己电脑的路径调整
%run F:\anaconda\lib\this.py
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!
jupyter附件

创建模块

创建一个模块非常简单,有两种方式:

  • 创建一个 .py 文件,即可创建一个模块;

  • 创建一个文件夹,则该文件夹也是一个模块(包)。

模块中可以无任何内容。但模块是用来组织代码,实现处理各类问题或完成各种功能的,这更便于应用或开发。例如正则模块 re,科学计算库 pandas 等。

创建一个文件夹,如果文件夹中包含一个 __init__.py 模块,则该文件夹是一个常规包;否则是一个命名空间包。包中还可以再创建子包或子模块。

__init__.py 文件中可以无任何内容,但因为当一个常规包被导入时,这个 __init__.py 文件会隐式地被执行,所以通常用来写入一些导入包即可执行的代码,或导入子包,或导入子包中模块的属性等,从而可以直接调用某些属性,例如 pandas 包的文档描述属性 __doc__ 和 DataFrame 数据结构。

import folder # 创建的空文件夹
folder
<module 'folder' (namespace)>
import pandas as pd

print(pd.__doc__)
pandas - a powerful data analysis and manipulation library for Python
=====================================================================

**pandas** is a Python package providing fast, flexible, and expressive data
structures designed to make working with "relational" or "labeled" data both
easy and intuitive. It aims to be the fundamental high-level building block for
doing practical, **real world** data analysis in Python. Additionally, it has
the broader goal of becoming **the most powerful and flexible open source data
analysis / manipulation tool available in any language**. It is already well on
its way toward this goal.

Main Features
-------------
Here are just a few of the things that pandas does well:

  - Easy handling of missing data in floating point as well as non-floating
    point data.
  - Size mutability: columns can be inserted and deleted from DataFrame and
    higher dimensional objects
  - Automatic and explicit data alignment: objects can be explicitly aligned
    to a set of labels, or the user can simply ignore the labels and let
    `Series`, `DataFrame`, etc. automatically align the data for you in
    computations.
  - Powerful, flexible group by functionality to perform split-apply-combine
    operations on data sets, for both aggregating and transforming data.
  - Make it easy to convert ragged, differently-indexed data in other Python
    and NumPy data structures into DataFrame objects.
  - Intelligent label-based slicing, fancy indexing, and subsetting of large
    data sets.
  - Intuitive merging and joining data sets.
  - Flexible reshaping and pivoting of data sets.
  - Hierarchical labeling of axes (possible to have multiple labels per tick).
  - Robust IO tools for loading data from flat files (CSV and delimited),
    Excel files, databases, and saving/loading data from the ultrafast HDF5
    format.
  - Time series-specific functionality: date range generation and frequency
    conversion, moving window statistics, date shifting and lagging.
# 直接调用子包 core 中模块 frame 的属性 DataFrame
pd.DataFrame
pandas.core.frame.DataFrame

可执行文件

每一个 .py 文件模块,都是 Python 的可执行文件。文件内容可以为空,但执行什么也不发生。

你可以在某些编辑工具里打开文件执行,也可以在命令行使用 python 文件路径python -m 模块 的方式执行。这些方式都是主模块中直接执行文件。

主模块(你正在运行代码的当前模块)的 __name__ 属性总是 '__main__'__main__ 是一个在解释器启动时直接初始化的特殊模块),因此直接执行文件,if __name__ == '__main__': 语句下的代码一定会被执行,而如果导入到其他模块则不会。

__name__
'__main__'

下面举例说明:

当前文件路径下,有一个 myfile 的包,包里有 space.pymycode.py 模块,内容如下:

# space.py 为空
# mycode.py 的内容, 在此源码直接执行
_a = '自学'

def __f():
    print(_a)

msg1 = '我是mycode模块中的代码'
print(msg1)

if __name__ == '__main__':
    msg2 = '我是导入其他模块不会执行的代码'
    print(msg2)
我是mycode模块中的代码
我是导入其他模块不会执行的代码
# 导入不会执行 __name__ == '__main__' 下面的代码
from myfile import mycode
print(mycode.msg1)
mycode.msg2
我是mycode模块中的代码
我是mycode模块中的代码


---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

~\AppData\Local\Temp/ipykernel_21344/86150704.py in <module>
      2 from myfile import mycode
      3 print(mycode.msg1)
----> 4 mycode.msg2


AttributeError: module 'myfile.mycode' has no attribute 'msg2'

命令行直接执行模块效果同上。下面使用命令执行(%run 是 jupyter 的魔法命令,终端请改为 python):

# 文件路径必须带 .py,可以是相对或绝对路径
%run myfile/space.py 
%run myfile/mycode.py
我是mycode模块中的代码
我是导入其他模块不会执行的代码
import warnings # 忽略警告
warnings.filterwarnings("ignore") 

# 模块可以使用属性表示法,但不能有 .py
%run -m myfile.mycode 
我是mycode模块中的代码
我是导入其他模块不会执行的代码

导入操作

导入操作使用 import 语句,详细的语法规则查看 import 导入语句

  • import ... 只能导入模块:
import random as r, pandas.core as pc
r, pc
(<module 'random' from 'F:\\anaconda\\lib\\random.py'>,
 <module 'pandas.core' from 'F:\\anaconda\\lib\\site-packages\\pandas\\core\\__init__.py'>)
# 导入方法报错
import random.randint
---------------------------------------------------------------------------

ModuleNotFoundError                       Traceback (most recent call last)

<ipython-input-2-8d4ecd1fe339> in <module>
      1 # 导入方法报错
----> 2 import random.randint


ModuleNotFoundError: No module named 'random.randint'; 'random' is not a package
  • from ... import ... 从模块中导入子模块,类,函数等:
from pandas import core
core
<module 'pandas.core' from 'F:\\anaconda\\lib\\site-packages\\pandas\\core\\__init__.py'>
from pandas import DataFrame as df
df
pandas.core.frame.DataFrame
from random import randint
randint
<bound method Random.randint of <random.Random object at 0x000001E44ED52020>>
from math import pi
pi
3.141592653589793
  • from ... import * 将导入模块中所有的公有属性:

当前文件路径下,有一个 myfile 的包,包里有 space.pymycode.py__init__.py 等模块,__init__.py 内容为空,则 myfile 包里的模块都不是它的属性,无法导入

import myfile
dir(myfile)
['__builtins__',
 '__cached__',
 '__doc__',
 '__file__',
 '__loader__',
 '__name__',
 '__package__',
 '__path__',
 '__spec__']
from myfile import *
mycode
---------------------------------------------------------------------------

NameError                                 Traceback (most recent call last)

~\AppData\Local\Temp/ipykernel_21108/1508979804.py in <module>
      1 from myfile import *
----> 2 mycode


NameError: name 'mycode' is not defined

如果在文件 __init__.py 中我们定义了属性 __all__ ,从包 myfile 导入则只能导入该属性中的列出名称。

# 修改 myfile/__init__.py
__all__ = ['mycode','xue']
from myfile import *
mycode
我是mycode模块中的代码


<module 'myfile.mycode' from 'D:\\Jupyter\\jupyter\\jupyter-python\\15_module\\myfile\\mycode.py'>

在文件 myfile/mycode.py 中,以下划线打头的属性名,从模块 mycode.mycode 导入时都不可导入。

# myfile/mycode.py
_a = '自学'

def __f():
    print(_a)

msg1 = '我是mycode模块中的代码'
print(msg1)

if __name__ == '__main__':
    msg2 = '我是导入其他模块不会执行的代码'
    print(msg2)
from myfile.mycode import *
_a
---------------------------------------------------------------------------

NameError                                 Traceback (most recent call last)

~\AppData\Local\Temp/ipykernel_9512/1217097939.py in <module>
      1 from myfile.mycode import *
----> 2 _a


NameError: name '_a' is not defined
__f
---------------------------------------------------------------------------

NameError                                 Traceback (most recent call last)

~\AppData\Local\Temp/ipykernel_9512/3733874203.py in <module>
----> 1 __f


NameError: name '__f' is not defined

通常情况下不应使用这个功能,因为它在解释器中引入了一组未知的名称,而它们很可能会覆盖一些你已经定义过的名称。而且会导致代码的可读性很差。

# 其他方式则可以导入
from myfile.mycode import __f
print(__f)
del __f
<function __f at 0x00000157E4BA53A0>
  • 相对导入:

存在相对导入代码的模块,通常是不能直接执行的。因为直接执行,解释器认为该模块即为顶级模块,属性 __package__ 的值为 None。

print(__package__)
None

但可以使用 python -m 模块 命令直接执行。

例如,当前文件路径下,文件 myfile/test.py 中相对导入的代码可以使用 python -m 模块 命令直接执行,因为该命令将属性 __package__ 重新设置为顶级模块的名称:

# xue.py 模块在 myfile 包中,内容是 “msg = '自学是门手艺'”
# myfile/test.py 的内容
from . import xue
print(xue.msg)
print(__package__)
%run -m myfile.test
自学是门手艺
myfile

模块导入之后,即可使用属性表示法调用模块中属性:

import random, math
random.randint, math.pi
(<bound method Random.randint of <random.Random object at 0x000002811CF98C80>>,
 3.141592653589793)

面向对象概述

面向对象概述

面向对象是一种对现实世界理解和抽象的方法,是计算机编程技术发展到一定阶段后的产物。它把相关的数据和方法组织为一个整体来看待,从更高的层次来进行系统建模,更贴近事物的自然运行模式。

例如生活中我们会遇到各种数字及其运算,于是计算机科学家们就把数字抽象出来,成为一类对象。它们有别于其他事物(例如文字,图像)的特征,有自己的一套运算、操作方法。

Python 中的数字当然非常类似数学中的数字,但也受限于计算机中的数字表示方法。

为了方便,又把数字这一类对象,基本分成了整数,浮点数,复数三类对象。这对应 Python 中的 int,float,comlex 三种数字类型,三种数字类型下具体的整数,浮点数,复数也是对象。它们有共同的特征和计算方法,也有各自特有的特征和方法。例如都可以进行加减乘除,只有整数有按位运算。

整数中,0 和 1 的特殊性,又使得可以将整数类中再分出一类对象,布尔类型对象。布尔类型的对象,只有两个对象,True 和 False。

从数字类对象,到三类数字对象,到布尔类对象,到具体的数字对象以及 True 和 False 对象,层层细分,最终是具体的一个个对象,它们都是对象。这也是为什么称 Python 一切皆对象的原因。

类似地,对数据,因为要实现的目的不同,将装数据的 “容器” 分为字符串,列表,元组,集合,字典等类型的对象。

函数抽象出函数对象,模块又抽象出模块对象…… 甚至连空都是对象(None)。

具体的对象,称为实例,它归属于某个类。而所有的类,又都是 object 对象的子类。各种类(类型),又统一归为类型对象(type)。

type(1), isinstance(1,int) # 1 是 int 类的实例
(int, True)
issubclass(int,object) # int 类是 object 的子类
True
type(int), isinstance(int,type) # int 是 type 的实例
(type, True)

按照面向对象的思想,我们可以定义一个 “人” 类对象,然后很方便地给这个 “人” 加上现有的各种函数,类,以及模块的功能,造出更多更多有各种特异功能的 “人”。这是不是非常伟大的设计思想呢?

类定义

类是用来创建用户定义对象的模板,是抽象的对象(类对象)。例如我们说鱼类,指的就是一类抽象的对象,而具体到武昌鱼,鲈鱼,鲫鱼…… 就是具体的对象。

定义了一个类,就可以用这个模块来创建它的具体对象(实例)。类定义的详细语法规则见 class 定义类

创建一个类,需要继承另一个类。新创建的类是子类,继承的类是基类。如不指定,创建类默认继承自所有类的基类 object。

下面是一个没有任何自定义属性的简单类:

class A:
    pass

A
__main__.A
type(A)
type
A.__bases__ # 查看基类
(object,)

继承一个指定的类,可以对它进行定制化开发。需要注意参数 self, 它是约定名称(可自定义但不推荐),它就代表类创建的实例对象自身。

例如创建一个自定义的列表类,增加 add() 方法:

class Mylist(list):
    def add(self,value):
        self.append(value)
        
lst = Mylist('123') # 创建实例赋值给 lst
lst.add(4) # lst 调用 add 方法,self 参数就是 lst
lst
['1', '2', '3', 4]

或者自定义一个字典,当访问的键不存在时,不报错,而是返回 None(__missing__() 是用来定义字典子类时找不到键如何处理的魔法方法):

class Mydict(dict):
    def __missing__(self, key):
        return 

d = Mydict(a=1,b=2)
d, d['c']
({'a': 1, 'b': 2}, None)

我们也可以自定义任意的类,其中 __init__() 方法是创建实例时用来初始化实例对象的魔法方法,可以用它来增加一些自定义属性。没有该方法,创建实例时,将自动调用基类的该方法完成初始化。

例如定义一个 “人” 类:

class Person:
    '这是人类'
    # 定义人的属性
    def __init__(self, name):
        self.name = name
        self.ears = '耳朵'
        self.brain = '大脑'
        self.hands = '双手'
    
    # 人有生活
    def live(self):
        print(f'{self.name}{self.ears}'
              '听着音乐,'
              '享受美好生活。')
        
    # 还有工作
    def work(self):
        print(f'{self.name}{self.brain}和'
              f'{self.hands}'
               '勤劳致富。')
        
# 造人
xm = Person('小明')
xz = Person('小张')
xm.live() # 小明在享受生活
xz.work() # 小张在努力工作
小明用耳朵听着音乐,享受美好生活。
小张用大脑和双手勤劳致富。

实例

实例是调用类对象创建的具体对象。例如调用内置类型 int,创建的所有整数,都是 int 类的实例。可通过内置函数 isinstance() 进行实例检查。

int('1')
1
isinstance(1,int)
True

Python 中所有的实例对象,都是 object 的实例。所有的类都是 object 的子类,也被视为 object 的实例;所有的类也被视为默认的元类(创建类的类)type 的实例。

如果一个实例对象应该视为某个类的实例,可以通过魔法方法 __instancecheck__() 来重载 isinstance() 函数,自定义实例检查行为。

isinstance(1,object), isinstance([],object)
(True, True)
isinstance(int,object)
True
isinstance(int,type)
True

下面定义一个 “人” 类,来对实例进行说明:

class Person:
    '这是人类'
    # 定义人的属性
    def __init__(self, name):
        self.name = name
        self.ears = '耳朵'
        self.brain = '大脑'
        self.hands = '双手'
    
    # 人有生活
    def live(self):
        print(f'{self.name}{self.ears}'
              '听着音乐,'
              '享受美好生活。')
        
    # 还有工作
    def work(self):
        print(f'{self.name}{self.brain}和'
              f'{self.hands}'
               '勤劳致富。')
        
# 造人
xm = Person('小明')
xz = Person('小张')

上述 “人” 类对象 Person 是类对象,调用类对象创建的 xm 和 xz,是两个具体的 “人”,是实例对象。

Person
__main__.Person
xm
<__main__.Person at 0x24cf9ef6d68>
xz
<__main__.Person at 0x24cf9ef6d30>
isinstance(xm, Person), isinstance(xz, Person)
(True, True)
isinstance(Person, object), isinstance(Person, type)
(True, True)

对象

对象是 Python 中对数据的抽象。Python 程序中的所有数据都是由对象或对象间关系来表示的。

例如计算 x + y 就有对象 xy+ 其实是调用了 __add__ 方法对象。

[1,2] + [3,4]
[1, 2, 3, 4]
list.__add__
<slot wrapper '__add__' of 'list' objects>
[1,2].__add__([3,4])
[1, 2, 3, 4]

每个对象都有各自的编号、类型和值。一个对象被创建后,它的编号就绝不会改变,可以将其理解为该对象在内存中的地址。

id([1,2]), type([1,2]) # 实例对象 [1,2] 的编号 和 类型
(2229938322504, list)
id(list), type(list) # 类对象 list 的编号 和 类型
(140736641645872, type)

对象的值在 Python 中是一个相当抽象的概念:

  • 对象的值并没有一个规范的访问方法;
  • 对象的值并不要求具有特定的构建方式,例如由其全部数据属性组成等;
  • 比较运算符实现了一个特定的对象的值概念,可以认为正是通过实现对象比较,间接地定义了对象的值。
# 列表对象 [1,2] 和类对象 list 
# 是两个不同对象,值是不相等的
[1,2] == list 
False

具有固定值的对象为不可变对象,例如数字,字符串,元组(包含的对象集不可变),相等的两个值,可能会也可能不会指向同一个对象,看具体实现。

# 元组的对象集固定不可变
t = ([1,2],(3,4))
print(id(t[0]),id(t[1]),t)
del t[0][:]
print(id(t[0]),id(t[1]),t)
2229937078536 2229937013960 ([1, 2], (3, 4))
2229937078536 2229937013960 ([], (3, 4))
# 同一个对象 1
print(id(1))
print(id(1))
140736642126656
140736642126656
# 两个不同对象 1000
print(id(1000))
print(id(1000))
2229938479344
2229938479248

对象的编号保持不变,可以改变值的对象为可变对象。对于可变对象,分别创建两个相等的值,一定是不同对象。

# 可变对象 a,值改变了还是 a
a = [1,2]
print(id(a), a)
del a[:]
print(id(a), a)
2229937082056 [1, 2]
2229937082056 []
# 类对象 A,增加属性 a 还是 A
class A:pass
print(id(A), A)
A.a = 0
print(id(A), A)
2229917928424 <class '__main__.A'>
2229917928424 <class '__main__.A'>
a = []
b = []
print(id(a))
print(id(b))
a == b
2229937416840
2229937416648


True

类或函数对象的哈希值基于其 id,创建两个看起来完全一样的类或函数,id 是不一样的,而可哈希对象必须具有相同的哈希值,比较结果才会相同。

def f():pass
print(id(f))
a = f

def f():pass
print(id(f))
b = f
a == b
2229938263992
2229938264128


False

名称

名称(标识符)用于指代对象。当名称被绑定到一个对象时,对该名称求值将返回相应对象。当名称未被绑定时,尝试对其求值将引发 NameError 异常。Python 的保留字或称关键字是特殊的名称,不可被用作普通名称,例如不可用做变量名,函数名,模块名。

# 将 1 绑定名称 a
a = 1
a
1
# 将自定义类对象绑定到名称 A
class A:
    pass
A
__main__.A
# 将模块 random 绑定到名称 r
import random as r
r
<module 'random' from 'F:\\anaconda\\lib\\random.py'>
# 迭代的每一项循环绑定到名称 i
for i in 'xue':
    display(i)

i
'x'

'u'

'e'

'e'
# 未绑定的名称 m
m
---------------------------------------------------------------------------

NameError                                 Traceback (most recent call last)

<ipython-input-4-2469ef96c490> in <module>
      1 # 未绑定的名称 m
----> 2 m


NameError: name 'm' is not defined
help('keywords') # 保留字
Here is a list of the Python keywords.  Enter any keyword to get more help.

False               class               from                or
None                continue            global              pass
True                def                 if                  raise
and                 del                 import              return
as                  elif                in                  try
assert              else                is                  while
async               except              lambda              with
await               finally             nonlocal            yield
break               for                 not                 
False = 0
  File "<ipython-input-9-223dbc74e028>", line 1
    False = 0
    ^
SyntaxError: cannot assign to False

检查一个名称是否有效可用字符串方法 str.isidentifier():

'for'.isidentifier()
True
'my_name'.isidentifier()
True
'函数'.isidentifier()
True

普通名称命名规则:

  • 可以使用大部分非标点符号的 Unicode 字符,但请使用 ASCII 范围内 (U+0001..U+007F) 的字符;
  • 习惯使用大写和小写字母 A 至 Z,下划线 _ 以及数字 0 至 9,但不可以数字打头;
  • 长度没有限制,对大小写敏感。
数字一 = 1
数字一
1
def 函数():
    pass
函数
<function __main__.函数()>

命名模式以下划线字符打头和结尾,有特殊含义的名称(* 代表任意数量可用名称字符):

  • _* 不会被 from module import * 导入。特殊标识符 _ 在交互式解释器中被用来存放最近一次求值结果;它保存在 builtins 模块中。当不处于交互模式时,_ 无特殊含义也没有预定义。

  • __*__ 系统定义的名称,这些名称是由解释器及其实现(包括标准库)定义的特殊属性和方法名称。未来的 Python 版本中还将定义更多此类名称。任何情况下任何不遵循文档所显式指明的 __*__ 名称使用方式都可能导致无警告的错误。

  • __* 类的私有名称。这种名称在类定义中使用时,会以一种混合形式重写以避免在基类及派生类的 “私有” 属性之间出现名称冲突。例如,出现在一个名为 Ham 的类中的名称 __spam 会被转换为 _Ham__spam。如果类名仅由下划线组成,则不会进行转换。

b = 100
b
100
_
100
dir(object)
['__class__',
 '__delattr__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__gt__',
 '__hash__',
 '__init__',
 '__init_subclass__',
 '__le__',
 '__lt__',
 '__ne__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__']
class B:
    __b = 0
dir(B)
['_B__b',
 '__class__',
 '__delattr__',
 '__dict__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__gt__',
 '__hash__',
 '__init__',
 '__init_subclass__',
 '__le__',
 '__lt__',
 '__module__',
 '__ne__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 '__weakref__']

del 语句可删除名称绑定:

c = 3
print(f'c={c}')
del c
c
c=3


---------------------------------------------------------------------------

NameError                                 Traceback (most recent call last)

<ipython-input-23-5ba97d2f3254> in <module>
      2 print(f'c={c}')
      3 del c
----> 4 c


NameError: name 'c' is not defined

变量

Python 中绑定对象的名称即为变量。

人们称呼模块、类、函数和方法对象的名称时,习惯直接指代对象本身,而不是把它们当作变量,例如 random,int,print 等,当将它们绑定到另外的名称时,才称新名称为变量(或化名)。

# 函数名称 f 绑定自定义函数对象
def f(x, func): 
    print(func(x))
    
a = '1231' # 变量 a
b = [int,list,set] # 变量 b
c = f # 名称 f 起别名 c

for i in b: # 变量 b 中的项循环赋值给变量 i
    c(a,i)
1231
['1', '2', '3', '1']
{'3', '1', '2'}

如果名称绑定在一个代码块中,则为该代码块的局部变量,除非声明为 nonlocal 或 global。

如果名称绑定在模块层级,则为全局变量。(模块代码块的变量既为局部变量又为全局变量。)

如果变量在一个代码块中被使用但不是在其中定义,则为自由变量。

# 第一个代码块中全局变量 n
n = 100 
# 第二个代码块
# random 模块中的全局变量 randint,
# 在此为局部变量,引用后赋值给全局变量 r
import random
r = random.randint
# 第三个代码块中 x 为局部变量
# n 和 r 不在该代码块中定义,在该代码块为自由变量
def f(x):
    print(r(x,n))
    
f(1)
22

如果代码块中定义了一个局部变量,则其作用域包含该代码块。如果定义发生于函数代码块中,则其作用域会扩展到该函数所包含的任何代码块,除非有某个被包含代码块引入了对该名称的不同绑定。

m = 0
n = 1 # 全局变量 n
def f():
    n = 2 # 局部变量 n
    print(m,n)
f()
0 2

改变变量作用域,详见 global 语句nonlocal 语句

类变量和实例变量:

  • 在类中定义,仅在类层级修改的变量为类变量,实例也可访问类变量;
  • self.name 命名的变量为实例变量,类不能访问。
class A:
    a = 1 # 类变量
    def __init__(self):
        self.a = 100 # 实例变量
        self.b = 2
        
a = A()
A.a, a.a, a.b
(1, 100, 2)
A.b # 类 A 不能访问它的实例的变量 b
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-11-f92f3bcfde9d> in <module>
----> 1 A.b # 类 A 不能访问它的实例的变量 b


AttributeError: type object 'A' has no attribute 'b'

属性

属性指关联到一个对象的值,可以使用点号表达式通过其名称来引用。

可以使用 dir() 函数查看任意对象的属性。

如果属性是可调用对象(例如类,函数,方法等),引用之后可直接调用,也可先赋值给变量再调用。

# 查看 list 对象的属性
dir(list)
['__add__',
 '__class__',
 '__contains__',
 '__delattr__',
 '__delitem__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__getitem__',
 '__gt__',
 '__hash__',
 '__iadd__',
 '__imul__',
 '__init__',
 '__init_subclass__',
 '__iter__',
 '__le__',
 '__len__',
 '__lt__',
 '__mul__',
 '__ne__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__reversed__',
 '__rmul__',
 '__setattr__',
 '__setitem__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 'append',
 'clear',
 'copy',
 'count',
 'extend',
 'index',
 'insert',
 'pop',
 'remove',
 'reverse',
 'sort']
# 通过名称 __add__ 引用属性
list.__add__ 
<slot wrapper '__add__' of 'list' objects>
# 直接调用属性
list.__add__([1,2],[3,4])
[1, 2, 3, 4]
# 赋值给变量再调用
la = list.__add__
la([1,2],[3,4])
[1, 2, 3, 4]

有时也只将具体的值称为属性从而与抽象的值(类,函数,方法等对象的值)区分开来。犹如将一个人有身体和四肢的属性和这个人会唱歌跳舞的属性区分开来。

import pandas as pd
pd.Series # pandas 模块的属性 Series 类
pandas.core.series.Series
import math
math.pi # math 模块的属性圆周率
3.141592653589793
class A:
    a = 1
    def f(self):
        print(self)
        
a = A()
A.a, a.a # 类 A 及其实例的数据属性 a
(1, 1)
A.f, a.f # 类 A 及其实例的方法属性 f
(<function __main__.A.f(self)>,
 <bound method A.f of <__main__.A object at 0x000001B569DC0860>>)
# 方法属性的等价调用
# 实例直接调用,第一个参数 self 就是它自身
a.f()
# 类直接调用,则需要传入实例作为参数
A.f(a)
<__main__.A object at 0x000001B569DC0860>
<__main__.A object at 0x000001B569DC0860>

属性名以一个下划线开头的属性应该视为 “私有” 属性,但可以直接访问。

属性名以两个下划线开头,非两个下划线结尾的属性,在模块中视为 “私有” 属性,但可以直接访问;在类中属于私有属性,这种名称在类定义中使用时,会以一种混合形式重写以避免在基类及派生类的 “私有” 属性之间出现名称冲突。类的私有属性并非不可访问(使用重写名称可访问),全靠自觉。

class A:
    _a = 1
    def __f():
        pass
dir(A)
['_A__f',
 '__class__',
 '__delattr__',
 '__dict__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__gt__',
 '__hash__',
 '__init__',
 '__init_subclass__',
 '__le__',
 '__lt__',
 '__module__',
 '__ne__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 '__weakref__',
 '_a']
A._a
1
A._A__f
<function __main__.A.__f()>

方法

方法指在类内部定义的函数。但并不严格要求一定要在类内部定义。

def f(self):
    pass

class A:
    f = f
A().f
<bound method f of <__main__.A object at 0x00000290DC56E198>>

下列方法看起来是模块中的函数,其实是模块中,类实例方法重新赋值的名称:

from random import randint
type(randint)
method
import random
# randint 完整路径
random.Random.randint

# 例如重新赋值后即可直接调用
r = random.Random()
randint = r.randint
randint(0,3)
2

普通方法(第一个参数通常命名为 self)如果作为该类的实例的一个属性来调用,方法将会获取实例对象作为其第一个参数。

# list 类的方法 append
help('list.append')
Help on method_descriptor in list:

list.append = append(self, object, /)
    Append object to the end of the list.
# list 的实例 [] 调用,
# 方法将会获取实例对象 [] 作为其第一个参数
a = []
a.append(1)
a
[1]
# 等价于
a = []
list.append(a,1)
a
[1]

类方法(第一个参数通常命名为 cls)则无论是类或是实例调用,方法都将获取类对象作为其第一个参数。类方法定义详见 classmethod 封装函数为类方法

class A:
    @classmethod
    def f(cls,x):
        print(cls,x)
        
print(f'A = {A}')        
A.f('类方法')
A().f('类方法')
A = <class '__main__.A'>
<class '__main__.A'> 类方法
<class '__main__.A'> 类方法

静态方法则不会接收隐式的第一个参数。调用它需要正常传递参数。静态方法详见 staticmethod 封装函数为静态方法

class A:
    @staticmethod
    def in_print(value):
        print(value)
        
a = A()
a.in_print('静态方法')
A.in_print('静态方法')
静态方法
静态方法

特殊方法(也称魔术方法):一种由 Python 隐式调用的方法,用来对某个类型执行特定操作例如相加等等。这种方法的名称的首尾都为双下划线。可以通过方法重载,对某个类型定义特定操作。

# 1 + 2 操作其实是隐式调用了 __add__
1 + 2
3
(1).__add__(2)
3
# 列表没有 “-” 操作符,自定义一个
class Mlist(list):
    def __sub__(self, other):
        return list(set(self) - set(other))
    
a = Mlist('123')
b = Mlist('13')
a, b, a - b
(['1', '2', '3'], ['1', '3'], ['2'])

命名空间

命名空间是存放变量的场所。命名空间有局部、全局和内置的,还有对象中的嵌套命名空间。不同命名空间中的变量没有关系。

命名空间通过防止命名冲突来支持模块化。例如,函数 builtins.open 与 os.open 可通过各自的命名空间来区分。

# 函数的局部命名空间,在函数调用时创建
def f():
    x = '函数 f 命名空间中的变量 x'
    print(x)
# 全局命名空间
x = '全局命名空间中的变量 x'
# 调用函数 f,与全局命名空间中的 x 无关
f()
函数 f 命名空间中的变量 x
# 模块 random 导入创建它自己的局部命名空间
import random
# 定义一个全局变量 randint
def randint():
    print('全局 randint')
# random 局部命名空间中的 randint 
# 与全局变量 randint 无关
randint()
random.randint(0,3)
全局 randint


1
# 上述定义的变量 f, x, randint,导入的变量 random,
# 都存放在了当前全局命名空间中
f, x, random, randint
(<function __main__.f()>,
 '全局命名空间中的变量 x',
 <module 'random' from 'C:\\ProgramData\\Anaconda3\\lib\\random.py'>,
 <function __main__.randint()>)

内置命名空间是在 Python 解释器启动时创建,存放的变量包括内置函数、异常等。

# 全局命名空间中定义变量 str,
# 将屏蔽内置命名空间中的 str
def str(x):
    return f'x = {x}'
str(123)
'x = 123'
# 但可以在 builtins 中继续调用
import builtins

builtins.str(123)
'123'

递归函数,每次递归调用,都会有一个新的命名空间。

def f(x):
    if x > 0:
        print(f'x={x}调用,x-1={x-1}继续调用')
    if x == 0:
        print(f'x={x},调用结束')
    else:
        # 以 x-1 作为参数调用函数 f
        return f(x-1)
f(3)
x=3调用,x-1=2继续调用
x=2调用,x-1=1继续调用
x=1调用,x-1=0继续调用
x=0,调用结束

嵌套的命名空间:

a = '全局变量 a'
class A:
    a = '类变量 a'
    def f(self):
        a = '函数局部变量 a'
        return a

print(a)
print(A.a)
print(A().f())
全局变量 a
类变量 a
函数局部变量 a

作用域

作用域定义了一个代码块中名称的可见性。如果代码块中定义了一个局部变量,则其作用域包含该代码块。如果定义发生于函数代码块中,则其作用域会扩展到该函数所包含的任何代码块,除非有某个被包含代码块引入了对该名称的不同绑定。

当一个名称在代码块中被使用时,会由包含它的最近作用域来解析。对一个代码块可见的所有这种作用域的集合称为该代码块的环境。

一个作用域是一个 命名空间 可直接访问的 Python 程序的文本区域。这里的 “可直接访问” 意味着对名称的非限定引用(限定引用指点号表达式例如 math.pi)会尝试在命名空间中查找名称。

# 全局变量作用域为当前模块
a = '全局变量'
def f():
    # print 内置名称作用域包含全局
    print(f'print 函数的命名空间可以访问:{a}')
    return f'f 的命名空间可以访问:{a}'

print(a)
print(f())
全局变量
print 函数的命名空间可以访问:全局变量
f 的命名空间可以访问:全局变量
# 全局变量作用域为当前模块
# 但被包含命名空间引入了同名的局部变量
a = '全局变量'
def f():
    # f 中定义的局部变量
    # 作用域为函数内部
    a = '局部变量'
    print(f'print 函数的命名空间可以访问:{a}')
    return f'f 的命名空间可以访问:{a}'

print(a)
print(f())
全局变量
print 函数的命名空间可以访问:局部变量
f 的命名空间可以访问:局部变量

虽然作用域是静态地确定的,但它们会被动态地使用。嵌套作用域的搜索顺序:

  • 最先搜索最内部作用域包含的局部名称
  • 从最近的封闭作用域开始搜索作用域包含的名称
  • 倒数第二个作用域包含当前模块的全局名称
  • 最外面的作用域(最后搜索)是包含内置名称的命名空间
# 搜索最内部作用域名称 str
# 屏蔽上层作用域
def f():
    str = 0
    def f1():
        str = 1
        return str
    return f1()

f(), str
(1, str)
# 搜索最近封闭作用域名称 str
# 屏蔽上层作用域
def f():
    str = 0
    def f1():
        return str
    return f1()

f(), str
(0, str)
# 搜索当前模块作用域名称 str
# 屏蔽上层作用域
str = '当前模块str'
def f():
    def f1():
        return str
    return f1()

f()
'当前模块str'
# 搜索最外面作用域名称 str
# 删除前面对 str 的绑定
del str
# 注意,运行多次会将内置名称 str 都删除

def f():
    def f1():
        return str
    return f1()

f()
str

可见,当前模块中的全局名称,最好不要和内置名称相同,它会屏蔽掉内置名称,从而不可以直接使用内置功能。内置名称如下:

import builtins
dir(builtins)
['ArithmeticError',
 'AssertionError',
 'AttributeError',
 'BaseException',
 'BlockingIOError',
 'BrokenPipeError',
 'BufferError',
 'BytesWarning',
 'ChildProcessError',
 'ConnectionAbortedError',
 'ConnectionError',
 'ConnectionRefusedError',
 'ConnectionResetError',
 'DeprecationWarning',
 'EOFError',
 'Ellipsis',
 'EnvironmentError',
 'Exception',
 'False',
 'FileExistsError',
 'FileNotFoundError',
 'FloatingPointError',
 'FutureWarning',
 'GeneratorExit',
 'IOError',
 'ImportError',
 'ImportWarning',
 'IndentationError',
 'IndexError',
 'InterruptedError',
 'IsADirectoryError',
 'KeyError',
 'KeyboardInterrupt',
 'LookupError',
 'MemoryError',
 'ModuleNotFoundError',
 'NameError',
 'None',
 'NotADirectoryError',
 'NotImplemented',
 'NotImplementedError',
 'OSError',
 'OverflowError',
 'PendingDeprecationWarning',
 'PermissionError',
 'ProcessLookupError',
 'RecursionError',
 'ReferenceError',
 'ResourceWarning',
 'RuntimeError',
 'RuntimeWarning',
 'StopAsyncIteration',
 'StopIteration',
 'SyntaxError',
 'SyntaxWarning',
 'SystemError',
 'SystemExit',
 'TabError',
 'TimeoutError',
 'True',
 'TypeError',
 'UnboundLocalError',
 'UnicodeDecodeError',
 'UnicodeEncodeError',
 'UnicodeError',
 'UnicodeTranslateError',
 'UnicodeWarning',
 'UserWarning',
 'ValueError',
 'Warning',
 'WindowsError',
 'ZeroDivisionError',
 '__IPYTHON__',
 '__build_class__',
 '__debug__',
 '__doc__',
 '__import__',
 '__loader__',
 '__name__',
 '__package__',
 '__spec__',
 'abs',
 'all',
 'any',
 'ascii',
 'bin',
 'bool',
 'breakpoint',
 'bytearray',
 'bytes',
 'callable',
 'chr',
 'classmethod',
 'compile',
 'complex',
 'copyright',
 'credits',
 'delattr',
 'dict',
 'dir',
 'display',
 'divmod',
 'enumerate',
 'eval',
 'exec',
 'filter',
 'float',
 'format',
 'frozenset',
 'get_ipython',
 'getattr',
 'globals',
 'hasattr',
 'hash',
 'help',
 'hex',
 'id',
 'input',
 'int',
 'isinstance',
 'issubclass',
 'iter',
 'len',
 'license',
 'list',
 'locals',
 'map',
 'max',
 'memoryview',
 'min',
 'next',
 'object',
 'oct',
 'open',
 'ord',
 'pow',
 'print',
 'property',
 'range',
 'repr',
 'reversed',
 'round',
 'set',
 'setattr',
 'slice',
 'sorted',
 'staticmethod',
 'str',
 'sum',
 'super',
 'tuple',
 'type',
 'vars',
 'zip']

创建实例

直接调用类对象,即可创建该类的实例对象。

int() # 调用 int 类
0
list('123') # 传参调用 list 类
['1', '2', '3']
class A:
    def __init__(self, name):
        self.name = name
        
    def __repr__(self):
        return self.name
    
A('实例1'), A('实例2') # 调用自定义类
(实例1, 实例2)

实例对象是由特殊方法 __new__() 创建,__init__() 定制完成。两个方法是隐式地完成创建和定制的,当然也可以显式地创建并定制。

class A:
    def __init__(self):
        self.name = '实例'
        
A # 类对象 A
__main__.A
# 显式地创建类 A 的一个实例 a
a = object.__new__(A)
a
<__main__.A at 0x20d23e95d30>
# 未初始化,无属性
a.name
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-36-b3a4d04d98fc> in <module>
      1 # 未初始化,无属性
----> 2 a.name


AttributeError: 'A' object has no attribute 'name'
# 也可以使用 a.__init__(), 
# 它会自动将实例作为第一个参数完成初始化
A.__init__(a)
a
<__main__.A at 0x20d23e95d30>
a.name
'实例'

属性操作

Python 是动态语言,对象的属性不仅可以查看、访问、调用,还可以动态地增、删、改。

下面定义一个没有自定义属性的类 A 举例:

dir() 函数查看属性,双下划线 __ 包围的属性是继承自 object 的特殊属性:

# 定义一个类,查看属性
class A:
    pass

dir(A)
['__class__',
 '__delattr__',
 '__dict__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__gt__',
 '__hash__',
 '__init__',
 '__init_subclass__',
 '__le__',
 '__lt__',
 '__module__',
 '__ne__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 '__weakref__']
# 访问属性 __class__ 相当于调用 type() 函数
A.__class__, A().__class__
(type, __main__.A)

给类 A 及其实例动态添加属性,属性可以是数据属性和方法。类层级添加的属性,将作为所有实例的属性;实例添加的属性,只有对应的实例能访问。

# 添加数据属性
a1 = A()
a2 = A()
A.a = '类变量'
a1.x = 'a1 的属性'
a2.y = 'a2 的属性'
# 类层级添加方法
def f(self):
    print(self.a)

A.f = f
# 类 A 调用刚添加的属性
A.f(a1)
A.f(a2)
类变量
类变量
# 实例调用刚添加的属性
a1.f()
a2.f()
类变量
类变量
# 实例 a1 没有 y 属性
# 同理,a2 没有 x 属性
a1.y 
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-44-178a7ac627a0> in <module>
      1 # 实例 a1 没有 y 属性
      2 # 同理,a2 没有 x 属性
----> 3 a1.y


AttributeError: 'A' object has no attribute 'y'

实例直接添加一个函数作为属性,将不会隐式地将自身作为第一个参数,而是和正常函数一样使用:

def p(self):
    print(self)

a1.p = p
a1.p('正常传参')
a1.p()
正常传参


---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-45-4ebdece61250> in <module>
      4 a1.p = p
      5 a1.p('正常传参')
----> 6 a1.p()


TypeError: p() missing 1 required positional argument: 'self'

如果要通过实例添加实例方法(第一个参数 self 即是自身的方法),可以通过 types 模块添加。添加的属性,类不可访问,只有对应的实例可访问:

from types import MethodType

# 改变了 a1 的上述 p 属性
a1.p = MethodType(p, a1)
a1.p()
<__main__.A object at 0x000001D40578D730>
A.p
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-47-94cb580acfb0> in <module>
----> 1 A.p


AttributeError: type object 'A' has no attribute 'p'
a2.p
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-48-29321b2aa581> in <module>
----> 1 a2.p


AttributeError: 'A' object has no attribute 'p'

删除属性使用 del 语句:

del a1.p

a1.p()
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-49-9cf9b7e7dc86> in <module>
      1 del a1.p
      2 
----> 3 a1.p()


AttributeError: 'A' object has no attribute 'p'

方法操作

一个从父类继承过来的方法,如果不满足子类的需求,可以进行重写,重写的方法将屏蔽父类的方法,但可以显示地调用,或使用 super() 委托给父类调用,实现父类、子类的方法都可调用。

class A:
    def f(self,x):
        y = x + x
        print(y)
class B(A):
    def f(self,x):
        y = (x + x)**2 
        print(y)

b = B()
b.f(2)
16
# 显示地调用
A.f(b, 2)
4
class A:
    def f(self,x):
        y = x + x
        print(y)
class B(A):
    def f(self,x):
        super().f(x)
        y = (x + x)**2 
        print(y)

b = B()
b.f(2)
4
16

还可使用装饰器修改方法,或添加丰富功能等。

# 将方法定义为静态方法
class C:
    @staticmethod
    def f(value):
        print(value)
c = C()
c.f('必须传参调用')
必须传参调用
# 将私有属性定义为只读,
# 直接用不带下划线的名称访问
class C:
    def __init__(self):
        self.__name = '私有属性'
    @property
    def name(self):
        return self.__name
c = C()
c.name
'私有属性'
# 尝试修改不被允许
c.name = 0
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-11-c0f354f546f5> in <module>
      1 # 尝试修改不被允许
----> 2 c.name = 0


AttributeError: can't set attribute

用特殊方法定制类:

class D:
    pass

d = D()
bool(d)
True
# 上述类的实例逻辑值检查为 True
# 定义为 False
class D:
    def __bool__(self):
        return False
d = D()
bool(d)
False
# 让数字字符串也可以相减
class Mystr(str):
    def __sub__(self, other):
        return str(float(self) - float(other))
m = Mystr('123')
n = Mystr('3.14')
m, n, m - n
('123', '3.14', '119.86')

类继承

所有的类都继承自 object。被继承的类称为基类(或父类,超类),继承者称为子类。

对于多数应用来说,在最简单的情况下,你可以认为搜索从父类所继承属性的操作是深度优先、从左至右的,当层次结构中存在重叠时不会在同一个类中搜索两次。

class A():
    def show(self):
        print('A')

class B(A):
    pass

class C():
    pass

class D():
    def show(self):
        print('D')

class E(C, B, D):  # C -> B -> A -> D
    pass

e = E()
e.show()
A

真实情况比这个更复杂一些;方法解析顺序会动态改变以支持对 super() 的协同调用。这种方式在某些其他多重继承型语言中被称为 后续方法调用,它比单继承型语言中的 super 调用更强大。

动态改变顺序是有必要的,因为所有多重继承的情况都会显示出一个或更多的菱形关联(即至少有一个父类可通过多条路径被最底层类所访问)。例如,所有类都是继承自 object,因此任何多重继承的情况都提供了一条以上的路径可以通向 object。为了确保基类不会被访问一次以上,动态算法会用一种特殊方式将搜索顺序线性化,保留每个类所指定的从左至右的顺序,只调用每个父类一次,并且保持单调(即一个类可以被子类化而不影响其父类的优先顺序)。

总而言之,这些特性使得设计具有多重继承的可靠且可扩展的类成为可能。

一个基类如果有 __init__() 方法,则其所派生的类如果也有 __init__() 方法,就必须显式地调用它以确保实例基类部分的正确初始化:

class A:
    def __init__(self):
        self.a = 'A'
    
    def f(self):
        print(self.a)
        
class B(A):
    def __init__(self):
        self.b = 'B'

b = B()
b.f() # 基类未初始化,属性 a 不可调用
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-5-cda60db4451f> in <module>
     11 
     12 b = B()
---> 13 b.f()


<ipython-input-5-cda60db4451f> in f(self)
      4 
      5     def f(self):
----> 6         print(self.a)
      7 
      8 class B(A):


AttributeError: 'B' object has no attribute 'a'
A.__init__(b) # 将实例 b 传给 A 初始化
b.f()
A
# 或者直接用 super()
class A:
    def __init__(self):
        self.a = 'A'
    
    def f(self):
        print(self.a)
        
class B(A):
    def __init__(self):
        super().__init__()
        self.b = 'B'

b = B()
b.f()
A

通俗易懂 Python 正则表达式

官方文档

正则表达式, 也叫规则表达式, 是强大的文本字符串处理工具, 通常用来验证, 查找, 筛选, 提取, 替换那些符合某个规则的文本字符串,是实现文本高效处理的神器

1, 匹配规则

正则表达式的核心就是设计一个规则, 按照这个规则”按图索骥”, 去寻找符合这个规则的字符串, 并将它按需处理

先以一个最简单的例子进行探索:

re: 正则表达式模块
findall: 模块的一个方法, 传入 正则表达式 和 要去匹配字符串 将匹配结果以列表形式返回, 没有匹配结果返回空列表
\d: 定义的规则, 表示匹配任意一个 0~9 的数字
198\d年: 匹配符合 198某年 的字符串

然后按照规则去匹配字符串: '1988年 2000年 2020年 1980年'

# 导入模块 re  
import re  

# 按规则 r'198\d年' 匹配, r 的作用在 python 基础部分已介绍  
re.findall(r'198\d年', '1988年 2000年 2020年 1980年')  
['1988年', '1980年']

\d 是其中一个规则定义符, 可以和其他字符组合成正则表达式, 它自身也是一个正则表达式

d 则是字母 d 本身,像 \d 这样的特殊规则定义符有许多,如 .*+\s()[] 等等

任意一个非特殊字符都可以作为匹配它自身的正则表达式

如果要匹配规则定义符,例如如要匹配 \d, 需要用 \ 进行转义,也就是要用 \\d 来匹配 \d(其实是 \\ 来匹配 \d 来匹配 d

re.findall(r'\\d.+\[a]', r'a\d.+[a]')  
['\\d.+[a]']

2, 常用规则定义符

2.01, 定义类别匹配

\w 匹配任意一个可以构成词语的 Unicode 字符, 包括数字及下划线, \W 则相反

a = r'my\wname'  
b = r'my\Wname'  
c = 'my1name, my_name, my.name, my我name'  
re.findall(a, c)
['my1name', 'my_name', 'my我name']
re.findall(b, c)
['my.name']

\d 匹配任意一个 十进制 数字, \D 匹配任意一个非数字

a = r'01\d-\D123'  
b = '010-0123, 010-o123, 010-P123'  
re.findall(a, b)  
['010-o123', '010-P123']

\s 匹配任何Unicode空白字符(包括 [ \t\n\r\f\v],还有很多其他字符,比如不同语言排版规则约定的不换行空格), \S 则相反

a = r'a\sb\Sc'  
b = 'a b c, a bcc, a  bcc'  
re.findall(a, b)  
['a bcc']

. 匹配除换行符 \n 之外的任意一个字符

a = r'a.b'  
b = '''a  
b, a-b, a b, a\nb'''  
re.findall(a, b)  
['a-b', 'a b']

\b 匹配 \w\W 之间(或 \w 开头和结尾的边界)的空字符串, \B\b 相反,匹配非 \w\W 之间的空字符串(或 \W 开头和结尾的边界)

re.findall(r'\b.', 'ab啊_c。d,\n')
['a', '。', 'd', ',']
re.findall(r'.\B', 'ab啊_c。d,\n')
['a', 'b', '啊', '_', ',']

2.02, 定义范围匹配

用括号 [] 将字符(表达式)包围起来, 表示在括号内指定的范围内匹配任意一个

re.findall('[abc]', 'bill')  
['b']

[] 内, 以 ^ 开头, 表示排除括号内的字符(表达式)范围匹配

re.findall('[^abc]', 'abcd')  
['d']

[] 内, 数字或字母之间用 - 连接, 表示在两者(包含)之间的范围内匹配

re.findall('[a-d0-5A\-D]', 'af357AB-')  
['a', '3', '5', 'A', '-']

一些特殊字符在 [] 内失去特殊含义

re.findall('[(+*)\]\w]', '+(*)a].?')
['+', '(', '*', ')', 'a', ']']

2.03, 定义边界匹配

^\A, 表示必须以接下来的字符(表达式)开头才能被匹配, 换行开头也不能匹配

a = '^b\d[bc]'  
b = '''a2b  
b2b'''  
c = 'b2bcd'  
re.findall(a, b)
[]
re.findall(a, c)  
['b2b']
a = '\Ab\d[bc]'  
b = '''a2b  
b2b'''  
c = 'b2bcd'  
re.findall(a, b)
[]
re.findall(a, c)  
['b2b']

$\Z表示必须以其前面的字符(表达式)结尾才能被匹配, 换行之前的结束也不能匹配

re.findall('a\w$', '''ab
ac''')
['ac']
re.findall('a\w\Z', 'ab\nad')  
['ad']

2.04, 定义数量匹配

+ 其前面的字符(表达式)至少有一个的都能匹配

re.findall(r'10+', '110, 100, 1001')  
['10', '100', '100']

? 其前面的字符(表达式)最多有一个的才能匹配

re.findall(r'10?', '1, 10, 100')  
['1', '10', '10']

* 其前面的字符(表达式)没有或有多个都可以匹配

re.findall(r'10*', '1, 10, 1001')  
['1', '10', '100', '1']

{n} 其前面的字符(表达式)有 n 个才能匹配

re.findall(r'1\d{3}', '12, 123, 1a23, 1234')  
['1234']

{n,} 其前面的字符(表达式)有至少 n 个才能匹配, {m,n} 则表示有 m~n 个才能匹配, m 和 n 之间只有 , 号而不能有空格

re.findall(r'1\d{2,}', '12, 123, 1234, 12345')  
['123', '1234', '12345']
re.findall(r'1\d{2,3}', '12, 123, 1234, 12345')  
['123', '1234', '1234']

2.05, 匹配符 |

符号 | 两边的内容, 有一边匹配 或 两边都匹配都可以, 但当 | 在括号 [] 中则无此作用, 只代表它自身

re.findall(r'aa|bb', 'aacbbcaabbab')  
['aa', 'bb', 'aa', 'bb']
re.findall(r'a[|]b', 'ab, a|b')  
['a|b']

2.06, 定义组合匹配

() 将多个字符组合成一个整体来匹配, 不管 () 外是否有数量匹配符, 都只返回 () 内的内容, 如果表达式内有多个 () 封装的内容, 匹配结果以元组形式返回

# 匹配的是 ab 和 abab 但只返回括号内的 ab  
re.findall(r'(ab)+', 'ab11abab')   
['ab', 'ab']
re.findall(r'^(ab)+', 'ab11abab') # 必须 ab 开头 
['ab']
# 匹配的是后面的 abab 返回元组  
re.findall(r'(ab)(ab)', 'ab11abab')   
[('ab', 'ab')]

将贪婪匹配转为非贪婪匹配

# 贪婪匹配 \d+ 使得无法将 000 取出  
re.findall(r'(\d+)(0*)$', '123000')  
[('123000', '')]
# + 后面加个 ? 号  
re.findall(r'(\d+?)(0*)$', '123000')  
[('123', '000')]
re.findall(r'[(ab)]', 'ab, (ab)')  
['a', 'b', '(', 'a', 'b', ')']

如果 () 内以 ?: 开头, 只有一组时,返回匹配的字符串,多组则 ?: 开头的不捕获不返回

re.findall(r'(?:ab)+', 'ab11abab')   
['ab', 'abab']
# 匹配的是后面的 abab, 但只返回前一个 () 内的内容  
re.findall(r'(ab)(?:ab)+', 'ab11abab')   
['ab']

如果 () 内以 ?= 开头, 则 () 内的内容只是用来匹配, 不返回也不消耗匹配的字符, 也就是说, 匹配完了, 后面的字符需要继续匹配

re.findall(r'a(?=bc)bc', 'ab, abc, abcde')  
['abc', 'abc']

如果 () 内以 ?! 开头, 则 () 内的内容只是用来排除, 也就是说, 排除括号内的内容都可以匹配, 不占字符, 匹配完了, 后面的字符需要继续匹配

# a 后面除了 bc 都可以匹配  
re.findall(r'a(?!bc)\w+', 'abc, acb, abde')  
['acb', 'abde']

\number 匹配指定的组合,组合从 1 开始编号

# \1 匹配第一个组合 (a)
re.findall(r'(a)(\d+)(\1)', 'aba1aa34a2')  
[('a', '1', 'a'), ('a', '34', 'a')]

绝大部分 Python 的标准转义字符也被正则表达式分析器支持:

\a      \b      \f      \n
\N      \r      \t      \u
\U      \v      \x      \\

(注意 \b 被用于表示词语的边界,它只在 [] 内表示退格,比如 [\b]

2.07, 匹配规则修改标志

介绍几个常用的规则修改标志

re.I 大小写不敏感匹配

re.findall(r'aA','aA, aa', re.I)  
['aA', 'aa']

re.S 使 . 匹配任何字符, 包括换行符

s = '''a
b, a-b'''  
re.findall(r'a.b', s, re.S)  
['a\nb', 'a-b']

re.M 使得 ^$ 能匹配换行的开始或换行前的结束

a = '^b\d[bc]'  
b = '''b2c
b2b'''  
re.findall(a, b, re.M)  
['b2c', 'b2b']
a = 'a\d[bc]$'  
b = '''a2b
a2c'''  
re.findall(a, b, re.M)  
['a2b', 'a2c']

3, 匹配和处理方法

match 从字符串起始位置匹配, 匹配到的字符返回为 match 对象, 可以用方法加以获取

a = re.match(r'ab', 'abc')  
b = re.match(r'a(bc)d(ef)g', 'abcdefgh')  
c = re.match(r'ab', 'bab')  
a
<re.Match object; span=(0, 2), match='ab'>
b
<re.Match object; span=(0, 7), match='abcdefg'>
c
# group() 获取匹配的全部字符,   
# group(1) 获取 () 内匹配的第一个, 以此类推  
a.group(), b.group(), b.group(1), b.group(2)  
('ab', 'abcdefg', 'bc', 'ef')
# start 获取匹配开始的位置  
a.start(), b.start()  
(0, 0)
# end 获取匹配结束的位置  
a.end(), b.end()  
(2, 7)
# span 获取 (开始, 结束) 元组  
a.span(), b.span()  
((0, 2), (0, 7))

compile 编译正则表达式, 返回一个 Pattern 对象(后面简写为p), 然后可以用该对象调用方法

p = re.compile('\d+')  
p  
re.compile(r'\d+', re.UNICODE)
# 调用上面的 match 方法, 可以指定开始和结束位置  
a = p.match('12a21', 3)  
a  
<re.Match object; span=(3, 5), match='21'>

re.search 扫描整个字符串, 第一个匹配的字符串返回为 match 对象

p.search 扫描指定范围, 不指定就全部扫描

r = 'python\d.\d'  
s = 'python3.0, python3.8'  
p = re.compile(r)  
a = re.search(r, s)  
b = p.search(s)  
c = p.search(s, 1)  
a
<re.Match object; span=(0, 9), match='python3.0'>
b
<re.Match object; span=(0, 9), match='python3.0'>
c
<re.Match object; span=(11, 20), match='python3.8'>

sub 替换字符串中的匹配项, 可以控制替换次数, 还可传入函数高级替换

r = '[,.]'  
s = '去掉,逗号和.句号.'  
p = re.compile(r)  
re.sub(p, '', s)
'去掉逗号和句号'
p.sub('', s, 1)  
'去掉逗号和.句号.'

findall 已经介绍过

r = r'\D+(\d+)\D+(\d+)\D+(\d+)*'  
s = '分组提取123所有456数字78.'  
p = re.compile(r)  
re.findall(p, s)
[('123', '456', '78')]
p.findall(s, 8)  
[('456', '78', '')]

finditerfindall类似, 只是返回的是一个 match 迭代器

import re  

r = r'\D+(\d+)\D+(\d+)\D+(\d+)*'  
s = '分组提取123所有456数字78.'  
p = re.compile(r)  
a = re.finditer(p, s)  
b = p.finditer(s, 8)  
a
<callable_iterator at 0x18d4fa31460>
b
<callable_iterator at 0x18d4fa31430>
for i, j in zip(a, b):  
    print(i)  
    print(j)  
    print(i.group(1, 2, 3))  
    print(j.group(1, 2, 3))  
<re.Match object; span=(0, 16), match='分组提取123所有456数字78'>
<re.Match object; span=(8, 17), match='有456数字78.'>
('123', '456', '78')
('456', '78', None)

split 用匹配到的字符(或字符串), 将整个字符串分割返回列表, 可设置最大拆分数

r = '[\s\,-]'  
s = '将,每 个-字 拆,开'  
p = re.compile(r)  
re.split(p, s)
['将', '每', '个', '字', '拆', '开']
p.split(s, 2)  
['将', '每', '个-字 拆,开']
jupyter附件

Numpy 基础快速了解和查询

官方文档

numpy 是 python 的强大科学计算库, 它让 python 处理数据, 进行科学计算变得极其便捷, 高效

一, N 维数组 ndarray 对象

1, ndaraay 介绍

ndarray 是具有相同类型和大小的项目的多维容器, 它的数据存储效率和输入输出性能远远优于 python 中等价的数据结构

1.01, 理解 ndarray

创建 ndarray: np.array(), np.ndarray(), 下面只介绍 3 维以下数组, 理解它们的结构

import numpy as np  

# 一维  
a = np.array((2,3))  
# 二维,2 行 3 列
b = np.ndarray((2,3))
a
array([2, 3])
b
array([[0., 0., 0.],
       [0., 0., 0.]])
a.dtype, b.dtype # 类型
(dtype('int32'), dtype('float64'))
c = np.array((1,'2'))
c
array(['1', '2'], dtype='<U11')
type(c), type(c[0])
(numpy.ndarray, numpy.str_)

可见, ndarray 对象中的元素类型只有一种, 如上例,都统一成 Unicode, 元素位长是 11, 所以说 ndarray 是一个具有相同类型和大小的项目的多维容器

一维就是一行,二维就像一个表格, 横着是一行, 竖着是一列

# 三维  
np.ndarray((3, 3, 3))  
array([[[6.23042070e-307, 7.56587584e-307, 1.37961302e-306],
        [6.23053614e-307, 6.23053954e-307, 9.34609790e-307],
        [8.45593934e-307, 9.34600963e-307, 9.34598925e-307]],

       [[1.42418715e-306, 1.95819401e-306, 1.37961370e-306],
        [6.23054633e-307, 9.34598926e-307, 6.89804132e-307],
        [9.34598926e-307, 8.01091099e-307, 1.24610927e-306]],

       [[8.90094053e-307, 1.02361342e-306, 7.56593017e-307],
        [1.60219035e-306, 8.90111708e-307, 6.89806849e-307],
        [9.34601642e-307, 1.42410974e-306, 5.97819431e-322]]])

三维类似一个 excel 文件里的多个表格

1.02, ndarray 对象计算的优势

用实例来对比利用 ndarray 进行计算的优势

import random  
import time  
# 假设有如下列表和 array, 使其中每个数都变成它的平方  
a1 = [random.random() for i in range(100000000)]  
# a2 = np.array(a1)  

t1 = time.time()  
b = [i**2 for i in a1]  
t2 = time.time()  
a2 = np.array(a1)  
c = a2**2  
t3 = time.time()  
print(t2 - t1, t3 - t2, sep='\n')  
15.133688688278198
4.770834445953369

差距非常明显(如果 a2 在计算之前就已经是 ndarray 对象会更明显), numpy 有其自身机制(广播)实现快速计算

1.03, ndarray 的数据类型和常量

ndarray 对象数据的数据类型有 bool, int, float, U(字符串), O(python 对象)等, 查看数据类型用 dtype

import numpy as np  
a = np.array([1, 'a', np.nan])  
a.dtype, a[0].dtype, a[2].dtype  
(dtype('<U32'), dtype('<U1'), dtype('<U3'))
np.array(True).dtype  
dtype('bool')
np.array(123456).dtype  
dtype('int32')
np.array(3.1415926).dtype  
dtype('float64')
np.array(list).dtype  
dtype('O')

ndarray 对象数据的常量有 pi, e, nan (空值), inf (无穷大)等

np.pi  
3.141592653589793
np.e  
2.718281828459045
np.nan  
nan
np.inf  
inf

2, ndarray 的属性

常用 ndarray 对象的属性

a = np.array([[1, 2, 3], [4, 5, 6]])  
a  
array([[1, 2, 3],
       [4, 5, 6]])

查看数组形状 (行, 列), 这在数据处理和计算时非常有用, 例如遍历出所有行或列

a.shape  
(2, 3)
# 遍历所有列  
for i in range(a.shape[1]):  
    print(a[:, i])  
[1 4]
[2 5]
[3 6]

查看数组维度

a.ndim  
2

查看数组元素个数

a.size  
6

查看数组类型

a.dtype  
dtype('int32')

查看数组元素的大小, 字节为单位

a.itemsize  
4

数组转置, 就是行列互换

a.T  
array([[1, 4],
       [2, 5],
       [3, 6]])

二, ndarray 对象的切片操作

1, 切片取值

ndarray 的切片操作和 python 类似, 一维切片 [起始:结束:步长], 二维的切片 [起始行:结束行:步长, 起始列:结束列:步长][[行, ...], [列, ...]]

a = np.arange(20)  
a.shape = (4, 5)  
a 
array([[ 0,  1,  2,  3,  4],
       [ 5,  6,  7,  8,  9],
       [10, 11, 12, 13, 14],
       [15, 16, 17, 18, 19]])
# 第一行的第 1, 3, 5 个数  
a[0][0::2]  
array([0, 2, 4])
a[2, 3] 
13
a[0:2, 1:5]  
array([[1, 2, 3, 4],
       [6, 7, 8, 9]])
a[0:3:2, 0:4:2]  
array([[ 0,  2],
       [10, 12]])
a[[1, 3], 0:3]  
array([[ 5,  6,  7],
       [15, 16, 17]])

还可以通过逻辑运算取值

# 取出数组中的数翻倍之后小于 15 的数  
# 结果展开为 1 维  
a[a * 2 < 15]  
array([0, 1, 2, 3, 4, 5, 6, 7])
# & 与  
a[(a < 5) & (a > 1)]  
array([2, 3, 4])
# | 或  
a[(a < 3) | (a > 15)]  
array([ 0,  1,  2, 16, 17, 18, 19])
# ~ 非  
a[~ (a > 3)]  
array([0, 1, 2, 3])

2, 利用切片修改值

切片修改值, 其实就是将取到的值重新赋值

a[~ (a > 3)] = 1  
a  
array([[ 1,  1,  1,  1,  4],
       [ 5,  6,  7,  8,  9],
       [10, 11, 12, 13, 14],
       [15, 16, 17, 18, 19]])
# 将 1 修改为 nan, nan 是 float 类型,   
# 保证类型一致性, 需要将 a 转为 float  
a = a.astype(float)  
a[a == 1] = np.nan  
a  
array([[nan, nan, nan, nan,  4.],
       [ 5.,  6.,  7.,  8.,  9.],
       [10., 11., 12., 13., 14.],
       [15., 16., 17., 18., 19.]])
# 任何 nan 都不等于 nan,   
# 利用此特性取 nan, 或非 nan 的数  
a[a != a], a[a == a]  
(array([nan, nan, nan, nan]),
 array([ 4.,  5.,  6.,  7.,  8.,  9., 10., 11., 12., 13., 14., 15., 16.,
        17., 18., 19.]))
# 交换行, 交换列  
a[[0, 3], :] = a[[3, 0], :]  
a[:, [4, 0]] = a[:, [0, 4]]  
a  
array([[19., 16., 17., 18., 15.],
       [ 9.,  6.,  7.,  8.,  5.],
       [14., 11., 12., 13., 10.],
       [ 4., nan, nan, nan, nan]])

三, ndarray 对象的运算及其方法

1, ndarray 的运算

numpy 在运算时, 会通过广播机制, 将运算相对应到每个元素上:

数和数组计算, 数会和数组中的每一个元素进行计算

# 方法 reshape 用于设置数组的形状, 后面会说  
import numpy as np  

a = np.arange(9).reshape((3, 3))  
a  
array([[0, 1, 2],
       [3, 4, 5],
       [6, 7, 8]])
b = -a * 2  
b  
array([[  0,  -2,  -4],
       [ -6,  -8, -10],
       [-12, -14, -16]])

相同形状的多维数组之间的运算, 对应位置的元素进行计算

a + b  
array([[ 0, -1, -2],
       [-3, -4, -5],
       [-6, -7, -8]])
# 行相同  
a[1] + (a + b)  
array([[ 3,  3,  3],
       [ 0,  0,  0],
       [-3, -3, -3]])

二维一列的数组和多维数组之间的运算, 需要列元素个数相同, 然后一一对应并广播计算

# 列相同  
# a 的列取出来成了一维, 需要转为二维  
a[:, 1].reshape((3, 1)) + (a + b)  
array([[ 1,  0, -1],
       [ 1,  0, -1],
       [ 1,  0, -1]])

2, 常用 ndarray 对象的方法

就是直接用 ndarray 对象调用的方法

2.01, 形状操作

flatten 或 ravel 将多维数组展开为一维, 不改变原数组

a  
array([[0, 1, 2],
       [3, 4, 5],
       [6, 7, 8]])
# 默认参数 'C' 以行展开, 'F' 以列展开  
display(a.flatten())  
display(a.flatten('F'))  
a.ravel('F')  
array([0, 1, 2, 3, 4, 5, 6, 7, 8])



array([0, 3, 6, 1, 4, 7, 2, 5, 8])





array([0, 3, 6, 1, 4, 7, 2, 5, 8])

reshape 修改数组的形状, 不改变原数组, 行列数相乘要等于元素总数; resize 就地修改原数组形状

# 默认以行展开修改  
import numpy as np  
b = np.arange(12).reshape((3, 4))  
display(b)  
b.reshape((2, 6), order='F')  
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])





array([[ 0,  8,  5,  2, 10,  7],
       [ 4,  1,  9,  6,  3, 11]])
# 就地修改 b  
display(b)  
b.resize((4, 3))  
b  
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])





array([[ 0,  1,  2],
       [ 3,  4,  5],
       [ 6,  7,  8],
       [ 9, 10, 11]])

2.02, 转换类型

tolist 数组转列表

b.tolist()  
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10, 11]]

astype 改变数组类型

b.astype(float)  
array([[ 0.,  1.,  2.],
       [ 3.,  4.,  5.],
       [ 6.,  7.,  8.],
       [ 9., 10., 11.]])

2.03, 修改, 排序及查找

fill 给原数组重新填值, 类型不变

b[0].fill(2.01)  
b  
array([[ 2,  2,  2],
       [ 3,  4,  5],
       [ 6,  7,  8],
       [ 9, 10, 11]])

sort 就地排序, 参数 0 将列排序, 1 将行排序

a = np.array([[3, 0, 5], [8, 4, 6], [0, 7, 3]])  
a.sort(0)  
a  
array([[0, 0, 3],
       [3, 4, 5],
       [8, 7, 6]])
a.argsort() # 返回排序后的索引位置  
array([[0, 1, 2],
       [0, 1, 2],
       [2, 1, 0]], dtype=int64)

nonzero 返回非零元素的索引, 行坐标一个数组, 列坐标一个数组

a.nonzero()  
(array([0, 1, 1, 1, 2, 2, 2], dtype=int64),
 array([2, 0, 1, 2, 0, 1, 2], dtype=int64))

2.04, 计算

max 返回最大值, 给定轴将返回给定轴的最大值, argmax 返回索引, 与之对应的还有最小min 和 argmin

# 参数 0 列的最大值, 参数 1 行的最大值  
a = np.arange(1.01, 13.13, 1.01).reshape(3, 4)  
print(a)  
print(a.max(), a.max(axis=0), a.max(axis=1))  
[[ 1.01  2.02  3.03  4.04]
 [ 5.05  6.06  7.07  8.08]
 [ 9.09 10.1  11.11 12.12]]
12.12 [ 9.09 10.1  11.11 12.12] [ 4.04  8.08 12.12]
# 参数 0 行索引, 1 列索引  
print(a.argmax(), a.argmax(0), a.argmax(1))  
11 [2 2 2 2] [3 3 3]

ptp 返回极值或给定轴的极值(最大-最小)

a.ptp(), a.ptp(axis=0), a.ptp(axis=1)  
(11.11, array([8.08, 8.08, 8.08, 8.08]), array([3.03, 3.03, 3.03]))

clip 返回指定值之间的数组, 小于或大于指定值的, 用指定值填充

a.clip(2, 6)  
array([[2.  , 2.02, 3.03, 4.04],
       [5.05, 6.  , 6.  , 6.  ],
       [6.  , 6.  , 6.  , 6.  ]])

round 四舍五入到指定位数

a.round(1)  
array([[ 1. ,  2. ,  3. ,  4. ],
       [ 5. ,  6.1,  7.1,  8.1],
       [ 9.1, 10.1, 11.1, 12.1]])

sum 求和, cumsum 累积求和

print(a.sum()) # 总和  
print(a.sum(0)) # 列求和  
print(a.sum(1)) # 行求和  
78.78
[15.15 18.18 21.21 24.24]
[10.1  26.26 42.42]
print(a.cumsum()) # 全部累积  
print(a.cumsum(0)) # 列累积  
print(a.cumsum(1)) # 行累积  
[ 1.01  3.03  6.06 10.1  15.15 21.21 28.28 36.36 45.45 55.55 66.66 78.78]
[[ 1.01  2.02  3.03  4.04]
 [ 6.06  8.08 10.1  12.12]
 [15.15 18.18 21.21 24.24]]
[[ 1.01  3.03  6.06 10.1 ]
 [ 5.05 11.11 18.18 26.26]
 [ 9.09 19.19 30.3  42.42]]

类似还有 mean 均值, var 方差, std 标准差, prod 乘积, cumprod 累积乘积, 不在一一举例

all 数组所有元素都判断为 True, 返回 True, 而 any 只要任何一个 True 返回 True

a = np.array([1, 2])  
b = np.array([0, 0])  
a.all(), b.all(), a.any(), b.any()  
(True, False, True, False)

四, 创建或生成 ndarray 对象

1, 生成随机数

利用 numpy 生成随机数的模块 random, 创建数据来作例子非常有用. 先研究一下这个模块, 再研究通用函数

import numpy as np  

# random 生成 0 ~ 1 之间的随机数  
display(1 + np.random.random(3))  
np.random.random((2,3)) * 10  
array([1.33317465, 1.03829035, 1.19401465])





array([[2.13175522, 1.41162992, 9.67005144],
       [0.03480687, 4.63431381, 5.88380759]])
# 同上, 传参方式不一样  
display(np.random.rand(3))  
np.random.rand(1, 3, 2)  
array([0.60852454, 0.70296852, 0.31611002])





array([[[0.76136557, 0.14321424],
        [0.01322461, 0.89676522],
        [0.71400795, 0.00755323]]])
# 返回指定范围内的随机整数 [a,b) 包含 a 不包含 b, 不指定 b 则 [0,a)  
display(np.random.randint(3, size=3))  
np.random.randint(1, 4, (2, 3))  
array([0, 2, 0])





array([[2, 2, 1],
       [3, 2, 3]])
# 生成的随机数服从标准正态分布  
display(np.random.randn(3))  
np.random.randn(3, 2)  
array([-1.46427887,  0.87862733, -0.98698207])





array([[ 0.51350851, -0.67478546],
       [ 0.543379  ,  0.63665877],
       [-1.31200705, -0.30697352]])

类似还有拉普拉斯分布 laplace, 逻辑分布 logistic, 正态分布 normal 等, 可以设置分布的参数

np.random.logistic(5, 2, (2, 3))  
array([[2.40647744, 5.62327677, 5.8642342 ],
       [4.10741882, 7.65751053, 4.99483744]])
# 打乱原数组元素, 二维时只能打乱行的顺序  
a = np.arange(5)  
b = np.arange(9).reshape(3, 3)  
np.random.shuffle(a)  
np.random.shuffle(b)  
a, b  
(array([0, 2, 4, 1, 3]),
 array([[3, 4, 5],
        [6, 7, 8],
        [0, 1, 2]]))
# 从一个一维数组里随机选择指定数量的元素,   
# 或随机生成 0 到指定整数(不包含)的随机数  
# replace=True 元素可以重复选择(默认)  
display(np.random.choice(5, 5, replace=False))  
np.random.choice(a, (2, 3))  
array([4, 3, 1, 2, 0])





array([[3, 2, 2],
       [3, 0, 0]])

2, 创建数组

np.empty((2, 3))  
array([[2.40647744, 5.62327677, 5.8642342 ],
       [4.10741882, 7.65751053, 4.99483744]])
a = np.eye(3)  
a  
array([[1., 0., 0.],
       [0., 1., 0.],
       [0., 0., 1.]])
np.ones((2, 3))  
array([[1., 1., 1.],
       [1., 1., 1.]])
np.ones_like(a)  
array([[1., 1., 1.],
       [1., 1., 1.],
       [1., 1., 1.]])
np.zeros((2, 3))  
array([[0., 0., 0.],
       [0., 0., 0.]])
np.full((2, 3), 3)  
array([[3, 3, 3],
       [3, 3, 3]])
np.array(list)  
array(<class 'list'>, dtype=object)
np.arange(3)  
array([0, 1, 2])
np.linspace(1, 9, 3)  
array([1., 5., 9.])
np.logspace(1, 3, 3)  
array([  10.,  100., 1000.])

五, 常用的通函数及 API

numpy 调用, 用来操作 ndarray 对象的函数

1, 修改数组形状

reshape 与 ndarray 对象的方法一样, 另有 resize 只能按行展开

a = np.random.choice(5, (2, 3))  
display(a)  
np.reshape(a,(1, 6), 'F')  
array([[0, 2, 2],
       [4, 1, 2]])





array([[0, 4, 2, 1, 2, 2]])
np.resize(a, (1, 6))  
array([[0, 2, 2, 4, 1, 2]])

ravel 展开为一维(ndarray 对象也有该方法)

np.ravel(a, 'F')  
array([0, 4, 2, 1, 2, 2])

2, 组合数组

concatenate, hstack, vstack 连接数组

# axis=0, 行方向连接(默认), axis=1, 列方向连接  
b = np.random.choice(6, (2, 3))  
np.concatenate((a, b), axis=1)  
array([[0, 2, 2, 2, 5, 5],
       [4, 1, 2, 2, 5, 2]])
np.hstack((a, b)) # 列方向连接  
array([[0, 2, 2, 2, 5, 5],
       [4, 1, 2, 2, 5, 2]])
np.vstack((a, b)) # 行方向连接  
array([[0, 2, 2],
       [4, 1, 2],
       [2, 5, 5],
       [2, 5, 2]])

stack 堆叠数组, 堆叠后维度增加 1

# axis=0, 行方向堆叠(默认), axis=1, 列方向堆叠  
np.stack((a[0], b[0])), np.stack((a[0], b[0]), axis=1)  
(array([[0, 2, 2],
        [2, 5, 5]]),
 array([[0, 2],
        [2, 5],
        [2, 5]]))

3, 拆分数组

split, hsplit, vsplit 将数组拆分, 返回为列表

np.split(a, 2), np.vsplit(a, 2), np.hsplit(a, 3)  
([array([[0, 2, 2]]), array([[4, 1, 2]])],
 [array([[0, 2, 2]]), array([[4, 1, 2]])],
 [array([[0],
         [4]]),
  array([[2],
         [1]]),
  array([[2],
         [2]])])

4, 添加, 删除元素

delete 删除指定索引的元素, 行或列

a, np.delete(a, 1) # 删除第 2 个元素  
(array([[0, 2, 2],
        [4, 1, 2]]),
 array([0, 2, 4, 1, 2]))
# 删除第 2 行, 或第 2 列  
np.delete(a, 1, axis=0), np.delete(a, 1, axis=1)  
(array([[0, 2, 2]]),
 array([[0, 2],
        [4, 2]]))

insert 在指定索引前插入元素, 行或列

np.insert(a, 1, 9)  
array([0, 9, 2, 2, 4, 1, 2])
np.insert(a, 1, 9, axis=0), np.insert(a, 1, [1, 2, 3], axis=0)  
(array([[0, 2, 2],
        [9, 9, 9],
        [4, 1, 2]]),
 array([[0, 2, 2],
        [1, 2, 3],
        [4, 1, 2]]))
np.insert(a, 1, [1, 3], axis=1)  
array([[0, 1, 2, 2],
       [4, 3, 1, 2]])

append 在数组末尾(或行,列末尾)加入元素, 行或列(维数必须相同)

np.append(a, 2)  
array([0, 2, 2, 4, 1, 2, 2])
np.append(a, [[2, 2, 2]], axis=0), np.append(a, [[2], [2]], axis=1)  
(array([[0, 2, 2],
        [4, 1, 2],
        [2, 2, 2]]),
 array([[0, 2, 2, 2],
        [4, 1, 2, 2]]))

unique 查找数组中的唯一值

a = np.random.choice(4, (2, 3))  
display(a)  
np.unique(a,return_index=True,  
             return_inverse=True,  
             return_counts=True, axis=None)  
array([[3, 0, 3],
       [1, 3, 1]])





(array([0, 1, 3]),
 array([1, 3, 0], dtype=int64),
 array([2, 0, 2, 1, 2, 1], dtype=int64),
 array([1, 2, 3], dtype=int64))

5, 重新排列元素

flip, fliplr, flipud 翻转数组

np.flip(a), np.flip(a, 0), np.flip(a, 1)  
(array([[1, 3, 1],
        [3, 0, 3]]),
 array([[1, 3, 1],
        [3, 0, 3]]),
 array([[3, 0, 3],
        [1, 3, 1]]))

sort 排序数组元素

np.sort(a)  
array([[0, 3, 3],
       [1, 1, 3]])

roll 滚动数组元素

np.roll(a, 1), np.roll(a, 1, axis=0), np.roll(a, 1, axis=1)  
(array([[1, 3, 0],
        [3, 1, 3]]),
 array([[1, 3, 1],
        [3, 0, 3]]),
 array([[3, 3, 0],
        [1, 1, 3]]))

6, 常用数学运算函数

import numpy as np  
# 固定随机种子  
np.random.seed(0)  
a = np.random.randint(1, 6, (3, 4))  
b = np.arange(12).reshape((3, 4))  
a, b  
(array([[5, 1, 4, 4],
        [4, 2, 4, 3],
        [5, 1, 1, 5]]),
 array([[ 0,  1,  2,  3],
        [ 4,  5,  6,  7],
        [ 8,  9, 10, 11]]))
np.power(b, a) # b**a  
array([[     0,      1,     16,     81],
       [   256,     25,   1296,    343],
       [ 32768,      9,     10, 161051]], dtype=int32)
np.divmod(b, a) # 商 和 余  
(array([[ 0,  1,  0,  0],
        [ 1,  2,  1,  2],
        [ 1,  9, 10,  2]], dtype=int32),
 array([[0, 0, 2, 3],
        [0, 1, 2, 1],
        [3, 0, 0, 1]], dtype=int32))
np.fabs(-a), np.abs(-a) # 取绝对值  
(array([[5., 1., 4., 4.],
        [4., 2., 4., 3.],
        [5., 1., 1., 5.]]),
 array([[5, 1, 4, 4],
        [4, 2, 4, 3],
        [5, 1, 1, 5]]))
c = np.random.rand(3, 4) * 10  
display(c)  
np.rint(c) # 舍入最接近的整数  
array([[3.83441519, 7.91725038, 5.2889492 , 5.68044561],
       [9.25596638, 0.71036058, 0.871293  , 0.20218397],
       [8.32619846, 7.78156751, 8.70012148, 9.78618342]])





array([[ 4.,  8.,  5.,  6.],
       [ 9.,  1.,  1.,  0.],
       [ 8.,  8.,  9., 10.]])
np.exp(a).round(1) # np.e ** a  
array([[148.4,   2.7,  54.6,  54.6],
       [ 54.6,   7.4,  54.6,  20.1],
       [148.4,   2.7,   2.7, 148.4]])
b = np.exp2(a) # 2**a  
a, b  
(array([[5, 1, 4, 4],
        [4, 2, 4, 3],
        [5, 1, 1, 5]]),
 array([[32.,  2., 16., 16.],
        [16.,  4., 16.,  8.],
        [32.,  2.,  2., 32.]]))
np.log(a)  
np.log2(a)  
np.log10(a)  
np.sqrt(a) # 开根号  
np.gcd(a, b.astype(int)) # 最大公约数  
np.lcm(a, b.astype(int)) # 最小公倍数  
array([[160,   2,  16,  16],
       [ 16,   4,  16,  24],
       [160,   2,   2, 160]])
np.sin(a)  
np.cos(a)  
np.tan(a)  
array([[-3.38051501,  1.55740772,  1.15782128,  1.15782128],
       [ 1.15782128, -2.18503986,  1.15782128, -0.14254654],
       [-3.38051501,  1.55740772,  1.55740772, -3.38051501]])
np.greater(a, b) # a > b  
np.less(a, b) # a < b  
a != b # np.not_equal(a, b)  
(a > 2) | ~(b < 5) # np.logical_or(a>2, ~(b<5))  
array([[ True, False,  True,  True],
       [ True, False,  True,  True],
       [ True, False, False,  True]])
# 两者取其大, 同 np.fmax(a*5, b) 对应还有 minimum, fmin  
np.maximum(a*5, b)   
array([[32.,  5., 20., 20.],
       [20., 10., 20., 15.],
       [32.,  5.,  5., 32.]])
np.isinf(a) # 判断是否正负无穷大  
np.isnan(a) # 判断是否 nan  
array([[False, False, False, False],
       [False, False, False, False],
       [False, False, False, False]])
np.modf(c) # 分别返回小数和整数部分  
(array([[0.83441519, 0.91725038, 0.2889492 , 0.68044561],
        [0.25596638, 0.71036058, 0.871293  , 0.20218397],
        [0.32619846, 0.78156751, 0.70012148, 0.78618342]]),
 array([[3., 7., 5., 5.],
        [9., 0., 0., 0.],
        [8., 7., 8., 9.]]))
np.fmod(b, a) # 返回余数  
array([[2., 0., 0., 0.],
       [0., 0., 0., 2.],
       [2., 0., 0., 2.]])
c = np.random.rand(3, 4) * 10  
display(c)  
np.floor(c) # 向下取整  
np.ceil(c) # 向上取整  
array([[7.99158564, 4.61479362, 7.80529176, 1.18274426],
       [6.39921021, 1.43353287, 9.44668917, 5.21848322],
       [4.1466194 , 2.64555612, 7.74233689, 4.56150332]])





array([[ 8.,  5.,  8.,  2.],
       [ 7.,  2., 10.,  6.],
       [ 5.,  3.,  8.,  5.]])

7, 字符串操作

字符串的操作函数, 和 python 的操作函数相似, 只是 numpy 作用到整个数组上

a = np.array([['a', 'b', 'c'], [20, 19, 18]])  
np.char.add(a, a)  
array([['aa', 'bb', 'cc'],
       ['2020', '1919', '1818']], dtype='<U22')
np.char.multiply(a, 3)  
array([['aaa', 'bbb', 'ccc'],
       ['202020', '191919', '181818']], dtype='<U33')
# 将每一个制表符替换成指定个数的空格  
b = np.array('\ta\t\tbc\td')  
np.char.expandtabs(b, 1)  
array(' a  bc d', dtype='<U8')
np.char.replace(a, '1', '2') # 替换  
array([['a', 'b', 'c'],
       ['20', '29', '28']], dtype='<U2')
np.char.title(a)  
array([['A', 'B', 'C'],
       ['20', '19', '18']], dtype='<U11')
b = np.char.zfill(a, 4)  
b  
array([['000a', '000b', '000c'],
       ['0020', '0019', '0018']], dtype='<U4')
np.char.count(b, '0') # 0 出现的次数  
array([[3, 3, 3],
       [3, 2, 2]])
np.char.str_len(a) # 字符串长度  
array([[1, 1, 1],
       [2, 2, 2]])

8, 索引与迭代

a = np.random.choice(4, (3, 4))  
display(a)  
np.nonzero(a) # 非 0 索引  
array([[2, 0, 0, 0],
       [1, 1, 2, 0],
       [0, 1, 3, 0]])





(array([0, 1, 1, 1, 2, 2], dtype=int64),
 array([0, 0, 1, 2, 1, 2], dtype=int64))
# 返回指定条件的元素的索引, 并可进行替换  
# 不设条件, 和 nonzero 一样  
np.where(a>0)  
(array([0, 1, 1, 1, 2, 2], dtype=int64),
 array([0, 0, 1, 2, 1, 2], dtype=int64))
# 大于 0 都换成 a, 否则都换成 b  
np.where(a>0, 'a', 'b')  
array([['a', 'b', 'b', 'b'],
       ['a', 'a', 'a', 'b'],
       ['b', 'a', 'a', 'b']], dtype='<U1')
np.nditer(a) # 将数组变成一个高效的迭代器  
<numpy.nditer at 0x2b410944d50>

9, 统计运算函数

# 下述与 ndarray 对象直接调用的方法一样  
a = np.random.rand(3, 4) * 5  
np.all(a)  
np.any(a)  
np.max(a)  
np.sum(a)  
np.mean(a)  
np.cumsum(a)  
np.var(a)  
np.std(a)  
np.clip(a, 1, 3)  
np.around(a, 2) # ndarray 对象的方法是 round  
# 等等  
array([[3.41, 1.8 , 2.19, 3.49],
       [0.3 , 3.33, 3.35, 1.05],
       [0.64, 1.58, 1.82, 2.85]])

任何数与 nan 计算都是 nan, 可以用相应的函数排除 nan

b = np.array([1, np.nan, 3, 4])  
np.sum(b), np.nansum(b)  
(nan, 8.0)
np.nancumsum(b)  
array([1., 1., 4., 8.])
jupyter附件

Pandas 基础快速了解和查询

官方文档

Pandas 是 Python 的核心数据分析支持库, 基于 NumPy 创建, 它使 python 成为强大而高效的数据分析环境

一, 数据结构

pandas 的数据结构是基于 numpy 的, 因此其有着 numpy 的基因, 许多操作和大部分函数与 numpy 类似

pandas 的数据结构其实是 numpy 数组数据对象 array 的容器, 在 pandas 中有了索引结构, 可以利用索引来取 array 或 array 中的元素

pandas 中最重要的数据结构是 Series 和 DataFrame

1, 创建 Series 和 DataFrame

创建时若不指定索引, 将自动生成(从 0 开始)

Series 是一维, 只有行索引, 而 DataFrame 有行和列索引

创建方式灵活多变, 可以查看参数, 根据要求传参进行创建

import numpy as np  
import pandas as pd  

s = pd.Series(range(3))
s
0    0
1    1
2    2
dtype: int64
df = pd.DataFrame(range(3))  
print(df)
   0
0  0
1  1
2  2
s.values
array([0, 1, 2], dtype=int64)
df.values
array([[0],
       [1],
       [2]], dtype=int64)

可见,pandas 的数据结构,是 numpy 的 array 对象的容器,着意味着 pandas 在处理数据时,可以使用 numpy 的所有函数和方法

s = pd.Series(range(3), index=list('abc'))  
s
a    0
b    1
c    2
dtype: int64
df = pd.DataFrame({'A': range(3), 'B': list('jkl')}, index=list('abc')) 
print(df)
   A  B
a  0  j
b  1  k
c  2  l
dict_1 = {'a': 0, 'b': 1, 'c': 2}  
dict_2 = {'a': range(3), 'b': list('jkl')}  
s = pd.Series(dict_1)  
s
a    0
b    1
c    2
dtype: int64
df1 = pd.DataFrame(dict_1, index=['A'])  
print(df1)
   a  b  c
A  0  1  2
df2 = pd.DataFrame(dict_2)  
print(df2) 
   a  b
0  0  j
1  1  k
2  2  l
# df2 每一列是一个类型的 array 对象
df2["a"].values, df2["b"].values
(array([0, 1, 2], dtype=int64), array(['j', 'k', 'l'], dtype=object))

2, Series 和 DataFrame 的常用属性

大部分属性和 numpy 一样

s = pd.Series(1, index=list('abc'))  
s
a    1
b    1
c    1
dtype: int64
dict_3 = {'a': range(3), 'b': list('jkl')}  
df = pd.DataFrame(dict_3)  
print(df)  
   a  b
0  0  j
1  1  k
2  2  l
# 查看形状, 形状不包括 索引  
s.shape, df.shape  
((3,), (3, 2))
# 获取索引  
s.index, df.index, df.columns  
(Index(['a', 'b', 'c'], dtype='object'),
 RangeIndex(start=0, stop=3, step=1),
 Index(['a', 'b'], dtype='object'))
# 查看元素个数  
s.size, df.size  
(3, 6)
# 查看数据类型  
s.dtype, df.dtypes  
(dtype('int64'),
 a     int64
 b    object
 dtype: object)
# 查看值  
s.values, df.values, df['a'].values  
(array([1, 1, 1], dtype=int64),
 array([[0, 'j'],
        [1, 'k'],
        [2, 'l']], dtype=object),
 array([0, 1, 2], dtype=int64))

3, Series 和 DataFrame 结构理解

从上述可以看出, pandas 数据结构的值, 是一个 array 对象. 对于 df, 每一列的值取出来也是一个 array 对象, 并且每一列可以是不同的数据类型

需要注意的是, DataFrame 每一列取出来, 整体是一个 Series , 因此 DataFrame 又可以看成 Series 的容器

s = pd.Series(1, index=list('abc')) 
dict_3 = {'a': range(3), 'b': list('jkl')}  
df = pd.DataFrame(dict_3)  

type(s), type(df['a']), type(df)  
(pandas.core.series.Series,
 pandas.core.series.Series,
 pandas.core.frame.DataFrame)

Series 和 DataFrame 可以有多层索引

s.index = [['a', 'b', 'c'], [1, 2, 3]]
s
a  1    1
b  2    1
c  3    1
dtype: int64
df.index = [['a', 'b', 'c'], [1, 2, 3]]  
print(df)
     a  b
a 1  0  j
b 2  1  k
c 3  2  l

二, 数据结构操作, 处理与计算

1, 查看 Series 和 DataFrame 数据信息常用方法

a = np.random.randint(1, 9, (6, 2))  
s = pd.Series(a[:, 0])  
s
0    4
1    7
2    4
3    8
4    1
5    8
dtype: int32
df = pd.DataFrame(a)  
print(df) 
   0  1
0  4  4
1  7  4
2  4  8
3  8  3
4  1  3
5  8  1
# 查看前 5 行(默认)  
s.head()  
print(df.head())
   0  1
0  4  4
1  7  4
2  4  8
3  8  3
4  1  3
# 查看后 5 行(默认)  
s.tail()  
print(df.tail())  
   0  1
1  7  4
2  4  8
3  8  3
4  1  3
5  8  1
# 查看详情, Series 没有该方法  
df.info()  
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 6 entries, 0 to 5
Data columns (total 2 columns):
 #   Column  Non-Null Count  Dtype
---  ------  --------------  -----
 0   0       6 non-null      int32
 1   1       6 non-null      int32
dtypes: int32(2)
memory usage: 176.0 bytes
# 查看统计量  
s.describe()  
print(df.describe())  
              0         1
count  6.000000  6.000000
mean   5.333333  3.833333
std    2.804758  2.316607
min    1.000000  1.000000
25%    4.000000  3.000000
50%    5.500000  3.500000
75%    7.750000  4.000000
max    8.000000  8.000000

2, 数据的直接计算

Series 和 DataFrame 的数据可以像 numpy 的数组一样直接进行计算, 索引相同的行、列进行计算, 索引不同的 NaN 填充

a = np.random.randint(1, 9, (6, 2))  
s = pd.Series(a[:, 0])  
df = pd.DataFrame(a)  

print(df)
   0  1
0  6  2
1  1  6
2  5  1
3  3  2
4  5  5
5  2  6
df1 = pd.DataFrame(a, columns=[1, 'b'])  
print(df1 * df)  
    0   1   b
0 NaN  12 NaN
1 NaN   6 NaN
2 NaN   5 NaN
3 NaN   6 NaN
4 NaN  25 NaN
5 NaN  12 NaN
s
0    6
1    1
2    5
3    3
4    5
5    2
dtype: int32
s1 = pd.Series(a[:, 0], index=['a', 1, 2, 3, 4, 5])  
s1
a    6
1    1
2    5
3    3
4    5
5    2
dtype: int32
s**2 + s1  
0     NaN
1     2.0
2    30.0
3    12.0
4    30.0
5     6.0
a     NaN
dtype: float64
# 笛卡儿积展开  
print(s + df)
    0  1   2   3   4   5
0  12  3 NaN NaN NaN NaN
1   7  7 NaN NaN NaN NaN
2  11  2 NaN NaN NaN NaN
3   9  3 NaN NaN NaN NaN
4  11  6 NaN NaN NaN NaN
5   8  7 NaN NaN NaN NaN

3, Series 和 DataFrame 下标和索引切片操作

3.01, Series 和 DataFrame 下标切片取值

Series 和 DataFrame 下标切片取值有许多不同之处

s1 = pd.Series(a[:, 0], index=['a', 1, 2, 3, 4, 5])  
s1
a    6
1    1
2    5
3    3
4    5
5    2
dtype: int32
a = np.random.randint(1, 9, (6, 2))  
df1 = pd.DataFrame(a, columns=[1, 'b'])  
print(df1)
   1  b
0  6  5
1  3  8
2  8  7
3  1  5
4  7  4
5  6  3
# 此方法 DataFrame 只能取行, 不能取列  
s1[1:3]
1    1
2    5
dtype: int32
print(df1[0:2])
   1  b
0  6  5
1  3  8
s1[[1, 4]]  
1    1
4    5
dtype: int32

同 numpy 一样, 可以条件取值, 此方法常用来筛选和重新赋值等

s1[s1>4]
a    6
2    5
4    5
dtype: int32
print(df1[df1>5])
     1    b
0  6.0  NaN
1  NaN  8.0
2  8.0  7.0
3  NaN  NaN
4  7.0  NaN
5  6.0  NaN
print(df1[df1 == 4])  
    1    b
0 NaN  NaN
1 NaN  NaN
2 NaN  NaN
3 NaN  NaN
4 NaN  4.0
5 NaN  NaN

3.02, Series 和 DataFrame 索引切片取值

索引切片取值, 左右都包含

s1 = pd.Series(a[:, 0], index=['a', 1, 2, 3, 4, 5])  
s1
a    6
1    3
2    8
3    1
4    7
5    6
dtype: int32
a = np.random.randint(1, 9, (6, 2))  
df1 = pd.DataFrame(a, columns=[1, 'b'])  
print(df1) 
   1  b
0  3  5
1  7  3
2  8  7
3  7  6
4  3  7
5  5  4
# 需要将索引转换类型  
s1.index = s1.index.astype(str)  
df1.index = df1.index.astype(str) 
s1['a':'2']
a    6
1    3
2    8
dtype: int32
print(df1['1':'3'])  
   1  b
1  7  3
2  8  7
3  7  6
print(df1['1':'3'][[1]])
   1
1  7
2  8
3  7
df1['b']
0    5
1    3
2    7
3    6
4    7
5    4
Name: b, dtype: int32
print(df1[['b', 1]])  
   b  1
0  5  3
1  3  7
2  7  8
3  6  7
4  7  3
5  4  5

3.03, 用 iloc 方法取值(推荐)

上述的取值太复杂麻烦, iloc 方法传入下标取值更方便且条理清晰. 存在多层索引时, iloc 按最内层索引取值

s1 = pd.Series(a[:, 0], index=['a', 1, 2, 3, 4, 5])  
s1
a    3
1    7
2    8
3    7
4    3
5    5
dtype: int32
a = np.random.randint(1, 9, (6, 2))  
df1 = pd.DataFrame(a, columns=[1, 'b'])  
print(df1) 
   1  b
0  8  8
1  6  2
2  1  6
3  6  3
4  8  3
5  3  8
# 右不包含  
s1.iloc[0:3]
a    3
1    7
2    8
dtype: int32
print(df1.iloc[0:5:2])
   1  b
0  8  8
2  1  6
4  8  3
print(df1.iloc[[1, 4, 2], 0:])  
   1  b
1  6  2
4  8  3
2  1  6

3.04, 用 loc 方法

loc 方法取值左右都包含, 传入索引标签取值

s1 = pd.Series(a[:, 0], index=['a', 1, 2, 3, 4, 5])  
s1
a    8
1    6
2    1
3    6
4    8
5    3
dtype: int32
a = np.random.randint(1, 9, (6, 2))  
df1 = pd.DataFrame(a, columns=[1, 'b'])  
print(df1) 
   1  b
0  4  3
1  6  4
2  1  6
3  6  2
4  6  1
5  2  7
# 需要将索引转换类型  
s1.index = s1.index.astype(str)  
df1.index = df1.index.astype(str) 
s1.loc['a':'2']
a    8
1    6
2    1
dtype: int32
df1.loc['1':'3', 'b']  
1    4
2    6
3    2
Name: b, dtype: int32

3.05, 利用切片修改数据

利用切片修改数据, 其实就是取值重新赋值

import numpy as np  
import pandas as pd  

df = pd.DataFrame(np.random.rand(5, 4),  
                  index=list('abcde'),  
                  columns=list('ABCD'))  
print(df[df>0.5])  
          A         B         C         D
a  0.908869       NaN       NaN  0.584259
b       NaN       NaN  0.940409       NaN
c  0.947715  0.898426  0.745999       NaN
d       NaN  0.855820       NaN  0.742919
e       NaN       NaN       NaN  0.593532
# 将大于 0.5 的数据换成 nan  
df[df>0.5] = np.nan  
print(df)  
          A         B         C         D
a       NaN  0.019952  0.004443       NaN
b  0.391594  0.406059       NaN  0.021655
c       NaN       NaN       NaN  0.067905
d  0.238486       NaN  0.232429       NaN
e  0.378054  0.422029  0.386046       NaN
# 将 A 列全部改成 1 , 增加一列 E, 值为 0  
df['A'] = 1 # 与 df.A = 1 等价  
df['E'] = 0  
print(df)  
   A         B         C         D  E
a  1  0.019952  0.004443       NaN  0
b  1  0.406059       NaN  0.021655  0
c  1       NaN       NaN  0.067905  0
d  1       NaN  0.232429       NaN  0
e  1  0.422029  0.386046       NaN  0
# 行列值互换  
df.loc[['a', 'c'], ['A', 'C']] = df.loc[['c', 'a'], ['C', 'A']].to_numpy()  
print(df)  
          A         B         C         D  E
a       NaN  0.019952  1.000000       NaN  0
b  1.000000  0.406059       NaN  0.021655  0
c  0.004443       NaN  1.000000  0.067905  0
d  1.000000       NaN  0.232429       NaN  0
e  1.000000  0.422029  0.386046       NaN  0
# 只要 B 不为 nan 的数据  
print(df[df['B'] == df['B']])  
     A         B         C         D  E
a  NaN  0.019952  1.000000       NaN  0
b  1.0  0.406059       NaN  0.021655  0
e  1.0  0.422029  0.386046       NaN  0

三, Series 和 DataFrame 的处理和计算函数

1, 索引

在 pandas 里, 索引非常重要, 一个行索引, 通常就是数据的一条记录(例如一个人的信息), 一个列索引就是数据的一个特征(例如某个人的性别, 年龄等), 通过索引能够更方便数据处理与计算

import numpy as np  
import pandas as pd  

s = pd.Series(np.random.rand(4), index=list('abcd'))  
s
a    0.930171
b    0.263544
c    0.684870
d    0.702820
dtype: float64
df = pd.DataFrame(np.random.rand(4, 4),  
                  index=list('abcd'),  
                  columns=list('ABCD'))  
print(df)  
          A         B         C         D
a  0.236401  0.972351  0.030862  0.438897
b  0.774498  0.670181  0.379171  0.941319
c  0.701424  0.732319  0.882208  0.527572
d  0.442419  0.597335  0.258880  0.420447

reindex 索引重排, 新增的索引 nan 填充, 缺少索引的数据舍弃

# 新增 e 舍弃 d, 有许多参数可以调节  
s1 = s.reindex(list('bcae'))
s1
b    0.263544
c    0.684870
a    0.930171
e         NaN
dtype: float64
df2 = df.reindex(columns=list('BCAE'))  
print(df2)
          B         C         A   E
a  0.972351  0.030862  0.236401 NaN
b  0.670181  0.379171  0.774498 NaN
c  0.732319  0.882208  0.701424 NaN
d  0.597335  0.258880  0.442419 NaN

sort_index 索引排序

# 默认 True 升序, 许多参数可调, DataFrame 只排行索引  
s2 = s.sort_index(ascending=False)
s2
d    0.702820
c    0.684870
b    0.263544
a    0.930171
dtype: float64
df2 = df.sort_index(ascending=False)  
print(df2)
          A         B         C         D
d  0.442419  0.597335  0.258880  0.420447
c  0.701424  0.732319  0.882208  0.527572
b  0.774498  0.670181  0.379171  0.941319
a  0.236401  0.972351  0.030862  0.438897

rename, set_index, reset_index 设置索引

# 注意参数及传参方式  
s3 = s.rename(index={'a': 'f'})
s3
f    0.930171
b    0.263544
c    0.684870
d    0.702820
dtype: float64
df3 = df.rename(lambda x: x + x)  
print(df3)
           A         B         C         D
aa  0.236401  0.972351  0.030862  0.438897
bb  0.774498  0.670181  0.379171  0.941319
cc  0.701424  0.732319  0.882208  0.527572
dd  0.442419  0.597335  0.258880  0.420447
# Series 没有此方法
df4 = df.set_index([['a','b','c','d'], [1, 2, 3, 4]])  
print(df4)
            A         B         C         D
a 1  0.236401  0.972351  0.030862  0.438897
b 2  0.774498  0.670181  0.379171  0.941319
c 3  0.701424  0.732319  0.882208  0.527572
d 4  0.442419  0.597335  0.258880  0.420447
# 可以将某列设为索引, 默认不保留原列, 可设参数 drop 保留  
df5 = df.set_index('A')  
print(df5)  
                 B         C         D
A                                     
0.236401  0.972351  0.030862  0.438897
0.774498  0.670181  0.379171  0.941319
0.701424  0.732319  0.882208  0.527572
0.442419  0.597335  0.258880  0.420447
# 将索引设为列(drop=True将其删除), 多层索引时可选某层  
df6 = df4.reset_index(level=1)
print(df6)
   level_1         A         B         C         D
a        1  0.236401  0.972351  0.030862  0.438897
b        2  0.774498  0.670181  0.379171  0.941319
c        3  0.701424  0.732319  0.882208  0.527572
d        4  0.442419  0.597335  0.258880  0.420447
df7 = df5.reset_index()  
print(df7)
          A         B         C         D
0  0.236401  0.972351  0.030862  0.438897
1  0.774498  0.670181  0.379171  0.941319
2  0.701424  0.732319  0.882208  0.527572
3  0.442419  0.597335  0.258880  0.420447
# 索引可以设置名字, 设置索引还可以通过获取索引重新赋值  
# DataFrame 还可以转置  
df5.index.name
'A'
df5.index = list('abcd') # 重设索引后, 索引名消失  
print(df5)
          B         C         D
a  0.972351  0.030862  0.438897
b  0.670181  0.379171  0.941319
c  0.732319  0.882208  0.527572
d  0.597335  0.258880  0.420447
print(df5.T)  
          a         b         c         d
B  0.972351  0.670181  0.732319  0.597335
C  0.030862  0.379171  0.882208  0.258880
D  0.438897  0.941319  0.527572  0.420447

多层索引

ar = [['a', 'b', 'c'], [1, 2, 3]]  
tup = list(zip(*ar))  
pd.MultiIndex.from_tuples(tup)  
MultiIndex([('a', 1),
            ('b', 2),
            ('c', 3)],
           )
index = pd.MultiIndex.from_product(ar)  
s = pd.Series(range(9), index=index)  
s  
a  1    0
   2    1
   3    2
b  1    3
   2    4
   3    5
c  1    6
   2    7
   3    8
dtype: int64
pd.MultiIndex.from_frame(pd.DataFrame(np.random.randint(1, 9, (2, 3))))  
MultiIndex([(6, 5, 3),
            (8, 1, 3)],
           names=[0, 1, 2])

2, 增删与合并数据

import numpy as np  
import pandas as pd  

df = pd.DataFrame(np.random.randint(1, 9, (4, 5)))  
print(df)
   0  1  2  3  4
0  4  8  5  3  7
1  7  3  5  2  4
2  3  2  5  7  2
3  1  2  4  8  7

insert 在指定位置前插入数据

# Series 没有该方法  
df.insert(2, 'A', 1)  
print(df)
   0  1  A  2  3  4
0  4  8  1  5  3  7
1  7  3  1  5  2  4
2  3  2  1  5  7  2
3  1  2  1  4  8  7

append 在数据最后增加数据

df1 = pd.DataFrame(np.random.rand(4, 4),  
                  index=list('abcd'),  
                  columns=list('ABCD'))  
print(df1)
          A         B         C         D
a  0.748362  0.049567  0.603201  0.383037
b  0.658722  0.225040  0.107199  0.416646
c  0.024967  0.161487  0.338823  0.889825
d  0.584625  0.582264  0.228898  0.817555
# Series 只能传 Series  
df.iloc[0].append(pd.Series(8))  
0    4
1    8
A    1
2    5
3    3
4    7
0    8
dtype: int64
# DataFrame 可以在行后面增加, 也可在列后面新增  
print(df.append([1]))
   0    1    A    2    3    4
0  4  8.0  1.0  5.0  3.0  7.0
1  7  3.0  1.0  5.0  2.0  4.0
2  3  2.0  1.0  5.0  7.0  2.0
3  1  2.0  1.0  4.0  8.0  7.0
0  1  NaN  NaN  NaN  NaN  NaN
print(df.append(df1, ignore_index=True)) 
     0    1         A    2    3    4         B         C         D
0  4.0  8.0  1.000000  5.0  3.0  7.0       NaN       NaN       NaN
1  7.0  3.0  1.000000  5.0  2.0  4.0       NaN       NaN       NaN
2  3.0  2.0  1.000000  5.0  7.0  2.0       NaN       NaN       NaN
3  1.0  2.0  1.000000  4.0  8.0  7.0       NaN       NaN       NaN
4  NaN  NaN  0.748362  NaN  NaN  NaN  0.049567  0.603201  0.383037
5  NaN  NaN  0.658722  NaN  NaN  NaN  0.225040  0.107199  0.416646
6  NaN  NaN  0.024967  NaN  NaN  NaN  0.161487  0.338823  0.889825
7  NaN  NaN  0.584625  NaN  NaN  NaN  0.582264  0.228898  0.817555

drop 删除指定数据

df['A'].drop(1), df.drop(1, axis=1)  
(0    1
 2    1
 3    1
 Name: A, dtype: int64,
    0  A  2  3  4
 0  4  1  5  3  7
 1  7  1  5  2  4
 2  3  1  5  7  2
 3  1  1  4  8  7)

concat 主要用于行索引的合并

print(df)
   0  1  A  2  3  4
0  4  8  1  5  3  7
1  7  3  1  5  2  4
2  3  2  1  5  7  2
3  1  2  1  4  8  7
print(df1)
          A         B         C         D
a  0.748362  0.049567  0.603201  0.383037
b  0.658722  0.225040  0.107199  0.416646
c  0.024967  0.161487  0.338823  0.889825
d  0.584625  0.582264  0.228898  0.817555
# 默认按列索引合并, 保留合并后的全部索引, 缺失用 nan 填充  
# join 参数可控制合并的方式: inner 只留下都有的索引  
print(pd.concat([df, df1]))
     0    1         A    2    3    4         B         C         D
0  4.0  8.0  1.000000  5.0  3.0  7.0       NaN       NaN       NaN
1  7.0  3.0  1.000000  5.0  2.0  4.0       NaN       NaN       NaN
2  3.0  2.0  1.000000  5.0  7.0  2.0       NaN       NaN       NaN
3  1.0  2.0  1.000000  4.0  8.0  7.0       NaN       NaN       NaN
a  NaN  NaN  0.748362  NaN  NaN  NaN  0.049567  0.603201  0.383037
b  NaN  NaN  0.658722  NaN  NaN  NaN  0.225040  0.107199  0.416646
c  NaN  NaN  0.024967  NaN  NaN  NaN  0.161487  0.338823  0.889825
d  NaN  NaN  0.584625  NaN  NaN  NaN  0.582264  0.228898  0.817555
df2 = pd.DataFrame(np.random.rand(5, 4),  
                  index=list('abcde'),  
                  columns=list('BCDE'))  
print(df2) 
          B         C         D         E
a  0.061604  0.505149  0.247496  0.474912
b  0.431163  0.828570  0.302236  0.695153
c  0.238852  0.998520  0.516351  0.665134
d  0.383425  0.684282  0.857654  0.621860
e  0.186235  0.006509  0.612608  0.875893
print(pd.concat([df1, df2], join='inner'))  
          B         C         D
a  0.049567  0.603201  0.383037
b  0.225040  0.107199  0.416646
c  0.161487  0.338823  0.889825
d  0.582264  0.228898  0.817555
a  0.061604  0.505149  0.247496
b  0.431163  0.828570  0.302236
c  0.238852  0.998520  0.516351
d  0.383425  0.684282  0.857654
e  0.186235  0.006509  0.612608

join 主要用于行索引的合并

# 默认以左边(df1)为基准, 相同列名需要加以区分  
# how 参数: outer 全保留, inner 只保留共同部分  
print(df1.join(df2, lsuffix='_1', rsuffix='_2'))
          A       B_1       C_1       D_1       B_2       C_2       D_2  \
a  0.748362  0.049567  0.603201  0.383037  0.061604  0.505149  0.247496   
b  0.658722  0.225040  0.107199  0.416646  0.431163  0.828570  0.302236   
c  0.024967  0.161487  0.338823  0.889825  0.238852  0.998520  0.516351   
d  0.584625  0.582264  0.228898  0.817555  0.383425  0.684282  0.857654   

          E  
a  0.474912  
b  0.695153  
c  0.665134  
d  0.621860  
print(df1.join(df2, how='outer', lsuffix='_1', rsuffix='_2'))  
          A       B_1       C_1       D_1       B_2       C_2       D_2  \
a  0.748362  0.049567  0.603201  0.383037  0.061604  0.505149  0.247496   
b  0.658722  0.225040  0.107199  0.416646  0.431163  0.828570  0.302236   
c  0.024967  0.161487  0.338823  0.889825  0.238852  0.998520  0.516351   
d  0.584625  0.582264  0.228898  0.817555  0.383425  0.684282  0.857654   
e       NaN       NaN       NaN       NaN  0.186235  0.006509  0.612608   

          E  
a  0.474912  
b  0.695153  
c  0.665134  
d  0.621860  
e  0.875893  

merge 合并

df1 = pd.DataFrame({'K': ['K0', 'K1', 'K2', 'K3'],
                    'A': ['A0', 'A1', 'M2', 'M3'],
                    'B': ['B0', 'B1', 'B2', 'B3']})
print(df1)
    K   A   B
0  K0  A0  B0
1  K1  A1  B1
2  K2  M2  B2
3  K3  M3  B3
df2 = pd.DataFrame({'K': ['K0', 'K1', 'K4', 'K5'],
                    'C': ['C0', 'C1', 'M2', 'M3'],
                    'D': ['D0', 'D1', 'D2', 'D3']})
print(df2)
    K   C   D
0  K0  C0  D0
1  K1  C1  D1
2  K4  M2  D2
3  K5  M3  D3
# 默认 inner, 全部列都保留,   
# 但只保留两者都有的列(K),且列内容相同(K0,K1)的 行, 行索引都舍弃  
print(pd.merge(df1, df2))
    K   A   B   C   D
0  K0  A0  B0  C0  D0
1  K1  A1  B1  C1  D1
# 可以选择以某一个为基准, 需要合并的列内容, 以及保留某一个的索引  
print(pd.merge(df1, df2, how='left')) # 以 df1 为准, 匹配不上的保留 df1  
    K   A   B    C    D
0  K0  A0  B0   C0   D0
1  K1  A1  B1   C1   D1
2  K2  M2  B2  NaN  NaN
3  K3  M3  B3  NaN  NaN
# df1 的 A 列与 df2 的 C 列有内容相同需要合并  
print(pd.merge(df1, df2, left_on='A', right_on='C')) 
  K_x   A   B K_y   C   D
0  K2  M2  B2  K4  M2  D2
1  K3  M3  B3  K5  M3  D3

3, 数据选择与处理

import numpy as np  
import pandas as pd  

np.random.seed(0)  
df = pd.DataFrame(np.random.randint(1, 9, (6, 6)),  
                  index=list('abcdef'),  
                  columns=list('ABCDEF'))  
print(df)
   A  B  C  D  E  F
a  5  8  6  1  4  4
b  4  8  2  4  6  3
c  5  8  7  1  1  5
d  3  2  7  8  8  7
e  1  2  6  2  6  1
f  2  5  4  1  4  6

sample 随机选择数据

# 可以指定数量, 也可以按比例选  
df.iloc[0].sample(2) # Series 也可  
E    4
B    8
Name: a, dtype: int32
print(df.sample(3))
   A  B  C  D  E  F
a  5  8  6  1  4  4
b  4  8  2  4  6  3
c  5  8  7  1  1  5
print(df.sample(frac=0.5, axis=1))  
   F  C  D
a  4  6  1
b  3  2  4
c  5  7  1
d  7  7  8
e  1  6  2
f  6  4  1

where 按条件选择数据, 且可替换, 替换的是条件之外的数据

df.A.where(df.A>3) # Series 也可  
a    5.0
b    4.0
c    5.0
d    NaN
e    NaN
f    NaN
Name: A, dtype: float64
df1 = df.where(df>1)  
print(df1)
     A  B  C    D    E    F
a  5.0  8  6  NaN  4.0  4.0
b  4.0  8  2  4.0  6.0  3.0
c  5.0  8  7  NaN  NaN  5.0
d  3.0  2  7  8.0  8.0  7.0
e  NaN  2  6  2.0  6.0  NaN
f  2.0  5  4  NaN  4.0  6.0
print(df.where(df==1, lambda x: x*x))  
    A   B   C   D   E   F
a  25  64  36   1  16  16
b  16  64   4  16  36   9
c  25  64  49   1   1  25
d   9   4  49  64  64  49
e   1   4  36   4  36   1
f   4  25  16   1  16  36

isin 生成布尔数组来选择数据

values = [3, 5, 7, 'a', 'c']  
df.C.isin(values)
a    False
b    False
c     True
d     True
e    False
f    False
Name: C, dtype: bool
df.A[df.C.isin(values)]  
c    5
d    3
Name: A, dtype: int32
print(df[df.isin(values)])
     A    B    C   D   E    F
a  5.0  NaN  NaN NaN NaN  NaN
b  NaN  NaN  NaN NaN NaN  3.0
c  5.0  NaN  7.0 NaN NaN  5.0
d  3.0  NaN  7.0 NaN NaN  7.0
e  NaN  NaN  NaN NaN NaN  NaN
f  NaN  5.0  NaN NaN NaN  NaN
print(df[df.index.isin(values)])  
   A  B  C  D  E  F
a  5  8  6  1  4  4
c  5  8  7  1  1  5

isna( isnull ) 和 notna ( notnull ) 生成布尔数组

# 可以 pd 调用, 也可 DataFrame 或 Series 调用  
pd.isna(df1.D)
a     True
b    False
c     True
d    False
e    False
f     True
Name: D, dtype: bool
df1.D.isnull()
a     True
b    False
c     True
d    False
e    False
f     True
Name: D, dtype: bool
print(df1.isna())  
       A      B      C      D      E      F
a  False  False  False   True  False  False
b  False  False  False  False  False  False
c  False  False  False   True   True  False
d  False  False  False  False  False  False
e   True  False  False  False  False   True
f  False  False  False   True  False  False

dropna 删除 nan, fillna 将 nan 填充

print(df1)
     A  B  C    D    E    F
a  5.0  8  6  NaN  4.0  4.0
b  4.0  8  2  4.0  6.0  3.0
c  5.0  8  7  NaN  NaN  5.0
d  3.0  2  7  8.0  8.0  7.0
e  NaN  2  6  2.0  6.0  NaN
f  2.0  5  4  NaN  4.0  6.0
# 默认有 nan 的行就删除, 参数 all: 全部 nan 才删除  
df1.A.dropna() # Series 也可  
a    5.0
b    4.0
c    5.0
d    3.0
f    2.0
Name: A, dtype: float64
print(df1.dropna())
     A  B  C    D    E    F
b  4.0  8  2  4.0  6.0  3.0
d  3.0  2  7  8.0  8.0  7.0
print(df1.dropna(axis=1, how='all'))  
     A  B  C    D    E    F
a  5.0  8  6  NaN  4.0  4.0
b  4.0  8  2  4.0  6.0  3.0
c  5.0  8  7  NaN  NaN  5.0
d  3.0  2  7  8.0  8.0  7.0
e  NaN  2  6  2.0  6.0  NaN
f  2.0  5  4  NaN  4.0  6.0
# 给定填充值, 默认全部填充,   
# 可以指定填充数, 填充方式  
df1.A.fillna(0) # Series 也可
a    5.0
b    4.0
c    5.0
d    3.0
e    0.0
f    2.0
Name: A, dtype: float64
print(df1.fillna(0))
     A  B  C    D    E    F
a  5.0  8  6  0.0  4.0  4.0
b  4.0  8  2  4.0  6.0  3.0
c  5.0  8  7  0.0  0.0  5.0
d  3.0  2  7  8.0  8.0  7.0
e  0.0  2  6  2.0  6.0  0.0
f  2.0  5  4  0.0  4.0  6.0
print(df1.fillna(0, limit=1)) # 每一列填充一个  
     A  B  C    D    E    F
a  5.0  8  6  0.0  4.0  4.0
b  4.0  8  2  4.0  6.0  3.0
c  5.0  8  7  NaN  0.0  5.0
d  3.0  2  7  8.0  8.0  7.0
e  0.0  2  6  2.0  6.0  0.0
f  2.0  5  4  NaN  4.0  6.0
# ffill 前面值填充, bfill 后面值填充  
print(df1.fillna(method='ffill'))
     A  B  C    D    E    F
a  5.0  8  6  NaN  4.0  4.0
b  4.0  8  2  4.0  6.0  3.0
c  5.0  8  7  4.0  6.0  5.0
d  3.0  2  7  8.0  8.0  7.0
e  3.0  2  6  2.0  6.0  7.0
f  2.0  5  4  2.0  4.0  6.0
print(df1.fillna(method='bfill'))  
     A  B  C    D    E    F
a  5.0  8  6  4.0  4.0  4.0
b  4.0  8  2  4.0  6.0  3.0
c  5.0  8  7  8.0  8.0  5.0
d  3.0  2  7  8.0  8.0  7.0
e  2.0  2  6  2.0  6.0  6.0
f  2.0  5  4  NaN  4.0  6.0

drop_duplicates 去重

df.iloc[0] = df.iloc[1]  
print(df)
   A  B  C  D  E  F
a  4  8  2  4  6  3
b  4  8  2  4  6  3
c  5  8  7  1  1  5
d  3  2  7  8  8  7
e  1  2  6  2  6  1
f  2  5  4  1  4  6
# 默认保留第 1 条数据  
df.A.drop_duplicates() # Series 也可 
a    4
c    5
d    3
e    1
f    2
Name: A, dtype: int32
print(df.drop_duplicates())  
   A  B  C  D  E  F
a  4  8  2  4  6  3
c  5  8  7  1  1  5
d  3  2  7  8  8  7
e  1  2  6  2  6  1
f  2  5  4  1  4  6

nlargest 选择某列(某几列)值最大的几条数据, 对应还有 nsmallest

print(df.nlargest(3, 'A'))
   A  B  C  D  E  F
c  5  8  7  1  1  5
a  4  8  2  4  6  3
b  4  8  2  4  6  3
print(df.nsmallest(3, 'A'))
   A  B  C  D  E  F
e  1  2  6  2  6  1
f  2  5  4  1  4  6
d  3  2  7  8  8  7
print(df.nlargest(3, ['C', 'D']))  
   A  B  C  D  E  F
d  3  2  7  8  8  7
c  5  8  7  1  1  5
e  1  2  6  2  6  1

filter 按索引查找数据, 可正则模糊查找

print(df.filter(['A', 'B']))
   A  B
a  4  8
b  4  8
c  5  8
d  3  2
e  1  2
f  2  5
print(df.filter(like='a', axis=0))
   A  B  C  D  E  F
a  4  8  2  4  6  3
print(df.filter(regex='c', axis=0))  
   A  B  C  D  E  F
c  5  8  7  1  1  5

assign 用于新增辅助列

print(df.assign(A1=df['A']/df['F']))
   A  B  C  D  E  F        A1
a  4  8  2  4  6  3  1.333333
b  4  8  2  4  6  3  1.333333
c  5  8  7  1  1  5  1.000000
d  3  2  7  8  8  7  0.428571
e  1  2  6  2  6  1  1.000000
f  2  5  4  1  4  6  0.333333
print(df.assign(A1=lambda x:x.A/x.F))  
   A  B  C  D  E  F        A1
a  4  8  2  4  6  3  1.333333
b  4  8  2  4  6  3  1.333333
c  5  8  7  1  1  5  1.000000
d  3  2  7  8  8  7  0.428571
e  1  2  6  2  6  1  1.000000
f  2  5  4  1  4  6  0.333333

clip 将过大或过小的数据去掉, 并填充指定值

# 小于指定值的填充为指定值小者, 大于的反之  
print(df.clip(2, 6))
   A  B  C  D  E  F
a  4  6  2  4  6  3
b  4  6  2  4  6  3
c  5  6  6  2  2  5
d  3  2  6  6  6  6
e  2  2  6  2  6  2
f  2  5  4  2  4  6
print(df.clip(df.A, df.A + 2, axis=0))  
   A  B  C  D  E  F
a  4  6  4  4  6  4
b  4  6  4  4  6  4
c  5  7  7  5  5  5
d  3  3  5  5  5  5
e  1  2  3  2  3  1
f  2  4  4  2  4  4

4, 数据分组聚合计算

聚合计算和 numpy 函数基本一样, 例如 sum, count, median, min, max, mean, var, std 等, 比较容易

更为重要的, 是将数据按需分组后再聚合运算

import numpy as np  
import pandas as pd  

np.random.seed(0)  
df = pd.DataFrame(np.random.randint(1, 9, (6, 6)),  
                  index=list('abcdef'),  
                  columns=list('ABCDEF'))  

df.A.where(df.A>3, 'M', inplace=True)  
df.A.where(df.A=='M', 'N', inplace=True)  
df.B.where(df.B>3, 'J', inplace=True)  
df.B.where(df.B=='J', 'K', inplace=True)  
df.C.where(df.C<5, np.nan, inplace=True)  
print(df)  
   A  B    C  D  E  F
a  N  K  NaN  1  4  4
b  N  K  2.0  4  6  3
c  N  K  NaN  1  1  5
d  M  J  NaN  8  8  7
e  M  J  NaN  2  6  1
f  M  K  4.0  1  4  6
# 默认按列加和, 将 nan 转换为 0 来计算  
df.sum()
A    NNNMMM
B    KKKJJK
C       6.0
D        17
E        29
F        26
dtype: object
df.sum(axis=1, numeric_only=True)  
a     9.0
b    15.0
c     7.0
d    23.0
e     9.0
f    15.0
dtype: float64
# 按行统计忽略 nan  
df.count(axis=1)
a    5
b    6
c    5
d    5
e    5
f    6
dtype: int64
print(df.set_index(["A", "B"]).groupby(level="A").count())  
   C  D  E  F
A            
M  1  3  3  3
N  1  3  3  3
print(df.set_index(["A", "B"]).groupby(level="B").count())  
   C  D  E  F
B            
J  0  2  2  2
K  2  4  4  4

value_counts 统计 Series 中每个值出现次数

# Series 的值统计, 也即是 DataFrame 的每一列中每个值的数量统计  
df.A.value_counts()
N    3
M    3
Name: A, dtype: int64
df.C.value_counts()  
2.0    1
4.0    1
Name: C, dtype: int64
# 可以统计索引, 可按百分比显示, 可以分组统计  
df.set_index('A').index.value_counts(normalize=True)
N    0.5
M    0.5
Name: A, dtype: float64
df.D.value_counts(bins=2)  
(0.992, 4.5]    5
(4.5, 8.0]      1
Name: D, dtype: int64

nunique 去重计数, 统计每一行或列不同值的数量

df.nunique()   
A    2
B    2
C    2
D    4
E    4
F    6
dtype: int64

quantile 计算分位数

print(df.quantile([0.3, 0.6]))
       C    D    E    F
0.3  2.6  1.0  4.0  3.5
0.6  3.2  2.0  6.0  5.0

cut 与 qcut 数据分箱

# 将某一列中的值分别分到一个范围中, 默认左不包含右包含  
# 这对分组非常有用, 例如年龄分段  
c = pd.cut(df.D, bins=[0, 3, 8], labels=['0到3', '3到8'])  
c  
a    0到3
b    3到8
c    0到3
d    3到8
e    0到3
f    0到3
Name: D, dtype: category
Categories (2, object): ['0到3' < '3到8']
# 根据数值的频率来选择间隔, 使每个分段里值的个数相同  
pd.qcut(df.D, q=2)
a    (0.999, 1.5]
b      (1.5, 8.0]
c    (0.999, 1.5]
d      (1.5, 8.0]
e      (1.5, 8.0]
f    (0.999, 1.5]
Name: D, dtype: category
Categories (2, interval[float64, right]): [(0.999, 1.5] < (1.5, 8.0]]
pd.qcut(df.D, q=2).value_counts()  
(0.999, 1.5]    3
(1.5, 8.0]      3
Name: D, dtype: int64

rank 用来给数据排名, 例如销售额, 成绩等

# 有多种排名方式可供选择, 举一例: 给 D 列排名  
df['排名'] = df.D.rank(method='dense', ascending=False)  
print(df) 
   A  B    C  D  E  F   排名
a  N  K  NaN  1  4  4  4.0
b  N  K  2.0  4  6  3  2.0
c  N  K  NaN  1  1  5  4.0
d  M  J  NaN  8  8  7  1.0
e  M  J  NaN  2  6  1  3.0
f  M  K  4.0  1  4  6  4.0

sort_values 数据排序

df.sort_values('排名', inplace=True)  
print(df)  
   A  B    C  D  E  F   排名
d  M  J  NaN  8  8  7  1.0
b  N  K  2.0  4  6  3  2.0
e  M  J  NaN  2  6  1  3.0
a  N  K  NaN  1  4  4  4.0
c  N  K  NaN  1  1  5  4.0
f  M  K  4.0  1  4  6  4.0

shift 数据移动, 索引不变

print(df.shift(2))
     A    B    C    D    E    F   排名
d  NaN  NaN  NaN  NaN  NaN  NaN  NaN
b  NaN  NaN  NaN  NaN  NaN  NaN  NaN
e    M    J  NaN  8.0  8.0  7.0  1.0
a    N    K  2.0  4.0  6.0  3.0  2.0
c    M    J  NaN  2.0  6.0  1.0  3.0
f    N    K  NaN  1.0  4.0  4.0  4.0
print(df.shift(-1, axis=1))  
   A    B  C  D  E    F  排名
d  J  NaN  8  8  7  1.0 NaN
b  K  2.0  4  6  3  2.0 NaN
e  J  NaN  2  6  1  3.0 NaN
a  K  NaN  1  4  4  4.0 NaN
c  K  NaN  1  1  5  4.0 NaN
f  K  4.0  1  4  6  4.0 NaN

agg 和 apply 传入函数进行高级聚合运算, 已存在的函数用字符串形式传入, 自定义函数传入函数名

df.agg('sum')
A     MNMNNM
B     JKJKKK
C        6.0
D         17
E         29
F         26
排名      18.0
dtype: object
print(df.agg(['max', 'min']))
     A  B    C  D  E  F   排名
max  N  K  4.0  8  8  7  4.0
min  M  J  2.0  1  1  1  1.0
print(df.apply(['max', 'min']))  
     A  B    C  D  E  F   排名
max  N  K  4.0  8  8  7  4.0
min  M  J  2.0  1  1  1  1.0

🔺🔺 groupby 按指定的列(行)中不同值分组, 与前面的聚合函数组合出无限的变化, 满足各种需求

# 分组后是一个迭代器, 可以查看分组, 获取分组  
df.groupby('A')
<pandas.core.groupby.generic.DataFrameGroupBy object at 0x0000021E8A8295B0>
df.groupby('A').groups 
{'M': ['d', 'e', 'f'], 'N': ['b', 'a', 'c']}
print(df.groupby('A').get_group('M'))  
   A  B    C  D  E  F   排名
d  M  J  NaN  8  8  7  1.0
e  M  J  NaN  2  6  1  3.0
f  M  K  4.0  1  4  6  4.0
# 分别取出 M 和 N 中 F 列任意排名的数据  
def get_second(x, m, n):  
    return x[x[m].rank(method='dense', ascending=False)==n]  

# 通过修改 n 参数取出任意排名  
print(df.groupby('A').apply(get_second, m='F', n=3.0))  
     A  B    C  D  E  F   排名
A                           
M e  M  J  NaN  2  6  1  3.0
N b  N  K  2.0  4  6  3  2.0
# 将上述 cut 分箱得到的数据用来分组统计  
print(df.groupby(c).count())  
     A  B  C  D  E  F  排名
D                        
0到3  4  4  1  4  4  4   4
3到8  2  2  1  2  2  2   2
# 对不同的列作不同的分组聚合运算  
print(df.groupby('A').agg({'E': ['mean', 'max'], 'F': 'sum'})) 
          E       F
       mean max sum
A                  
M  6.000000   8  14
N  3.666667   6  12

5, 数据透视与窗口函数

import numpy as np  
import pandas as pd  

np.random.seed(0)  
df = pd.DataFrame(np.random.randint(1, 20, (4, 5)))  
df.columns = list('ABCDE')  
print(df)  
    A   B   C  D   E
0  13  16   1  4   4
1   8  10  19  5   7
2  13   2   7  8  15
3  18   6  14  9  10

melt 将列索引展开成数据

# 默认全部展开  
df1 = pd.melt(df, id_vars=['A', 'B'], var_name='F', value_name='G')  
print(df1)
     A   B  F   G
0   13  16  C   1
1    8  10  C  19
2   13   2  C   7
3   18   6  C  14
4   13  16  D   4
5    8  10  D   5
6   13   2  D   8
7   18   6  D   9
8   13  16  E   4
9    8  10  E   7
10  13   2  E  15
11  18   6  E  10
print(pd.melt(df, id_vars=['A', 'B'], value_vars=['C']))  
    A   B variable  value
0  13  16        C      1
1   8  10        C     19
2  13   2        C      7
3  18   6        C     14

pivot 将行值展开成为列索引

print(df1.pivot(columns='F', values=['A', 'B'])) 
       A                 B            
F      C     D     E     C     D     E
0   13.0   NaN   NaN  16.0   NaN   NaN
1    8.0   NaN   NaN  10.0   NaN   NaN
2   13.0   NaN   NaN   2.0   NaN   NaN
3   18.0   NaN   NaN   6.0   NaN   NaN
4    NaN  13.0   NaN   NaN  16.0   NaN
5    NaN   8.0   NaN   NaN  10.0   NaN
6    NaN  13.0   NaN   NaN   2.0   NaN
7    NaN  18.0   NaN   NaN   6.0   NaN
8    NaN   NaN  13.0   NaN   NaN  16.0
9    NaN   NaN   8.0   NaN   NaN  10.0
10   NaN   NaN  13.0   NaN   NaN   2.0
11   NaN   NaN  18.0   NaN   NaN   6.0

pivot_table 与 groupby + 聚合函数 类似, 可以对表格进行各种需求的透视

df1.index = list('LMNLMNLMNLMN')  
df1.reset_index(inplace=True)  
df1.rename(columns={'index': 'Q'}, inplace=True)  
print(df1)
    Q   A   B  F   G
0   L  13  16  C   1
1   M   8  10  C  19
2   N  13   2  C   7
3   L  18   6  C  14
4   M  13  16  D   4
5   N   8  10  D   5
6   L  13   2  D   8
7   M  18   6  D   9
8   N  13  16  E   4
9   L   8  10  E   7
10  M  13   2  E  15
11  N  18   6  E  10
print(df1.pivot_table(index=['Q', 'F'], aggfunc='mean'))  
        A   B     G
Q F                
L C  15.5  11   7.5
  D  13.0   2   8.0
  E   8.0  10   7.0
M C   8.0  10  19.0
  D  15.5  11   6.5
  E  13.0   2  15.0
N C  13.0   2   7.0
  D   8.0  10   5.0
  E  15.5  11   7.0
print(df1.pivot_table(index='Q',  
                columns='F',  
                values='A',  
                aggfunc=['mean', 'sum']))  
   mean             sum        
F     C     D     E   C   D   E
Q                              
L  15.5  13.0   8.0  31  13   8
M   8.0  15.5  13.0   8  31  13
N  13.0   8.0  15.5  13   8  31

rolling 将数据依次移动指定尺寸的窗口并进行聚合运算

df1.rolling(3)  
Rolling [window=3,center=False,axis=0,method=single]
print(df1)
    Q   A   B  F   G
0   L  13  16  C   1
1   M   8  10  C  19
2   N  13   2  C   7
3   L  18   6  C  14
4   M  13  16  D   4
5   N   8  10  D   5
6   L  13   2  D   8
7   M  18   6  D   9
8   N  13  16  E   4
9   L   8  10  E   7
10  M  13   2  E  15
11  N  18   6  E  10
# 移动 3 条数据加和一次作为一条新数据,  
# 前面默认 nan 填充  
print(df1.rolling(3).sum())  
       A     B     G
0    NaN   NaN   NaN
1    NaN   NaN   NaN
2   34.0  28.0  27.0
3   39.0  18.0  40.0
4   44.0  24.0  25.0
5   39.0  32.0  23.0
6   34.0  28.0  17.0
7   39.0  18.0  22.0
8   44.0  24.0  21.0
9   39.0  32.0  20.0
10  34.0  28.0  26.0
11  39.0  18.0  32.0
# 可以设置最小观察值(必须小于移动尺寸)  
# 可以用高级函数聚合运算  
print(df1.rolling(len(df1), min_periods=1).sum())
        A      B      G
0    13.0   16.0    1.0
1    21.0   26.0   20.0
2    34.0   28.0   27.0
3    52.0   34.0   41.0
4    65.0   50.0   45.0
5    73.0   60.0   50.0
6    86.0   62.0   58.0
7   104.0   68.0   67.0
8   117.0   84.0   71.0
9   125.0   94.0   78.0
10  138.0   96.0   93.0
11  156.0  102.0  103.0
print(df1.rolling(2).agg(['sum', np.max]))  
       A           B           G      
     sum  amax   sum  amax   sum  amax
0    NaN   NaN   NaN   NaN   NaN   NaN
1   21.0  13.0  26.0  16.0  20.0  19.0
2   21.0  13.0  12.0  10.0  26.0  19.0
3   31.0  18.0   8.0   6.0  21.0  14.0
4   31.0  18.0  22.0  16.0  18.0  14.0
5   21.0  13.0  26.0  16.0   9.0   5.0
6   21.0  13.0  12.0  10.0  13.0   8.0
7   31.0  18.0   8.0   6.0  17.0   9.0
8   31.0  18.0  22.0  16.0  13.0   9.0
9   21.0  13.0  26.0  16.0  11.0   7.0
10  21.0  13.0  12.0  10.0  22.0  15.0
11  31.0  18.0   8.0   6.0  25.0  15.0
def f(x):  
    return x.iloc[0] * x.iloc[1]  

print(df1[['A', 'G']])
     A   G
0   13   1
1    8  19
2   13   7
3   18  14
4   13   4
5    8   5
6   13   8
7   18   9
8   13   4
9    8   7
10  13  15
11  18  10
print(df1.rolling(2)['A', 'G'].apply(f))  
        A      G
0     NaN    NaN
1   104.0   19.0
2   104.0  133.0
3   234.0   98.0
4   234.0   56.0
5   104.0   20.0
6   104.0   40.0
7   234.0   72.0
8   234.0   36.0
9   104.0   28.0
10  104.0  105.0
11  234.0  150.0

6, 文本字符串处理

文本字符串处理方法基本上和 python 内建字符串方法同名, 这些方法自动忽略 nan 进行处理

方法较多, 常用的举几个例子:

s = pd.Series(['A_1', 'B_2', 'C_3', np.nan],  
              index=['A_a', 'B_b', 'C_c', 'D'])  
s  
A_a    A_1
B_b    B_2
C_c    C_3
D      NaN
dtype: object
# 取值  
s.str[0]
A_a      A
B_b      B
C_c      C
D      NaN
dtype: object
s.str[:2]
A_a     A_
B_b     B_
C_c     C_
D      NaN
dtype: object
s.index.str[2]  
Index(['a', 'b', 'c', nan], dtype='object')
# 拆分  
s.str.split('_')
A_a    [A, 1]
B_b    [B, 2]
C_c    [C, 3]
D         NaN
dtype: object
s.str.split('_').str.get(0)
A_a      A
B_b      B
C_c      C
D      NaN
dtype: object
s.str.split('_').str[1]  
A_a      1
B_b      2
C_c      3
D      NaN
dtype: object
s
A_a    A_1
B_b    B_2
C_c    C_3
D      NaN
dtype: object
print(s.str.split('_', expand=True))  
       0    1
A_a    A    1
B_b    B    2
C_c    C    3
D    NaN  NaN
# 替换, 默认正则匹配, 可传入函数高级匹配  
s.str.replace('_', '')
A_a     A1
B_b     B2
C_c     C3
D      NaN
dtype: object
s.index.str.replace('_', '')  
Index(['Aa', 'Bb', 'Cc', 'D'], dtype='object')
# 拼接  
s1 = s.str.split('_').str[0]  
s1
A_a      A
B_b      B
C_c      C
D      NaN
dtype: object
s1.str.cat() 
'ABC'
s1.str.cat(sep='_')
'A_B_C'
s1.str.cat(sep='_', na_rep='_')  
'A_B_C__'
s1.str.cat(['1', '2', '3', '4'], na_rep='_')  
A_a    A1
B_b    B2
C_c    C3
D      _4
dtype: object
# 提取  
s
A_a    A_1
B_b    B_2
C_c    C_3
D      NaN
dtype: object
print(s.str.extract(r'([ABC])_(\d)'))  
       0    1
A_a    A    1
B_b    B    2
C_c    C    3
D    NaN  NaN

三, 时间序列

时间序列对数据分析很重要, 很多数据都和时间发生的先后顺序相关

date_range 生成时间序列

import numpy as np  
import pandas as pd  

pd.date_range(start='20200701', end='20200705')  
DatetimeIndex(['2020-07-01', '2020-07-02', '2020-07-03', '2020-07-04',
               '2020-07-05'],
              dtype='datetime64[ns]', freq='D')
# 可以指定生成个数与频率等  
pd.date_range(start='6/1/2020', periods=5, freq='10D')  
DatetimeIndex(['2020-06-01', '2020-06-11', '2020-06-21', '2020-07-01',
               '2020-07-11'],
              dtype='datetime64[ns]', freq='10D')

to_datetime 转换时间格式

1970年 1 月 1 日 00:00:00 UTC+00:00 时区的时刻称为 epoch time,记为 0,当前时间就是相对于 epoch time 的秒数

# 获取本地当前时间  
from datetime import datetime  
print(datetime.now())  
d = datetime.now().timestamp()  
print(datetime.fromtimestamp(d))  
d  
2022-08-20 19:48:43.236534
2022-08-20 19:48:43.237531





1660996123.237531
# 数字形式的时间, 用 to_datetime 转换为时间格式后与上述有差别,  
# 是由于时区的原因, 转换时区即可一样  
print(pd.to_datetime(d, utc=True, unit='s'))  
d = pd.Series(d)  
pd.to_datetime(d, utc=True, unit='s').dt.tz_convert('Asia/Shanghai')  
2022-08-20 11:48:43.237530880+00:00





0   2022-08-20 19:48:43.237530880+08:00
dtype: datetime64[ns, Asia/Shanghai]
# 各种日期格式的转换  
print(pd.to_datetime(['07-17-2020', '11-07-2020'], dayfirst=True))  
print(pd.to_datetime('2020年7月17日', format='%Y年%m月%d日'))  
pd.to_datetime(['jul 17, 2020',  
                '2020-07-17',  
                '20200717',  
                '2020/07/17',  
                '2020.07.17',   
                np.nan])  
DatetimeIndex(['2020-07-17', '2020-07-11'], dtype='datetime64[ns]', freq=None)
2020-07-17 00:00:00





DatetimeIndex(['2020-07-17', '2020-07-17', '2020-07-17', '2020-07-17',
               '2020-07-17', 'NaT'],
              dtype='datetime64[ns]', freq=None)
# 可以跳过非时间, 可以转换 DataFrame 但索引名是固定的名称  
print(pd.to_datetime(['2020.07.17', '日期'], errors='coerce'))  
df = pd.DataFrame({'year': [2019, 2020],  
              'month': [6, 7],  
              'day': [4, 5]})  
pd.to_datetime(df)  
DatetimeIndex(['2020-07-17', 'NaT'], dtype='datetime64[ns]', freq=None)





0   2019-06-04
1   2020-07-05
dtype: datetime64[ns]
df = pd.DataFrame(np.random.randint(0, 10, (5, 2)),  
                  index=pd.date_range('20180717', periods=5, freq='200D'))  
print(df)
            0  1
2018-07-17  4  3
2019-02-02  0  3
2019-08-21  5  0
2020-03-08  2  3
2020-09-24  8  1

时间索引取值, between_time 取时间段

print(df['2018':'2019'])
            0  1
2018-07-17  4  3
2019-02-02  0  3
2019-08-21  5  0
print(df['2019-01':'2020-01'])  
            0  1
2019-02-02  0  3
2019-08-21  5  0
df.index = pd.date_range('20200717', periods=5, freq='2H') 
print(df)
                     0  1
2020-07-17 00:00:00  4  3
2020-07-17 02:00:00  0  3
2020-07-17 04:00:00  5  0
2020-07-17 06:00:00  2  3
2020-07-17 08:00:00  8  1
print(df.between_time('3:00', '7:00')) 
                     0  1
2020-07-17 04:00:00  5  0
2020-07-17 06:00:00  2  3

时间序列作为数据的操作

df.index = pd.date_range('20180717', periods=5, freq='100D')  
df.index.name = '日期'  
df.reset_index(inplace=True)  
print(df)
          日期  0  1
0 2018-07-17  4  3
1 2018-10-25  0  3
2 2019-02-02  5  0
3 2019-05-13  2  3
4 2019-08-21  8  1
df.日期.dt.day  
0    17
1    25
2     2
3    13
4    21
Name: 日期, dtype: int64
df['月份'] = df['日期'].dt.month  
print(df)
          日期  0  1  月份
0 2018-07-17  4  3   7
1 2018-10-25  0  3  10
2 2019-02-02  5  0   2
3 2019-05-13  2  3   5
4 2019-08-21  8  1   8
print(df[df.日期.dt.month >= 5])
          日期  0  1  月份
0 2018-07-17  4  3   7
1 2018-10-25  0  3  10
3 2019-05-13  2  3   5
4 2019-08-21  8  1   8
print(df[df.月份 >= 5])
          日期  0  1  月份
0 2018-07-17  4  3   7
1 2018-10-25  0  3  10
3 2019-05-13  2  3   5
4 2019-08-21  8  1   8
d = df.日期.astype(str).str.split('-', expand=True)  
print(d)
      0   1   2
0  2018  07  17
1  2018  10  25
2  2019  02  02
3  2019  05  13
4  2019  08  21
print(df[d[0] =='2018'])  
          日期  0  1  月份
0 2018-07-17  4  3   7
1 2018-10-25  0  3  10

四, 数据的导入导出与可视化

1, pandas 可以导入导出多种格式的数据:

read_csv, to_csv
read_json, to_json
read_html, to_html
read_excel, to_excel
read_hdf, to_hdf
等等

# 默认读取第一个 sheet, 默认第一行为列索引  
df = pd.read_excel(r'./sheet.xlsx',  
                   sheet_name=0, header=0)  
print(df)  
   名次  战队名         说明
0   1  FPX      四包二战术
1   2   G2      个人能力强
2   3   IG       喜欢打架
3   4  SKT  Faker状态低迷
4   5  GRF      上单是短板
5   6  DWG        下路弱
6   7  FNC       欧洲强队
7   8  SPY        AD强
8   9  RNG        四保一
9  10   TL       北美强队
# 可以设置将某列作为行索引, 某列作为列索引  
df1 = pd.read_excel(r'./sheet.xlsx',  
                   sheet_name=1)  

print(df1)
   名次        上单
0   1   GIMGOOM
1   2    WUNDER
2   3      KHAN
3   4   FLANDER
4   5    THESHY
5   6    NUGURI
6   7     BWIPO
7   8   IPMPACT
8   9  LICORICE
9  10      HUNI
df2 = pd.read_excel(r'./sheet.xlsx',  
                   sheet_name=1,  
                   header=1,  
                   index_col=0)  
print(df2)
     GIMGOOM
1           
2     WUNDER
3       KHAN
4    FLANDER
5     THESHY
6     NUGURI
7      BWIPO
8    IPMPACT
9   LICORICE
10      HUNI
# 有时需要根据文件调节编码和引擎参数  
df = pd.read_csv(r'./ratings_chinses.csv',  
                 engine=None,  
                 encoding='gbk')  
print(df)  
      数量   收获  评分
0      1    1   4
1      2    3   4
2      3    6   4
3      4   47   5
4      5   50   5
..   ...  ...  ..
105  106   47   5
106  107   50   3
107  108   70   5
108  109  101   4
109  110  110   5

[110 rows x 3 columns]

2, 可视化

import matplotlib.pyplot as plt  

plt.rcParams['font.family'] = 'Microsoft YaHei'   
plt.rcParams['font.size'] = 12  

df.收获.plot()   

png

df['评分'].plot(kind='hist')   

png

df.plot(x='数量', y='评分')   

png

df.评分.plot.box()  

png

jupyter附件

Matpotlib 快速入门

matpotlib官网

1, 以一个例子开始

matpotlib 绘图非常灵活, 方法和参数繁多, 同样的效果, 可以有很多实现方法, 因此常常让人很混乱和困惑, 理解了绘图的层级结构, 使用起来更得心应手:

下面将其分四个层级来作说明: 画布 Figure, 绘图对象 Axes, 坐标和图像, 以及图像调整和辅助描述

# 导入库  
import matplotlib.pyplot as plt  

'''  
设置 rc 参数: 可以用来修改图形的各种默认属性  
包括窗体大小, 线条宽度, 颜色, 符号, 字体等等  
'''  
plt.rcParams['font.family'] = 'Microsoft YaHei' # 字体  
plt.rcParams['font.size'] = 18 # 全体字体大小  
plt.rcParams['legend.fontsize'] = 12 # 图例标签字体大小  
plt.rcParams['axes.unicode_minus'] = False # 正常显示符号  

# 创建画布对象 Figure, 可以设置大小等  
fig = plt.figure()  

# 用 add_subplot()在画布上面创建绘图对象 Axes, 默认创建一个  
# 也可以用 subplots() 直接将 Figure 和 Axes 一起创建  
ax = fig.add_subplot()  

# 在绘图对象 ax 上绘图, 并设置标签  
x = range(-9, 10)  
y = [n**3 for n in x]  
ax.plot(x, y, label='曲线')  

# 绘制点 P(5, 125)  
ax.scatter(5, 125)  

# 绘制曲线过 P 点的切线  
y1 = [3*(5**2)*(m - 5) + 125 for m in x]  
ax.plot(x, y1, label='切线')  

# 一次性设置 ax 的多个属性  
ax.set(title='y=x**3', # ax 标题  
       xlabel='x', # x轴标题  
       ylabel='y', # y轴标题  
       xlim=(-10,10), # x轴范围  
       ylim=(-500, 600), # y轴范围  
       xticks=range(-10, 11, 2), # x轴刻度和标签  
       yticks=range(-500, 600, 100) # y轴刻度和标签         
      )   
# 上述属性设置方法, 都可以改为如下方法:   
# ax.set_title() 或 plt.title()  

# 注释标注 P 点, (4, 160) 为 P 的坐标  
ax.text(4, 160, 'P')  

# 设置轴标签字体和倾斜度  
ax.tick_params(labelsize=12, labelrotation=20)  

# 去除非 x, y 轴的包围线  
ax.spines['right'].set_color('none')  
ax.spines['top'].set_color('none')  

# 显示每个图的标签  
plt.legend(fontsize=15)  

# 显示  
plt.show()  

png

通过上例, 能够对 matplotlib 绘图有初步了解:

先有一个画布”桌子”, 然后在上面放”画纸”, 有了”画纸”, 就可以开始画图, 然后按照需求去选”笔”, 布局, 着色, 顺便”作诗落款”注释一下

2, 绘制多个绘图对象 Axes

import math  

fig = plt.figure(figsize=(20, 12), dpi=120)  

# '222'表示第 2 行, 第 2 列的第 2 个位置  
ax1 = fig.add_subplot(222)  
ax1.plot(range(8), [x**2 for x in range(8)])  

# 可以逗号隔开  
ax2 = fig.add_subplot(2,2,3)  
ax2.plot(range(8), [math.sin(x) for x in range(8)])  

ax3 = fig.add_subplot(221)  

# gca() 获取当前绘图对象, 最后绘制的那个  
# 如果没有, 将创建一个新的  
pg = plt.gca()  

# 设置属性  
pg.set(title='最后绘制的图', xlabel='x轴')  
pg.set_ylabel('y轴', rotation=0) # 设置 y 轴标签水平显示  

pg.plot(range(10), range(10))  

# 将 y 轴移动到 x=4 处  
pg.spines['left'].set_position(('data', 4))  

# 添加网格线, 设置颜色, 透明度  
pg.grid(color='r', linestyle='-.', alpha=0.5)  

plt.show()  

png

有了层次结构的了解, 就可以更潇洒地直接使用 plt 绘图了, 用 plt 直接绘图, 如果不创建画布, 画布 和 绘图对象 自动生成

下面用常用的图形风格举例(参数非常多, 不做详细介绍):

3, 折线图

折线图一般用来反映数据的变化情况和趋势

上面例子中绘制的都是折线图, 下面 将折线图映射成一排排点, 比较数据的分散程度:

import numpy as np  

# 创建两组数据  
x1 = np.random.randint(0, 20, size=500)  
x2 = np.random.randint(2, 10, size=500)  

# 映射成两排点  
plt.figure(figsize=(15, 4))  
plt.ylim(-0.5, 1.5)  
plt.plot(x1, np.zeros(len(x1)),  
         ls='', # 线条设为空  
         marker='o', # 数据点的样式  
         ms=10, # 点的大小  
         label='x1')  
plt.plot(x2, np.ones(len(x2)), ls='', marker='o', ms=10, label='x2')  

# 绘出两组数据均值所在位置  
plt.axvline(x1.mean(),  
            0.1, 0.4, # 竖线的范围, 取值为 0~1 之间  
            ls='--', # 线条样式  
            label='x1均值')  
plt.axvline(x2.mean(), 0.6, 0.9, ls='-.', color='r', label='x2均值')  

# 划出 x2 的范围线  
plt.hlines(0.8, x2.min(), x2.max())  

plt.legend(fontsize=18)  
plt.show()  

png

4, 散点图

散点图一般用来反映数据之间关联性(类别和分布规律)

# 创建数据  
y = np.random.randn(2, 100)  

# 第一个类别  
plt.scatter(y[0], y[1], label='第一类')  

# 第二个类别  
plt.scatter(y[0] + 1, y[1] + 3, label='第二类')  

plt.legend()  
plt.show()  

png

5, 柱状图

柱状图一般用来比较数据的统计量, 对数据进行直观的比较

# 接下来 5 天下雨的概率  
prob = [0.81, 0.22, 0.63, 0.18, 0.74]  

# x 轴标签  
x = ['第一天', '第二天', '第三天', '第四天', '第五天']  

# 绘图并将图赋值给 rects  
rects = plt.bar(x, prob, width=0.5)  

# 设置 x 轴标签字体和倾斜度; y 轴范围  
plt.xticks(fontsize=12, rotation=45)  
plt.ylim(0, 1)  

# 在柱状图上添加概率标注  
for rect in rects:  
    height = rect.get_height()  
    plt.text(rect.get_x(), height + 0.01, str(height))  

plt.show()  

png

# 还可以横向绘制  
rects = plt.barh(x, prob, height=0.5)  

plt.yticks(fontsize=12, rotation=45)  
plt.xlim(0, 1)  

# 在柱状图上添加概率标注  
for rect in rects:  
    width = rect.get_width()  
    plt.text(width,  
             rect.get_y() + 0.5/2,  
             str(width),  
             va='center')  

plt.show() 

png

# 假设概率大于 0.5 就是下雨, 展示下雨和不下雨, 同时展示概率  
# 接下来 5 天下雨的概率  
prob1 = [0.81, 0.22, 0.63, 0.18, 0.74]  

# 不下雨的概率  
prob2 = 1 - np.array(prob)  

# 设置 x 轴标签  
x = ['第一天', '第二天', '第三天', '第四天', '第五天']  
m = ['下雨' if i > 0.5 else '不下' for i in prob1]  
n = [x[i] + m[i] for i in range(len(x))]  

# 绘图  
plt.bar(n, prob1, label='下雨的概率')  
plt.bar(n, prob2, label='不下雨的概率', bottom=prob1)  

plt.ylim(0, 1)  
plt.xticks(fontsize=12, rotation=45)  

plt.legend(fontsize=10, loc='best', bbox_to_anchor=(0.7, 1))  
plt.show()  

png

6, 直方图

直方图用来直观地反映数据的总体分布情况

# 创建数据  
np.random.seed(0)  
data = np.random.normal(30, 5, size=1000)  
# 30 是数据的均值, 5 是标准差  

# 绘制直方图  
plt.hist(data,  
         40, # 组数  
         density=True # 每组以占比显示  
        )  

# 绘制概率密度曲线  
x = np.arange(int(data.min()), int(data.max()))  
f = lambda x, mu, sigma: np.exp(-((x - mu)**2) / (2 * sigma**2)  
                               ) / (sigma * np.sqrt(2 * np.pi))  
y = f(x, 30, 5)  
plt.plot(x, y, ls='-', color='r')  

plt.show()  

png

上图可以利用基于 matpoltlib 的库 seaborn 绘制更方便:

import seaborn as sns  
sns.displot(data)  
plt.show() 

png

7, 饼图

饼图用来直观地反映各个类别占比

plt.rcParams['font.size'] = 14  
fig = plt.figure(figsize=(5, 6))  

# 准备数据  
x = [60, 45, 21, 12, 8, 2] # 每个类别数量  
labels = [f'{i}类' for i in range(1, len(x) + 1)] # 类别名  
explode = [0.02] * len(x) # 每一块离中心的距离  

plt.pie(x,  
        explode=explode,  
        labels=labels,  
        colors=None, # 颜色不配置, 使用默认  
        autopct='%.1f%%', # 设置百分比标签  
        pctdistance=0.7, # 设置百分比标签离中心的距离  
        shadow=False, # 设置阴影  
        labeldistance=1.05, # 设置类别标签离中心的距离  
        startangle=180, # 设置绘制起始位置  
        radius=1.1, # 设置饼图大小  
        counterclock=False, # 设置顺逆时针  
       )  

plt.legend(fontsize=16, loc='best', bbox_to_anchor=(1, 1))  
plt.show()  

png

拓展: 绘制三维立体图

from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt
from matplotlib import cm

ax = plt.figure().add_subplot(projection='3d')
X, Y, Z = axes3d.get_test_data(0.05)

ax.contour(X, Y, Z, cmap=cm.coolwarm)  # Plot contour curves

plt.show()

png

jupyter附件

描述统计

数理统计以概率论为基础, 研究大量随机现象的统计规律性. 分为 描述统计推断统计 , 在数据分析领域具有非常重要的地位

描述统计, 就是从总体数据中提取变量的主要信息(总和, 均值, 最大, 最多等), 从而从总体层面上, 对数据进行统计性描述. 通常配合绘制相关统计图进行辅助

统计学的变量类型

统计学中的变量指研究对象的特征(属性), 每个变量都有变量值和类型, 类型可分为:

类别变量 : 对研究对象定性, 分类

类别变量又可分为:

  • 有序类别变量: 描述对象等级或顺序等, 例如, 优良中差
  • 无序类别变量: 仅做分类, 例如 A, B 血型, 男女

数值变量 : 对研究对象定量描述

数值变量又可分为:

  • 离散变量: 取值只能用自然数或整数个单位计算, 例如统计人数
  • 连续变量: 在一定区间内可以任意取值, 例如计算身高

数值变量对加, 减, 求平均等操作有意义, 而类别变量无意义

统计量

描述统计所提取的统计信息, 称为统计量, 主要包括:

  • 类别分析: 频数, 频率
  • 集中趋势分析: 均值, 中位数, 众数, 分位数
  • 离散程度分析: 极差, 方差, 标准差
  • 描述分布形状: 偏度, 峰度

准备数据:

import numpy as np  
import pandas as pd  
import matplotlib.pyplot as plt  
import seaborn as sns  

plt.rcParams['font.family'] = 'SimHei'  
plt.rcParams['axes.unicode_minus'] = False  

# 正态分布  
data1 = np.around(np.random.normal(10, 3, 600)).reshape(-1, 1)  

# 左偏  
t1 = np.random.randint(1, 21, size=100)  
t2 = np.random.randint(21, 31, size=500)  
left_data = np.concatenate([t1, t2]).reshape(-1, 1)  

# 右偏  
t3 = np.random.randint(1, 11, size=500)  
t4 = np.random.randint(11, 21, size=100)  
right_data = np.concatenate([t3, t4]).reshape(-1, 1)  

# 类别  
type_data = np.random.randint(0, 2, size=600).reshape(-1, 1)  

data = np.concatenate([data1, left_data, right_data, type_data], axis=1)  
data = pd.DataFrame(data,  
                   columns=['data1', 'left_data', 'right_data', 'type_data'])  
# 随机取 10 条数据  
data.sample(10)  
  data1	left_data	right_data	type_data
202	13.0	27.0	8.0	0.0
595	12.0	23.0	15.0	0.0
523	11.0	21.0	20.0	1.0
259	12.0	29.0	8.0	0.0
498	12.0	24.0	3.0	0.0
110	8.0	27.0	1.0	0.0
65	7.0	12.0	5.0	0.0
231	13.0	25.0	2.0	0.0
321	8.0	30.0	3.0	0.0
544	5.0	29.0	19.0	1.0

a, 频数

数据中某个类别出现的次数称为该类别的频数

例如, 计算上述两个类别(0.01.0)出现的频数:

frequency = data['type_data'].value_counts()  
frequency 
0.0    309
1.0    291
Name: type_data, dtype: int64

b, 频率

数据中某个类别出现次数与总次数的比值称为该类别的频率

例如, 计算上述两个类别(0.01.0)出现的频率:

percentage = frequency * 100 / len(data)  
percentage  
0.0    51.5
1.0    48.5
Name: type_data, dtype: float64

c, 均值

平均值, 一组数据的总和除以数据的个数

d, 中位数

将一组数据按顺序排列, 位于最中间位置的值, 即是中位数, 如果数据个数为偶数, 取中间两个的平均值

e, 众数

一组数据中出现次数最多的值

通常三者的关系如下图所示:

注意点 :
数值变量通常使用均值和中值表示集中趋势, 类别变量则通常使用众数
正态分布下, 数据量足够多, 三者相同
均值使用所有数据计算, 容易受极端值影响, 中位数和众数则不会
众数在一组数据中可能不唯一

例, 计算字段 data1 的均值, 中位数和众数:

mean = data['data1'].mean()  
median = data['data1'].median()  
mode = data['data1'].mode()  
print(f'均值:{mean} 中位数:{median}\n众数:\n{mode}')  
均值:10.121666666666666 中位数:10.0
众数:
0    9.0
dtype: float64

f, 分位数

通过 n - 1 个分位, 将升序排列的数据分为 n 个区间, 使得每个区间数值个数相等(或近似相等), 则每个分位对应的数, 就是该 n 分位的分位数. 常用的有四分位数和百分位数

以四分位数为例:
第一个分位称为 1/4 分位(下四分位), 第二个称为 2/4 分位(中四分位), 第三个称为 3/4 分位(上四分位), 其中中四分位数, 其实就是中位数

求四分位的值:

  • 首先计算各个分位的位置
    index1 = (n - 1) * 0.25
    index2 = (n - 1) * 0.5
    index3 = (n - 1) * 0.75
    (index 从 0 开始, n 为元素的个数)

  • 根据位置计算各个分位的值
    index 为整数, 值就是相应的 index 对应的元素
    index 不为整数, 四分位位置介于 ceil(index) 和 floor(index) 之间, 加权计算分位值

例, 求 x 的四分位数:
index 为整数

x = np.arange(0, 9)  
n = len(x)  

index1 = (n - 1) * 0.25  
index2 = (n - 1) * 0.5    
index3 = (n - 1) * 0.75    

index = np.array([index1, index2, index3]).astype(np.int32)  
x[index]  
array([2, 4, 6])

index 不是整数

x = np.arange(0, 10)  
n = len(x)  

index1 = (n - 1) * 0.25  
index2 = (n - 1) * 0.5    
index3 = (n - 1) * 0.75    

index = np.array([index1, index2, index3])  
left = np.floor(index).astype(np.int32)  
right = np.ceil(index).astype(np.int32)  
weight, _ = np.modf(index) # 获取 index 整数和小数部分  

result = x[left] * (1 - weight) + x[right] * weight  
result  
array([2.25, 4.5 , 6.75])

Numpy 中计算分位数可直接用方法 np.quantilenp.percentile

np.quantile(x, q=[0.25, 0.5, 0.75]), np.percentile(x, q=[25, 50, 75])  
(array([2.25, 4.5 , 6.75]), array([2.25, 4.5 , 6.75]))

Pandas 中计算分位数可利用 describe (默认 4 分位)

s = pd.Series(x)  
s.describe()  
count    10.00000
mean      4.50000
std       3.02765
min       0.00000
25%       2.25000
50%       4.50000
75%       6.75000
max       9.00000
dtype: float64
s.describe().iloc[4:7]  
25%    2.25
50%    4.50
75%    6.75
dtype: float64

可自定义分位:

s.describe(percentiles=[0.15, 0.4, 0.8])  
count    10.00000
mean      4.50000
std       3.02765
min       0.00000
15%       1.35000
40%       3.60000
50%       4.50000
80%       7.20000
max       9.00000
dtype: float64

g, 极差

一组数据中, 最大值与最小值之差

h, 方差

方差体现一组数据中, 每个元素与均值的偏离程度

$$\sigma^{2}=\frac{1}{n-1} \sum_{i=1}^{n}\left(x_{i}-\bar{x}\right)^{2}$$

$x_{i}:$ 数组中的每个元素
$n:$ 数组元素的个数
$\bar{x}:$ 数组中所有元素的均值

i, 标准差

标准差为方差的开方. 方差和标准差可以体现数据的分散性, 越大越分散, 越小越集中. 也可体现数据波动性(稳定性), 越大波动越大, 反之亦然

当数据足够多时, 可用 n 代替 n - 1

例, 计算 left_data 字段的极差, 方差, 标准差:

sub = np.ptp(data['left_data'])  
var = data['left_data'].var()  
std = data['left_data'].std()  
sub, var, std  
(29.0, 44.631048970506306, 6.680647346665315)

绘图对比 data1left_data 的分散程度

plt.figure(figsize=(11, 1))  
plt.ylim(-0.5, 1.5)  
plt.plot(data['data1'], np.zeros(len(data)), ls='', marker='o', color='r', label='data1')  
plt.plot(data['left_data'], np.ones(len(data)), ls='', marker='o', color='g', label='left_data')  
plt.axvline(data['data1'].mean(), ls='--', color='r', label='data1均值')  
plt.axvline(data['left_data'].mean(), ls='--', color='g', label='left_data均值')  
plt.legend()  
plt.show()  

png

j, 偏度

统计数据分布偏斜方向和程度的度量, 统计数据分布非对称程度的数字特征, 偏度为 0 , 对称分布, 小于 0, 左偏分别, 大于 0, 右偏分布

k, 峰度

表征概率密度分布曲线在平均值处峰值高低的特征数. 直观看来, 峰度反映了峰部的尖度, 峰度高意味着标准差增大是由低频度的大于或小于平均值的极端差值引起的. 在相同的标准差下,峰度越大,分布就有更多的极端值,那么其余值必然要更加集中在众数周围,其分布必然就更加陡峭

样本的峰度是和正态分布相比较而言的统计量, 符合正态分布的峰度为 0

例, 计算 data 中前三个字段的偏度, 峰度与标准差, 并绘图比较:

print('偏度:', data['data1'].skew(), data['left_data'].skew(), data['right_data'].skew())  
print('峰度:', data['data1'].kurt(), data['left_data'].kurt(), data['right_data'].kurt())  
print('标准差:', data['data1'].std(), data['left_data'].std(), data['right_data'].std())  
偏度: 0.0013827051273872734 -1.704193031847586 0.9122511031664028
峰度: 0.01807838530280126 2.5013831586663304 0.29539776195275813
标准差: 2.891504548352662 6.680647346665315 4.672046842962734
sns.kdeplot(data['data1'], shade=True, label='正态')  
sns.kdeplot(data['left_data'], shade=True, label='左偏')  
sns.kdeplot(data['right_data'], shade=True, label='右偏')  
plt.show()  

png

推断统计

推断统计, 通过样本推断总体的统计方法, 包括对总体的未知参数进行估计; 对关于参数的假设进行检查; 对总体进行预测预报等. 推断统计的基本问题可以分为两大类:一类是 参数估计 问题; 另一类是 假设检验 问题

1, 总体, 个体与样本

总体, 要研究对象的所有数据, 获取通常比较困难. 总体中的某个数据, 就是个体. 从总体中抽取部分个体, 就构成了样本, 样本中的个体数, 称为样本容量.

2, 参数估计

参数估计, 用样本指标(统计量)估计总体指标(参数). 参数估计有 点估计区间估计 两种

2.01, 点估计

点估计是依据样本统计量估计总体中的未知参数. 通常它们是总体的某个特征值,如数学期望, 方差和相关系数等. 点估计问题就是要构造一个只依赖于样本的量,作为总体未知参数的估计值.

2.02, 区间估计

区间估计是根据样本的统计量, 计算出一个可能的区间(置信区间) 和 概率(置信度), 表示总体的未知参数有多少概率位于该区间.

注意:
点估计使用一个值来作为总体参数值, 能给出具体值, 但易受随机抽样影响, 准确性不够
区间估计使用一个置信区间和置信度, 表示总体参数值有多少可能(置信度)会在该范围(置信区间)内, 能给出合理的范围和信心指数, 不能给出具体值

2.03, 中心极限定理

要确定置信区间与置信度, 我们先要知道总体与样本之间, 在分布上有着怎样的联系. 中心极限定理(独立同分布的中心极限定理)给出了它们之间的联系:

如果总体均值为 $\mu$, 方差为 $\sigma^{2}$, 我们进行随机抽样, 样本容量为 n, 当 n 增大时,则样本均值 $\bar{X}$ 逐渐趋近服从均值为 $\mu$, 方差为 $\sigma^{2} / n$ 的正态分布:

$$\bar{X} \sim N\left(\mu, \sigma^{2} / n\right)$$

说明:
进行多次抽样,每次抽样会得到一个均值, 这些均值会围绕在总体均值左右,呈正态分布
当样本容量 n 足够大时, 抽样样本均值的均值 ≈ 样本均值 $\bar{X}$ ≈ 总体均值 $\mu$, 样本均值分布的标准差等于 $\sigma / \sqrt{n}$
样本均值分布的标准差, 称为标准误差, 简称标准误

模拟证明:

import numpy as np  
import pandas as pd  
import matplotlib.pyplot as plt  
import seaborn as sns  

plt.rcParams['font.family'] = 'SimHei'  
plt.rcParams['axes.unicode_minus'] = False  

# 定义非正态分布总体(也可以是正态分布)  
data = np.random.normal(20, 5, size=10000)  
data.sort()  
all_ = np.random.choice(data[0:8000], size=10000)  
# sns.displot(all_)  

# 将总体的均值和标准差设为已知条件  
print('总体均值:', all_.mean(), '总体标准差:', all_.std())  

# 创建存放每次抽样的平均值的数组(初始值为 0)  
mean_arr = np.zeros(1000)  

# 循环抽取 1000 个样本, 每次抽 100 个  
for i in range(len(mean_arr)):  
    mean_arr[i] = np.random.choice(all_, size=100, replace=False).mean()  

# 验证结果  
print('样本均值:', mean_arr[1], '样本均值的均值:', mean_arr.mean(),   
      '标准误差:', mean_arr.std(), '偏度:', pd.Series(mean_arr).skew(), sep='\n')  

sns.displot(mean_arr, kde=True)  
plt.show() 
总体均值: 18.270423532980452 总体标准差: 3.8201265113791596
样本均值:
18.194948520041606
样本均值的均值:
18.26385715935595
标准误差:
0.373202226318143
偏度:
0.00746666188264042

png

2.04, 正态分布的特性

正态分布: $X \sim N\left(\mu, \sigma^{2}\right)$

png

以均值为中心:
在 1 倍标准差内包含约 68.2% 的样本数据
在 2 倍标准差内包含约 95.4% 的样本数据
在 3 倍标准差内包含约 99.7% 的样本数据

证明:

# 定义标准差  
scale = 10  

# 定义数据  
x = np.random.normal(0, scale, size=100000)  

# 计算  
for times in range(1, 4):  
    y = x[(x > -times * scale) & (x < times * scale)]  
    print(f'{times}倍的标准差:')  
    print(f'{len(y) * 100 / len(x)}%')  
1倍的标准差:
68.206%
2倍的标准差:
95.354%
3倍的标准差:
99.711%

2.05, 重要结论

根据中心极限定理和正态分布的特性, 如果总体标准差为 $\sigma$, 对总体进行一次抽样, 如果样本足够大, 则样品均值 $\bar{X}$ 服从正态分布, 该均值约有 95.4% 的概率会在 2 倍的标准误差 ( $\mu - 2\sigma / \sqrt{n}, \mu + 2\sigma / \sqrt{n}$) 范围内, 并且该样本均值约等于总体均值 $\mu$. 从而, 可以利用这一结论, 对总体均值进行区间估计.

结论验证:

# 随机生成总体均值, 其值未知  
mean = np.random.randint(0, 10000)  

# 总体的标准差已知为 50  
std = 50  

# 定义总体数据  
all_ = np.random.normal(mean, std, size=100000)  

# 从总体抽取 100 个元素构成样本  
sample = np.random.choice(all_, size=100, replace=False)  

# 计算样本均值  
sample_mean = sample.mean()  
print('样本均值:', sample_mean)  

# 计算样本的标准误差  
se = std / np.sqrt(n)  

# 计算置信区间 95%置信度  
min_ = sample_mean - 1.96 * se  
max_ = sample_mean + 1.96 * se  
print('置信区间(95%置信度):', (min_, max_))  

# 区间估计  
print(f'总体均值有 95% 的概率在{(min_, max_)}区间内')  
print('总体均值:', mean)  

# 绘图辅助  
plt.plot(mean, 0, marker='*', color='orange', ms=12, label='总体均值')  
plt.plot(sample_mean, 0, marker='o', color='r', label='样本均值')  
plt.hlines(0, xmin=min_, xmax=max_, color='b', label='置信区间')  
plt.axvline(min_, 0.4, 0.6, color='r', ls='--', label='左边界')  
plt.axvline(max_, 0.4, 0.6, color='g', ls='--', label='右边界')  
plt.legend()  
plt.show()  
样本均值: 9695.658932218576
置信区间(95%置信度): (9685.858932218576, 9705.458932218575)
总体均值有 95% 的概率在(9685.858932218576, 9705.458932218575)区间内
总体均值: 9696

png

3, 假设检验

假设检验(显著性检验), 先对总体做出假设, 然后通过判断样本与总体之间是否存在显著性差异, 来验证总体的假设

假设检验使用了一种类似于 “反证法” 的推理方法,它的特点是:

  • 先对总体做出两个完全相反的假设, 原假设(设为真) 和 备择假设, 计算后导致不合理现象产生,则拒绝原假设, 接受备择假设, 反之接受原假设, 放弃备择假设

  • 这种 “反证法” 不同于一般的反证法. 所谓不合理现象产生,并非指形式逻辑上的绝对矛盾,而是基于小概率原理:概率很小的事件在一次试验中几乎是不可能发生的,若发生了,就是不合理的.

  • 怎样才算 “小概率”, 通常可将概率不超过 0.05 的事件称为 “小概率事件” ,也可视具体情形而取 0.1 或 0.01 等. 在假设检验中常记这个概率为 α,称为显著性水平

假设检验可分为正态分布检验, 正态总体均值检验, 非参数检验三类, 本文只介绍 正态总体均值检验 , 包括 Z检验 和 t检验 两种情况

3.01, 关键概念:

对总体参数做出两个完全对立的假设, 分别为:
原假设(零假设) $H_{0}$
备择假设(对立假设) $H_{1}$

双边假设检验 :
$H_{0}: \mu=\mu_{0}, H_{1}: \mu \neq \mu_{0}$

单边假设检验 :
$H_{0}: \mu \geq \mu_{0}, H_{1}: \mu<\mu_{0}$ (左边检验)
$H_{0}: \mu \leq \mu_{0}, H_{1}: \mu>\mu_{0}$ ( 右边检验 )
$\mu$ 为总体均值, $\mu_{0}$ 为假设均值

显著性水平 : 根据需要设定的小概率事件的概率 α (1 - α 为置信度)

检验统计量 (Z 和 t): 用来判断样本均值与总体均值是否存在显著性差异

P值: 通过检验统计量计算而得的概率值, 表示原假设可被拒绝的最小值(或可支持原假设的概率):
P ≤ α, 原假设可被拒绝的最小值比显著性水平还低, 原假设可被拒绝, 则拒绝原假设
P > α, 原假设可被拒绝的最小值大于显著性水平, 原假设不可被拒绝, 支持原假设

3.02, 假设检验的步骤

设置原假设与备择假设
设置显著性水平 α
根据问题选择假设检验的方式
计算统计量(Z 或 t)
计算 P值(Z 或 t 围成的分布面积)
根据 P值 与 α值, 决定接受原假设还是备择假设

例, 某车间用一台包装机包装葡萄糖. 袋装糖的净重是一个随机变量,它服从正态分布. 当机器正常时,其均值为 0.5kg,标准差为 0.015kg. 某日开工后为检验包装机是否正常,随机地抽取它所包装的糖 9 袋,称得净重为(kg):
0.497, 0.506, 0.518, 0.524, 0.498, 0.511, 0.520, 0.515, 0.512
判断下面说法是否正确:
(1) 机器正常

例, 某车间用包装机包装葡萄糖. 袋装糖的净重是一个随机变量,它服从正态分布. 随机地抽取糖 9 袋,称得净重为(kg):
0.497, 0.506, 0.518, 0.524, 0.498, 0.511, 0.520, 0.515, 0.512
判断下面说法是否正确:
(2) 该车间袋装糖净重均值为 0.5kg
(3) 该车间袋装糖净重均值不少于 0.5kg
(4) 该车间袋装糖净重均值不多于 0.5kg

3.03, Z检验

Z检验适用于: 总体正态分布且方差已知, 样本容量较大(一般 ≥ 30)

Z统计量计算公式:

$$Z=\frac{\bar{x}-\mu_{0}}{S_{\bar{x}}}=\frac{\bar{x}-\mu_{0}}{\sigma / \sqrt{n}}$$

$\bar{x}$: 样本均值
$\mu_{0}$: 假设的总体均值
$S_{\bar{x}}$: 样本的标准误差
$\sigma$: 总体的标准差
$n$: 样本容量

检验说法(1): 机器正常

双边检验:
原假设机器正常: $H_{0}: \mu=\mu_{0}=0.5kg$
备择假设机器不正常: $H_{1}: \mu \neq \mu_{0} \neq 0.5kg$
设置显著性水平: α = 0.05

import numpy as np  
from scipy import stats  

# 样本已知  
a = np.array([0.497, 0.506, 0.518, 0.524, 0.498, 0.511, 0.520, 0.515, 0.512])  

# 总体均值和标准差已知  
mean, std = 0.5, 0.015  

# 计算样本均值  
sample_mean = a.mean()  

# 计算样本标准误差  
se = std / np.sqrt(len(a))  

# 计算 Z统计量  
Z = (sample_mean - mean) / se  
print('Z统计量:', Z)  

# 计算 P值, 双边检验: Z值与其右边曲线围成的面积的 2 倍  
P = 2 * stats.norm.sf(abs(Z))  

print('P值:' , P)  
Z统计量: 2.244444444444471
P值: 0.02480381963225589

由结果可知, Z值 超过了 1.96, 由 Z值 与其右边曲线围成的面积的 2 倍, 必然小于 α(1.96 与其右边曲线围成的面积的 2 倍), 计算结果 P < α, 因此拒绝原假设, 接受备择假设, 机器不正常

3.04, t检验

t检验适用于: 总体正态分布, 方差未知, 样本数量较少(一般 < 30), 但是随着样本容量的增加, 分布逐渐趋于正态分布

t统计量计算公式:

$$t=\frac{\bar{x}-\mu_{0}}{S_{\bar{x}}}=\frac{\bar{x}-\mu_{0}}{S / \sqrt{n}}$$

$\bar{x}$: 样本均值
$\mu_{0}$: 假设的总体均值
$S_{\bar{x}}$: 样本的标准误差
$S$: 样本的标准差
$n$: 样本容量

双边检验 :
检验说法(2): 该车间袋装糖净重均值为 0.5kg

原假设, 该车间袋装糖净重均值为 0.5kg: $H_{0}: \mu=\mu_{0}=0.5kg$
备择假设, 该车间袋装糖净重均值不为 0.5kg: $H_{1}: \mu \neq \mu_{0} \neq 0.5kg$
设置显著性水平: α = 0.05

# 样本已知  
a = np.array([0.497, 0.506, 0.518, 0.524, 0.498, 0.511, 0.520, 0.515, 0.512])  

# 假设的总体均值已知  
mean = 0.5  

# 计算样本均值  
sample_mean = a.mean()  

# 计算样本标准差  
std = a.std()  

# 计算 t统计量  
t = (sample_mean - mean) / ( std / np.sqrt(len(a)))  
print('t统计量:', t)  

# 计算 P值, df 是自由度: 样本变量可自由取值的个数  
P = 2 * stats.t.sf(abs(t), df=len(a) - 1)  
print('P值:', P)  
t统计量: 3.802382179137283
P值: 0.005218925008708613

P < α, 拒绝原假设, 接受备择假设: 该车间袋装糖净重均值不为 0.5kg

还可以通过 scipy 提供的方法 ttest_1samp 来进行 t检验计算:

from scipy import stats
stats.ttest_1samp(a, 0.5)  
Ttest_1sampResult(statistic=3.584920298041139, pvalue=0.007137006417828698)

左边检验 :
检验说法(3): 该车间袋装糖净重均值不少于 0.5kg

原假设, 该车间袋装糖净重均值不少于 0.5kg: $H_{0}: \mu \geq \mu_{0}$
备择假设, 该车间袋装糖净重均值少于 0.5kg: $H_{1}: \mu<\mu_{0}$
设置显著性水平: α = 0.05

# t统计量上述已经计算, 只需计算 P值: t统计量与其左边曲线围成的面积  
P = stats.t.cdf(t, df=len(a) - 1)  
print('P值:', P)  
P值: 0.9973905374956458

P > α, 接受原假设, 该车间袋装糖净重均值不少于 0.5kg

右边检验 :
检验说法(4): 该车间袋装糖净重均值不多于 0.5kg

原假设, 该车间袋装糖净重均值不多于 0.5kg: $H_{0}: \mu \leq \mu_{0}$
备择假设, 该车间袋装糖净重均值多于 0.5kg: $H_{1}: \mu>\mu_{0}$
设置显著性水平: α = 0.05

# 计算 P值: t统计量与其右边曲线围成的面积  
P = stats.t.sf(t, df=len(a) - 1)  
print('P值:', P)  
P值: 0.0026094625043543065

P < α, 拒绝原假设, 接受备择假设, 该车间袋装糖净重均值多于 0.5kg

线性回归

1, 模型

模型是指对于某个(类)实际问题的求解或客观事物运行规律进行抽象后的一种形式化表达方式, 可以理解为一个函数(一种映射规则)

任何模型都是由三个部分组成: 目标, 变量和关系. 建模时明确了模型的目标,才能进一步确定影响目标(因变量)的各关键变量(自变量),进而确定变量之间的关系(函数关系)

通过大量数据检验(训练)模型, 将模型(函数)的各个参数求解, 当参数确定之后, 便可利用模型对未知数据进行求值, 预测

用于训练模型的样本数据中的每个属性称为特征, 用 x 表示, 样本中的每条数据经过模型计算得到的输出值称为标签(监督学习), 用 y 表示, 从而得到 y = f(x) 的函数关系

2, 回归分析

在统计学中, 回归分析指的是确定两种或两种以上变量间相互依赖的定量关系的一种统计分析方法

回归分析按照涉及的变量的多少,分为一元回归分析和多元回归分析;按照因变量的多少,可分为简单回归分析和多重回归分析;按照自变量和因变量之间的关系类型,可分为线性回归分析和非线性回归分析

回归分析解释自变量 x 发生改变, 因变量 y 会如何改变

拟合 , 插值 和 逼近 是数值分析的三大基础工具. 线性回归和非线性回归, 也叫线性拟合和非线性拟合, 拟合就是从整体上靠近已知点列,构造一种算法(模型或函数), 使得算法能够更加符合真实数据

3, 简单线性回归

线性回归分析的自变量和因变量之间是线性关系, 只有一个自变量时称为 简单线性回归 , 多个自变量时称为 多元线性回归

简单线性回归方程:

$$\hat{y}=w * x+b$$

$\hat{y}$ 为因变量, x 为自变量, w 为比例关系, b 为截距, w 和 b 就是模型的参数. 例如房屋价格与房屋面积的正比例关系

4, 多元线性回归

现实生活中自变量通常不止一个, 例如影响房屋价格的, 除了房屋面积, 还有交通, 地段, 新旧, 楼层等等因素. 不同的因素对房屋的价格影响力度(权重)不同, 因此使用多个因素来分析房屋的价格(各个因素与房屋价格近似线性关系), 可以得出多元线性回归方程:

$\hat{y}=w_{1} * x_{1}+w_{2} * x_{2}+w_{3} * x_{3}+\cdots+w_{n} * x_{n}+b$

$x$: 影响因素, 特征
$w$: 每个 x 的影响力度
$n$: 特征个数
$\hat{y}$: 房屋的预测价格

令:

$x_{0}=1, w_{0}=b$

$\vec{w}$$\vec{x}$ 为两个向量如下:

$$\vec{w}=\left(w_{0}, w_{1}, w_{2}, w_{3}, \ldots, w_{n}\right)^{T}$$ $$\vec{x}=\left(x_{0}, x_{1}, x_{2}, x_{3}, \ldots, x_{n}\right)^{T}$$

则方程可表示为:

$$\begin{aligned} \hat{y} &=w_{0} * x_{0}+w_{1} * x_{1}+w_{2} * x_{2}+w_{3} * x_{3}+\ldots \ldots+w_{n} * x_{n} \ =\sum_{j=0}^{n} w_{j} * x_{j} \ =\vec{w}^{T} \cdot \vec{x} \end{aligned}$$

接下来只需要计算出参数 $\vec{w}^{T}$, 便可以建立模型

5, 损失函数

损失函数, 用来衡量模型预测值与真实值之间的差异的函数, 也称目标函数或代价函数. 损失函数的值越小, 表示预测值与真实值之间的差异越小.

因此, 求解上述模型的参数 $\vec{w}^{T}$, 就是要建立一个关于模型参数的损失函数(以模型参数 $\vec{w}^{T}$ 为自变量的函数), 然而 $\vec{w}^{T}$ 的取值组合是无限的, 目标就是通过机器学习, 求出一组最佳组合, 使得损失函数的值最小

在线性回归中, 使用平方损失函数(最小二乘法), 用 J(w) 表示:

$$\begin{array}{l} J(w)=\frac{1}{2} \sum_{i=1}^{m}\left(y^{(i)}-\hat{y}^{(i)}\right)^{2} \ =\frac{1}{2} \sum_{i=1}^{m}\left(y^{(i)}-\vec{w}^{T} \vec{x}^{(i)}\right)^{2} \end{array}$$

m: 样本(训练集)数据的条数
$y^{(i)}$: 样本第 i 条数据的真实值
$\hat{y}^{(i)}$: 样本第 i 条数据的预测值
$\vec{x}^{(i)}$: 样本第 i 条数据的特征

m, $y^{(i)}$$\vec{x}^{(i)}$ 已知, 要使 J(w) 最小, 对 $\vec{w}^{T}$ 求导并令导数等于 0 , 便可求得 $\vec{w}^{T}$, 然后将样本(训练集)输入通过机器学习计算出具体的 $\vec{w}^{T}$

6, 回归模型评估

建立模型之后, 模型的效果如何, 需要进行评估, 对于回归模型, 可用如下指标来衡量:

MSE :
平均平方误差, 所有样本数据误差的平方和取均值:

$$M S E=\frac{1}{m} \sum_{i=1}^{m}\left(y^{(i)}-\hat{y}^{(i)}\right)^{2}$$

RMSE :
平均平方误差的平方根:

$$R M S E=\sqrt{M S E}=\sqrt{\frac{1}{m} \sum_{i=1}^{m}\left(y^{(i)}-\hat{y}^{(i)}\right)^{2}}$$

MAE :
平均绝对值误差, 所有样本数据误差的绝对值的和取均值:

$$M A E=\frac{1}{m} \sum_{i=1}^{m}\left|y^{(i)}-\hat{y}^{(i)}\right|$$

上述指标越小越好, 小到什么程度, 不同的对象建立的模型不一样

:
决定系数,反应因变量的全部变异能通过回归关系被自变量解释的比例. 如 R²=0.8,则表示回归关系可以解释因变量 80% 的变异. 换句话说,如果我们能控制自变量不变,则因变量的变异程度会减少 80%

在训练集中 R² 取值范围为 [0, 1], 在测试集(未知数据)中, R² 的取值范围为 [-∞, 1], R² 的值越大, 模型拟合越好

R² 的计算公式:

$$R^{2}=1-\frac{R S S}{T S S}=1-\frac{\sum_{i=1}^{m}\left(y^{(i)}-\hat{y}^{(i)}\right)^{2}}{\sum_{i=1}^{m}\left(y^{(i)}-\bar{y}\right)^{2}}$$

$\bar{y}$: 样本(测试集)的平均值

不管何种对象建立的模型, R² 都是越大模拟越好

例一, 简单线性回归模型: 求鸢尾花花瓣长度和宽度的关系

import numpy as np  

# 导入用于线性回归的类  
from sklearn.linear_model import LinearRegression  

# 切分训练集与测试集的模块  
from sklearn.model_selection import train_test_split  

# 鸢尾花数据集  
from sklearn.datasets import load_iris  

# 设置输出数据的精度为 2 (默认是8)  
np.set_printoptions(precision=2)  

# 获取花瓣长度 x, 宽度 y  
iris = load_iris()  
x, y = iris.data[:, 2].reshape(-1, 1), iris.data[:, 3]  

# 将数据拆分为训练集和测试集, 指定测试集占比 test_size  
# 指定随机种子 random_state(可以任意值但必须确定), 锁定拆分行为  
x_train, x_test, y_train, y_test = train_test_split(  
    x, y, test_size=0.25, random_state=5)  

# 使用训练集训练模型  
lr = LinearRegression()  
lr.fit(x_train, y_train)  

# 求得模型参数  
print('权重 w:', lr.coef_, '截距 b:', lr.intercept_)  

# 调用模型进行预测  
y_hat = lr.predict(x_test)  

# 结果可视化  
import matplotlib.pyplot as plt  

plt.rcParams['font.family'] = 'SimHei'  
plt.rcParams['axes.unicode_minus'] = False  
plt.rcParams['font.size'] = 10  

plt.scatter(x_train, y_train, c='orange', label='训练集')  
plt.scatter(x_test, y_test, c='g', marker='D', label='测试集')  
plt.plot(x, lr.predict(x), 'r-')  
plt.legend()  
plt.xlabel('花瓣长度')  
plt.ylabel('花瓣宽度')  
plt.show()  
权重 w: [0.42] 截距 b: -0.370615595909495

png

# 模型评估  
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score  

print('MSE:', mean_squared_error(y_test, y_hat))  
print('RMSE:', np.sqrt(mean_squared_error(y_test, y_hat)))  
print('MAE:', mean_absolute_error(y_test, y_hat))  
print('训练集R²:', r2_score(y_train, lr.predict(x_train))) # 可换成 lr.score(x_train, y_train)  
print('测试集R²:', r2_score(y_test, y_hat)) # 可换成 lr.score(x_test, y_test)  
MSE: 0.047866747643216113
RMSE: 0.21878470614559903
MAE: 0.1543808898175286
训练集R²: 0.9317841638431329
测试集R²: 0.9119955391492289

列二, 多元线性回归模型: 波士顿房价预测

import numpy as np  
from sklearn.linear_model import LinearRegression  
from sklearn.model_selection import train_test_split  
from sklearn.datasets import load_boston  
import pandas as pd  

boston = load_boston()  
x, y = boston.data, boston.target  
df = pd.DataFrame(np.concatenate([x, y.reshape(-1, 1)], axis=1),   
                 columns=boston.feature_names.tolist() + ['MEDV'])  
# 部分数据  
df.head(3)  
  CRIM	ZN	INDUS	CHAS	NOX	RM	AGE	DIS	RAD	TAX	PTRATIO	B	LSTAT	MEDV
0	0.00632	18.0	2.31	0.0	0.538	6.575	65.2	4.0900	1.0	296.0	15.3	396.90	4.98	24.0
1	0.02731	0.0	7.07	0.0	0.469	6.421	78.9	4.9671	2.0	242.0	17.8	396.90	9.14	21.6
2	0.02729	0.0	7.07	0.0	0.469	7.185	61.1	4.9671	2.0	242.0	17.8	392.83	4.03	34.7
x_train, x_test, y_train, y_test = train_test_split(  
    x, y, test_size=0.25, random_state=5)  
lr = LinearRegression()  
lr.fit(x_train, y_train)  
print('权重:', lr.coef_)  
print('截距:', lr.intercept_)  
y_hat = lr.predict(x_test)  
print('训练集R²:', lr.score(x_train, y_train))  
print('测试集R²:', lr.score(x_test, y_test))   

# 假如获取了一间房屋的数据, 预测其房价  
room_data = np.array([0.00732, 17.0, 1.31, 1.0, 0.638, 7.575, 62.2, 5.0900,  
                      1.0, 296.0, 15.3, 396.90, 4.98]).reshape(1, -1)  
y_price = lr.predict(room_data)  
print('房屋价格:', y_price)  
权重: [-1.53e-01  4.79e-02 -8.60e-03  2.58e+00 -1.46e+01  3.96e+00 -7.92e-03
 -1.46e+00  3.45e-01 -1.25e-02 -9.19e-01  1.32e-02 -5.17e-01]
截距: 32.214120389743606
训练集R²: 0.7468034208269784
测试集R²: 0.7059096071098042
房屋价格: [33.62]

多元线性回归在空间中, 可表示为一个超平面去拟合空间中的数据点

逻辑回归

逻辑回归和线性回归有类似之处, 都是利用线性加权计算的模型, 但逻辑回归是分类算法, 例如对是否患癌症进行预测, 因变量就是 , 两个类别, 自变量可以是年龄, 性别, 饮食, 作息, 病菌感染等, 自变量既可以是数值变量, 也可以是类别变量

1, 逻辑回归二分类推导

和线性回归类似, 设自变量为 x, 每个自变量的权重为 w, 令:

$$\begin{array}{l} z=w_{1} x_{1}+w_{2} x_{2}+\cdots+w_{n} x_{n}+b \ =\sum_{j=1}^{n} w_{j} x_{j}+b \ =\sum_{j=0}^{n} w_{j} x_{j} \ =\vec{w}^{T} \cdot \vec{x} \end{array}$$

z 是一个连续值, 取值范围(-∞, +∞), 为了实现分类, 一般设置阈值 z = 0, 当 z > 0 时, 将样本判定为一个类别(正例), 该类别设为 1, 当 z ≤ 0 时, 判定为另一个类别(负例), 该类别设为 0, 再设因变量为 y, 从而逻辑回归方程可表示为:

$y=1, z>0$ $y=0, z \leq 0$

上述方程虽然实现了分类, 但提供的信息有限, 因此引入 sigmoid函数 (也叫 Logistic函数), 将 z 映射到 (0, 1) 区间,可以实现二分类的同时, 还能体现将样本分为某个类的可能性, 这个可能性设为 p:

$$p=\operatorname{sigmoid}(z)=\frac{1}{1+e^{-z}}$$

sigmoid 函数图像如下:

于是, 逻辑回归方程又可表示为:

$y=1, p>0.5$ $y=0, 1-p \geq 0.5$

从而可见, 通过比较 p 和 1-p 哪个更大(z 的阈值不取 0 时做出调整即可), 预测结果就是对应的一类

2, 逻辑回归的损失函数

通过上述推导过程可知, 要得到逻辑回归模型, 最终就是要求得参数 $\vec{w}^{T}$, 于是将 p 和 1-p 统一, 构造一个损失函数来求 $\vec{w}^{T}$:

$$p(y=1 | x ; w)=s(z)$$ $$p(y=0 | x ; w)=1-s(z)$$

合并:

$$p(y | x ; w)=s(z)^{y}(1-s(z))^{1-y}$$

上式表示一个样本的概率, 我们要求解能够使所有样本联合概率密度最大的 $\vec{w}^{T}$ 值, 根据极大似然估计, 所有样本的联合概率密度函数(似然函数)为:

$$\begin{array}{l} L(w)=\prod_{i=1}^{m} p\left(y^{(i)} | x^{(i)} ; w\right) \ =\prod_{i=1}^{m} s\left(z^{(i)}\right)^{y^{(i)}}\left(1-s\left(z^{(i)}\right)\right)^{1-y^{(i)}} \end{array}$$

取对数, 让累积乘积变累积求和:

$$\begin{array}{l} \ln L(w)=\ln \left(\prod_{i=1}^{m} s\left(z^{(i)}\right)^{y^{(i)}}\left(1-s\left(z^{(i)}\right)^{1-y^{(i)}}\right)\right) \ =\sum_{i=1}^{m}\left(y^{(i)} \ln s\left(z^{(i)}\right)+\left(1-y^{(i)}\right) \ln \left(1-s\left(z^{(i)}\right)\right)\right) \end{array}$$

要求上式最大值, 取反变成求最小值, 就作为逻辑回归的损失函数(交叉熵损失函数):

$$J(w)=-\sum_{i=1}^{m}\left(y^{(i)} \ln s\left(z^{(i)}\right)+\left(1-y^{(i)}\right) \ln \left(1-s\left(z^{(i)}\right)\right)\right)$$

利用梯度下降法最终求得 $\vec{w}^{T}$ (省略)

例, 对鸢尾花实现二分类并分析:

from sklearn.linear_model import LogisticRegression  
from sklearn.model_selection import train_test_split  
from sklearn.datasets import load_iris  
import warnings  

warnings.filterwarnings('ignore')  

iris = load_iris()  
x, y = iris.data, iris.target  

# 鸢尾花数据集有 3 个类别, 4 个特性, 取两个类别, 两个特性  
x = x[y!=0, 2:]  
y = y[y!=0]  

# 拆分训练集与测试集  
x_train, x_test, y_train, y_test = train_test_split(x, y,  
        test_size=0.25, random_state=2)  

# 训练分类模型  
lr = LogisticRegression()  
lr.fit(x_train, y_train)  

# 测试  
y_hat = lr.predict(x_test)  
print('权重:', lr.coef_)  
print('偏置:', lr.intercept_)  
print('真实值:', y_test)  
print('预测值:', y_hat)  
权重: [[2.54536368 2.15257324]]
偏置: [-16.08741502]
真实值: [2 1 2 1 1 1 1 1 1 1 2 2 2 1 1 1 1 1 2 2 1 1 2 1 2]
预测值: [2 1 1 1 1 1 1 2 1 1 2 2 2 1 1 1 1 1 2 2 1 1 2 1 2]
# 样本的真实类别可视化  
import matplotlib.pyplot as plt  
plt.rcParams['font.family'] = 'SimHei'  

# 取出两种鸢尾花的特征  
c1 = x[y==1]  
c2 = x[y==2]  

# 绘制样本分布  
plt.scatter(x=c1[:, 0], y=c1[:, 1], c='g', label='类别1')  
plt.scatter(x=c2[:, 0], y=c2[:, 1], c='r', label='类别2')  
plt.xlabel('花瓣长度')  
plt.ylabel('花瓣宽度')  
plt.title('鸢尾花样本分布')  
plt.legend()  
plt.show()  

png

# 将预测类别和真实类别可视化对比  
plt.figure(figsize=(6, 2.2))  
plt.plot(y_test, marker='o', ls='', ms=10, c='r', label='真实类别')  
plt.plot(y_hat, marker='x', ls='', ms=10, c='g', label='预测类别')  
plt.xlabel('样本序号')  
plt.ylabel('类别')  
plt.title('预测结果')  
plt.legend()  
plt.show()  

png

# 因预测样本所属类别时, 通过比较概率得到结果,   
# 我们可将结果对应的概率可视化  
import numpy as np  

# 获取预测的概率值  
probability = lr.predict_proba(x_test)  
print('概率:', probability[:5], sep='\n')  

index = np.arange(len(x_test))  
pro_0 = probability[:, 0]  
pro_1 = probability[:, 1]  

# 设置预测结果标签, 对和错  
tick_label = np.where(y_test==y_hat, '对', '错')  

# 绘制堆叠图  
plt.figure(figsize=(8, 2))  
plt.bar(index, height=pro_0, color='g', label='类别1的概率')  
plt.bar(index, height=pro_1, color='r', bottom=pro_0,  
        label='类别2的概率', tick_label=tick_label)  
plt.xlabel('预测结果')  
plt.ylabel('各类别的概率')  
plt.title('分类概率')  
plt.legend()  
plt.show() 
概率:
[[0.46933862 0.53066138]
 [0.98282882 0.01717118]
 [0.72589695 0.27410305]
 [0.91245661 0.08754339]
 [0.80288412 0.19711588]]

png

# 绘制决策边界  
# 决策边界: 不同类别的分界线  
from matplotlib.colors import ListedColormap  

# 定义绘制函数  
def plot_decision_boundary(model, x, y):  
    color = ['r', 'g', 'b']  
    marker = ['o', 'v', 'x']  
    class_label = np.unique(y)  
    cmap = ListedColormap(color[:len(class_label)])  
    x1_min, x2_min = np.min(x, axis=0)  
    x1_max, x2_max = np.max(x, axis=0)  
    x1 = np.arange(x1_min - 1, x1_max + 1, 0.02)  
    x2 = np.arange(x2_min - 1, x2_max + 1, 0.02)  
    x1, x2 = np.meshgrid(x1, x2)  
    z = model.predict(np.array([x1.ravel(), x2.ravel()]).T).reshape(x1.shape)  

    plt.contourf(x1, x2, z, cmap=cmap, alpha=0.5)  
    for i, class_ in enumerate(class_label):  
        plt.scatter(x=x[y==class_, 0], y=x[y==class_, 1],  
                c=cmap.colors[i], label=class_, marker=marker[i])  
    plt.legend()  
    plt.show()  

# 绘制模型在训练集上的决策边界  
plot_decision_boundary(lr, x_train, y_train)  

png

拓展 :
逻辑回归实现多分类

iris = load_iris()  
x, y = iris.data, iris.target  
x = x[:, 2:]  
x_train, x_test, y_train, y_test = train_test_split(x, y,   
        test_size=0.25, random_state=2)  
lr = LogisticRegression()  
lr.fit(x_train, y_train)  

# 测试分类  
y_hat = lr.predict(x_test)  

# 可视化结果  
plt.rcParams['axes.unicode_minus']=False  
plot_decision_boundary(lr, x_test, y_test)  

png

分类模型评估

在完成模型训练之后,需要对模型的效果进行评估,根据评估结果继续调整模型的参数, 特征或者算法,以达到满意的结果

1, 混淆矩阵

将 真正例(TP), 假正例(FP), 真负例(TN), 假负例(FN) 统计于一个方阵中, 观察比较, 评价模型好坏, 矩阵如下:

混淆矩阵统计数量, 评价不直观也有限, 基于混淆矩阵又延伸出 正确率, 精准率, 召回率, F1(调和平均值), ROC曲线和AUC等

2, 评估指标分析

正确率:

$$\text { 正确率 }=\frac{T P+T N}{T P+T N+F P+F N}$$

正确率, 表示总体(包括正负)预测正确的比率, 在模型对正例和负例的预测准确度差异较大时, 难以评价模型的好坏, 例如正例较多, 负例较少, 正例全部预测对了, 负例只预测对几个, 正确率却可能较高

精准率:

$$\text { 精准率 }=\frac{T P}{T P+F P}$$

精准率, 表示所有预测为正例的结果中 预测正确的正例 的占比, 精准率越高, 说明正例预测正确概率越高, 因此精准率更关注”一击必中”, 比如通过预测找出上涨的概率很高的一支股票

召回率:

$$\text { 召回率 }=\frac{T P}{T P+F N}$$

召回率, 表示所有真实的正例中, 预测正确的正例 的占比, 召回率越高, 说明正例被”召回”的越多, 因此召回率更关注”宁错一千, 不放一个”, 例如通过预测尽可能将新冠肺炎患者全部隔离观察

调和平均值 F1 :

$$F 1=\frac{2 * \text {精准率} * \text {召回率}}{\text {精准率}+\text {召回率}}$$

F1 将综合了精准率和召回率, F1越高, 说明模型预测效果越好, F1 能够直接评估模型的好坏

ROC曲线:

ROC (Receiver Operating Characteristic) 曲线, 用图像来描述分类模型的性能好坏. 图像纵轴为 真 正例率(TPR), 横轴为 假 正例率(FPR):

$$\begin{array}{l} T P R=\text { 召回率 }=\frac{T P}{T P+F N} \ F P R=\frac{F P}{F P+T N} \end{array}$$

上述两式通过取分类模型的不同阈值, 从而计算出不同的值, 绘制出曲线, 曲线必过 (0,0) 和 (1, 1) 两个点, TPR 增长得越快, 曲线越往上凸, 模型的分类性能就越好. 如果 ROC 曲线为对角线, 可将模型理解为随机猜测; 如果 ROC 曲线在 0 点 真 正例率就达到了 1, 此时模型最完美

AUC:

AUC (Area Under the Curve), 是 ROC 曲线下面的面积, 因为有时通过 ROC 曲线看不出哪个分类模型性能好, 而 AUC 比较数值就不存在这样的问题

以鸢尾花数据集做如下练习:

import numpy as np  
from sklearn.datasets import load_iris  
from sklearn.linear_model import LogisticRegression   
from sklearn.model_selection import train_test_split  
from sklearn.metrics import confusion_matrix  
import matplotlib.pyplot as plt  
import warnings  

plt.rcParams["font.family"] = "SimHei"  
plt.rcParams["axes.unicode_minus"] = False   
plt.rcParams["font.size"] = 12   
warnings.filterwarnings("ignore")  

iris = load_iris()  
x, y = iris.data, iris.target  
x = x[y!=0, 2:]  
y = y[y!=0]  
x_train, x_test, y_train, y_test = train_test_split(x, y,  
                        test_size=0.25, random_state=2)  
lr = LogisticRegression()  
lr.fit(x_train, y_train)  
y_hat = lr.predict(x_test)  

# 传入真实值与预测值, 创建混淆矩阵  
matrix = confusion_matrix(y_true=y_test, y_pred=y_hat)  
print(matrix)  
y_hat[y_hat==1].sum()  
[[15  1]
 [ 1  8]]





16
# 将混淆矩阵可视化  
mat = plt.matshow(matrix, cmap=plt.cm.Blues, alpha=0.5)  
label = ["负例", "正例"]  

# 获取当前的绘图对象  
ax = plt.gca()  

# 设置属性, 设类别 1 为负例  
ax.set(  
    xticks=np.arange(matrix.shape[1]),   
    yticks=np.arange(matrix.shape[0]),  
    xticklabels=label,   
    yticklabels=label,   
    title="混淆矩阵可视化\n",   
    ylabel="真实值",   
    xlabel="预测值")  

# 设置统计值的位置  
for i in range(matrix.shape[0]):  
    for j in range(matrix.shape[1]):  
        plt.text(x=j, y=i, s=matrix[i, j], va="center", ha="center")   

plt.show()  

png

# 计算各个评估指标  
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score  

print("正确率:", accuracy_score(y_test, y_hat))  

# 默认以 1 为正例, 我们将 2 设为正例  
print("精准率:", precision_score(y_test, y_hat, pos_label=2))  
print("召回率:", recall_score(y_test, y_hat, pos_label=2))  
print("F1:", f1_score(y_test, y_hat, pos_label=2))  

# 也可以用逻辑回归模型对象的score方法计算正确率   
print("score方法计算正确率:", lr.score(x_test, y_test))  
正确率: 0.92
精准率: 0.8888888888888888
召回率: 0.8888888888888888
F1: 0.8888888888888888
score方法计算正确率: 0.92
# 还可以用 classification_report 方法直接计算各个指标  
from sklearn.metrics import classification_report  
print(classification_report(y_true=y_test, y_pred=y_hat))  
              precision    recall  f1-score   support

           1       0.94      0.94      0.94        16
           2       0.89      0.89      0.89         9

    accuracy                           0.92        25
   macro avg       0.91      0.91      0.91        25
weighted avg       0.92      0.92      0.92        25
# 绘制 ROC曲线 和计算 AUC  
from sklearn.metrics import roc_curve, auc, roc_auc_score  

iris = load_iris()  
x, y = iris.data, iris.target  
x = x[y!=0, 2:]  
y = y[y!=0]  
x_train, x_test, y_train, y_test = train_test_split(x, y,  
                            test_size=0.25, random_state=2)  

# 设置模型参数(有默认值可以不设), 并进行训练  
# 不同的参数训练结果不一样, 需要注意参数之间关系  
lr = LogisticRegression(multi_class="ovr", solver="liblinear")  
# lr = LogisticRegression(multi_class="multinomial")  
lr.fit(x_train, y_train)  

# 获取样本的概率  
probo = lr.predict_proba(x_test)  
print('类别 2 的概率:', probo[:, 1][:5])  

# 将概率值传入 roc_curve 方法, 从概率中选择若干个值作为阈值  
# 同时根据阈值判定正负例, 返回 fpr, tpr 和 阈值 thresholds  
fpr, tpr, thresholds = roc_curve(y_true=y_test,  
                       y_score=probo[:, 1], pos_label=2)  

# 阈值中的第一个值是第二个值 +1 得到, 为了让让曲线过 0 点  
print('阈值:', thresholds)  

# 计算 AUC   
print('用auc计算:', auc(fpr, tpr))  
print('用roc_auc_score计算:', roc_auc_score(y_true=y_test,  
                                    y_score=probo[:, 1]))  
类别 2 的概率: [0.4663913  0.28570842 0.60050037 0.3758227  0.48450719]
阈值: [1.69092453 0.69092453 0.60050037 0.54308778 0.50384451 0.49358343
 0.48450719 0.47242245 0.4663913  0.42043757 0.39590375 0.39413886
 0.3843811  0.24698327]
用auc计算: 0.8819444444444444
用roc_auc_score计算: 0.8819444444444444
# 绘制 ROC 曲线  
plt.figure(figsize=(6, 2))  
plt.plot(fpr, tpr, marker="o", label="ROC曲线")  
plt.plot([0,1], [0,1], lw=2, ls="--", label="随机猜测")   
plt.plot([0, 0, 1], [0, 1, 1], lw=2, ls="-.", label="完美预测")   
plt.xlim(-0.01, 1.02)  
plt.ylim(-0.01, 1.02)  
plt.xticks(np.arange(0, 1.1, 0.2))  
plt.yticks(np.arange(0, 1.1, 0.2))  
plt.xlabel("FPR")  
plt.ylabel("TPR")  
plt.grid()  
plt.title(f"ROC曲线, AUC值为:{auc(fpr, tpr):.2f}")  
plt.legend()  
plt.show()  

png

KNN 算法

1, 关于 KNN

KNN (K-Nearest Neighbor), 即 K 近邻算法, K 个最近的邻居. 当需要预测一个未知样本的时候, 就由与该样本最近的 K 个邻居来决定

KNN 既可以用于分类, 也可用于回归. 用来分类时, 使用 K 个邻居中, 类别数量最多(或加权最多)者, 作为预测结果; 当用来回归分析时, 使用 K 个邻居的均值(或加权均值), 作为预测结果

KNN 算法的原理是: 样本映射到多维空间时, 相似度较高的样本, 距离也会较接近, “近朱者赤近墨者黑”

2, K 值

KNN 算法的 K 值是一个模型训练前就要人为指定的参数 超参数 , 不同于模型内部通过训练数据计算得到的参数. KNN 的超参数, 需要通常通过 交叉验证 的方式来选择最合适的参数组合

K 值的选择非常重要, K 值较小时, 模型预测依赖附近的邻居, 敏感性高, 稳定性低, 容易导致过拟合; 反之, K 值较大, 敏感性低, 稳定性高, 容易欠拟合

K 值在数据量小时, 可以通过遍历所有样本(穷举)的方式找出最近的 K 个邻居, 当数据量庞大时, 穷举耗费大量时间, 此时可以采用 KD树 来找 K 个邻居

3, 交叉验证

KNN 的网格搜索交叉验证: 取不同的 K, 选择不同的距离或权重计算方式等, 将数据分为多个组, 一个组作为测试集, 其他部分作为训练集, 不断循环训练和测试, 对模型进行循环验证, 找出最佳参数组合

4, 距离的度量方式

闵可夫斯基距离:

设 n 维空间中两个点位 X 和 Y:

$X=\left(x_{1}, x_{2}, \ldots \ldots, x_{n}\right)$ $Y=\left(y_{1}, y_{2}, \ldots \ldots, y_{n}\right)$

则阁可夫斯基距离为:

$D(X, Y)=\left(\sum_{i=1}^{n}\left|x_{i}-y_{i}\right|^{p}\right)^{1 / p}$

当 p 为 1 时, 又称 曼哈顿距离 ; 当 p 为 2 时, 称 欧几里得距离

5, 权重

统一权重: K 个邻居权重相同, 不管近远都是 1/K

距离加权权重: K 个邻居的权重, 与他们各自和待测样本的距离成反比, 同时要保证权重之和为 1. 例如 3 个邻居 a, b, c 距离待测样本的距离分别为 a, b 和 c, 则 a 的权重为:

$$\frac{\frac{1}{a}}{\frac{1}{a}+\frac{1}{b}+\frac{1}{c}}=\frac{b c}{b c+a c+a b}$$

b 和 c 同理

6, 数据标准化

样本中的特征通常非常多,由于各特征的性质不同,通常具有不同的量纲(数量级). 当各特征间的量纲相差很大时,如果直接用原始特征值进行分析,就会突出数值较高的特征在综合分析中的作用,相对削弱数值较低特征的作用, 因此需要通过数据标准化, 将量纲统一, 才能客观地描述各个特征对模型的影响程度

线性回归和逻辑回归, 都是通过每个特征与其权重的乘积相加来进行计算, 不进行数据标准化(不考虑正则化), 对每个特征的权重影响较大, 但对结果不会造成影响, 而 KNN 是基于距离计算的, 如果特征的量纲不同, 量纲较大的特征会占据主导地位, 导致忽略量纲较小的特征, 从而对模型性能造成较大影响

7, 算法实现步骤

a, 确定超参数
确定 K
确定距离度量方式
确定权重计算方式
其他超参数

b, 从训练集中选择距离待测样本最近的 K 个样本

c, 根据 K 个样本对待测样本进行预测, 如果遇到多个样本距离相同的情况, 默认选取训练集中靠前的

8, 流水线 Pipline

流水线可以将每个评估器视为一个步骤, 然后将多个步骤作为整体依次执行. 例如数据处理工作较多时, 可能涉及更多步骤, 例如多项式扩展, One-Hot 编码, 特征选择, 数据标准化, 交叉验证等, 分别执行过于繁琐, 我们可以将数据处理与模型训练各个步骤作为一个整体来执行

流水线具有最后一个评估器的所有方法:

a, 当流水线对象调用 fit 方法时, 会从第一个评估器依次调用 fit_transform 方法, 然后到最后一个评估器调用 fit 方法

b, 当流水线对象调用 其他 方法时, 会从第一个评估器依次调用 transform 方法, 然后到最后一个评估器调用 其他 方法

9, 以鸢尾花为例, 对逻辑回归和 KNN 进行比较:

from sklearn.model_selection import train_test_split  
from sklearn.neighbors import KNeighborsClassifier  
from sklearn.metrics import classification_report  
from sklearn.datasets import load_iris  
from sklearn.preprocessing import StandardScaler  
from sklearn.linear_model import LogisticRegression  
import matplotlib as mpl  
import matplotlib.pyplot as plt  
import warnings  
warnings.filterwarnings("ignore")  
import numpy as np  

mpl.rcParams["font.family"] = "SimHei"  
mpl.rcParams["axes.unicode_minus"] = False  

iris = load_iris()  
X = iris.data[:, :2]  
y = iris.target  
X_train, X_test, y_train, y_test = train_test_split(X, y,   
                        test_size=0.25, random_state=0)  

# 数据标准化: StandardScaler 均值标准差标准化, MinMaxScaler 最大最小值标准化  
ss = StandardScaler()  
X_train = ss.fit_transform(X_train)  
X_test = ss.transform(X_test)  

# 逻辑回归训练  
lr = LogisticRegression()  
lr.fit(X_train,y_train)  

# KNN 训练  
# n_neighbors: 邻居的数量  
# weights:权重计算方式, 可选值为 uniform 统一权重, 与 distance 加权权重  
knn = KNeighborsClassifier(n_neighbors=3, weights="uniform")  
knn.fit(X_train, y_train)  

# 比较 AUC  
from sklearn.metrics import roc_curve, auc,roc_auc_score  

lr_fpr, lr_tpr, lr_thresholds = roc_curve(y_test,  
                lr.predict_proba(X_test)[:,1], pos_label=1)  
lr_auc = auc(lr_fpr, lr_tpr)  
print('Logistic 算法: AUC = %.3f' % lr_auc)  

knn_fpr, knn_tpr, knn_thresholds = roc_curve(y_test,  
                knn.predict_proba(X_test)[:,1], pos_label=1)  
knn_auc = auc(knn_fpr, knn_tpr)  
print('KNN 算法: AUC = %.3f' % knn_auc) 
Logistic 算法: AUC = 0.835
KNN 算法: AUC = 0.794
# 将 KNN 算法参数进行调优再来比较  
from sklearn.model_selection import GridSearchCV  

# K 值取 1~10, 并定义需要的参数组合  
knn = KNeighborsClassifier()  
grid = {'n_neighbors': range(1,11,1), 'weights': ['uniform','distance']}  

# 网格搜索交叉验证  
# param_grid:需要检验的超参数组合  
# scoring:模型评估标准, accuracy 正确率  
# n_jobs:并发数量  
# cv:交叉验证折数  
# verbose:输出冗余信息  
gs = GridSearchCV(estimator=knn, param_grid=grid, scoring='accuracy',  
                  n_jobs=-1, cv=5, verbose=0)  
gs.fit(X_train, y_train)  
gs_fpr, gs_tpr, gs_thresholds = roc_curve(y_test,  
                gs.predict_proba(X_test)[:,1], pos_label=1)  
gs_auc = auc(gs_fpr, gs_tpr)  
print('KNN 算法: AUC = %.3f' % gs_auc) 
KNN 算法: AUC = 0.855

10, 以波士顿房价为例, 对线性回归和 KNN 进行比较:

from sklearn.datasets import load_boston  
from sklearn.neighbors import KNeighborsRegressor   
from sklearn.linear_model import LinearRegression  

X, y = load_boston(return_X_y=True)  
X_train, X_test, y_train, y_test = train_test_split(X, y,  
                        test_size=0.25, random_state=0)  
knn = KNeighborsRegressor(n_neighbors=3, weights="distance")   
knn.fit(X_train, y_train)  
print("KNN 算法 R²:", knn.score(X_test, y_test))  
lr = LinearRegression()  
lr.fit(X_train, y_train)  
print("线性回归算法 R²:", lr.score(X_test, y_test))  
KNN 算法 R²: 0.5158073940789912
线性回归算法 R²: 0.6354638433202129
# 对 KNN 数据标准化和参数调优之后再来比较  
knn = KNeighborsRegressor()  
grid = {'n_neighbors': range(1,11,1), 'weights': ['uniform','distance']}  
gs = GridSearchCV(estimator=knn, param_grid=grid, scoring='r2',  
                  n_jobs=-1, cv=5, verbose=0)   

# 利用流水线处理  
from sklearn.pipeline import Pipeline  

# 定义流水线的步骤: 类型为一个列表, 列表中的每个元素是元组类型  
# 格式为:[(步骤名1,评估器1), (步骤名2, 评估器2), ……, (步骤名n, 评估器n)  
knn_steps = [("scaler", StandardScaler()), ("knn", gs)]  
knn_p = Pipeline(knn_steps)  

# 可以设置流水线的参数. 所有可用的参数可以通过 get_params 获取  
# 设置格式如下: (步骤名__参数)  
# p.set_params(knn__n_neighbors=3, knn__weights="uniform")  
knn_p.fit(X_train, y_train)  
print("KNN 算法 R²:", knn_p.score(X_test, y_test))  

# 线性回归数据标准化  
lr_steps = [("scaler", StandardScaler()), ("lr", LinearRegression())]  
lr_p = Pipeline(lr_steps)  
lr_p.fit(X_train, y_train)  
print("线性回归算法 R²:", lr_p.score(X_test, y_test))  
KNN 算法 R²: 0.6441485149216897
线性回归算法 R²: 0.6354638433202131

朴素贝叶斯

1, 概率基础

样本空间 :

随机试验 E 中, 实验的所有可能结果组成的集合, 称为 样本空间 S, 样本空间的每个元素, 即 E 的每个结果, 称 样本点

随机事件 :

进行随机试验时, 满足某种条件的样本点组成的集合, S 的子集, 称作 随机事件 , 只有一个样本点时, 称作 基本事件

概率 :

对于随机事件 A, 概率为:

$P(A)=\frac{A \text { 中基本事件数 }}{S \text { 中基本事件数 }}$

条件概率 :

定义事件 A 发生的前提下, 事件 B 发生的概率 P(B | A) 为条件概率:

$$P(B \mid A)=\frac{P(A B)}{P(A)}$$

由条件概率的定义可得, 事件 A 和 B 同时发生的概率 P(AB) 满足如下 乘法定理 :

$$P(A B)=P(B \mid A) P(A)$$

独立性:

定义 A 和 B 两个事件, 如果满足:

$$P(A B)=P(A) P(B)$$

则称事件 A, B 相互独立. 再结合乘法定理, 则有:

$$P(B \mid A) = P(B)$$

全概率公式:

设随机试验 E 的样本空间为 S, 若事件 $B_{1}$$B_{2}$,…, $B_{n}$ 构成一个完备事件组(即它们两两相互独立,事件并集为 S), 且都有正概率,则对任意一个 E 的事件 A,有如下公式成立:

$$P(A)=P\left(A \mid B_{1}\right) P\left(B_{1}\right)+P\left(A \mid B_{2}\right) P\left(B_{2}\right)+\ldots \ldots+P\left(A \mid B_{n}\right) P\left(B_{n}\right)$$

此公式即为全概率公式. 特别地,对于任意两随机事件 A 和 B,有如下成立:

$$P(B)=P(B \mid A) P(A)+P(B \mid \bar{A}) P(\bar{A})$$

贝叶斯公式:

设随机试验 E 的样本空间为 S, 若事件 $B_{1}$$B_{2}$,…, $B_{n}$ 构成一个完备事件组(即它们两两相互独立,事件并集为 S), 且都有正概率,则对任意一个 E 的正概率事件 A,有如下公式成立( i 为 1~n 的正整数):

$$P\left(B_{i} \mid A\right)=\frac{P\left(A B_{i}\right)}{P(A)}=\frac{P\left(A \mid B_{i}\right) P\left(B_{i}\right)}{P(A)} \ =\frac{P\left(A \mid B_{i}\right) P\left(B_{i}\right)}{\sum_{j=1}^{n} P\left(A \mid B_{j}\right) P\left(B_{j}\right)}$$

贝叶斯公式将求解 P(B | A) 的概率转换成求 P(A | B) 的概率, 在求解某个事件概率非常困难时, 转换一下更方便求解

例: 从以往数据得出, 机器调整良好时生产的产品合格的概率是 98%, 机器故障时合格的概率是 55%, 每天开机时机器调整良好的概率为 95%. 求某日开机生产的第一件产品是合格品时, 机器调整良好的概率?

解: 设事件 A 为产品合格, B 为机器调整良好, 则 $\bar{B}$ 为机器故障

$$P(B \mid A)=\frac{P(A \mid B) P(B)}{P(A \mid B) P(B)+P(A \mid \bar{B}) P(\bar{B})} \ =\frac{0.98 \times 0.95}{0.98 \times 0.95+0.55 \times 0.05} \ =0.97$$

先验概率和后验概率:

由以往的数据得出的概率称为 先验概率 , 如上例中的已知概率

得到某些信息后, 在先验概率的基础上进行修正而得到的概率, 称为 后验概率 , 如上例中求解的概率

2, 朴素贝叶斯算法原理

朴素贝叶斯是基于概率的分类算法, 前提假设各个特征(自变量)之间是相互独立的, 设类别(因变量)为 Y, Y 包含 m 个类别( $y_{1},\ldots, y_{m}$), 特征为 X, X 包含含有 n 个特征 ( $x_{1}, \ldots, x_{n}$), 然后通过计算比较, 在特征 X 确定的前提下, 类别 Y 中每个类别的概率大小, 概率最大者即为预测结果

设 Y 中任意一个类别为 y, 则:

$$P(y \mid X) = P\left(y \mid x_{1}, \ldots, x_{n}\right) \ =\frac{P(y) P\left(x_{1}, \ldots, x_{n} \mid y\right)}{P\left(x_{1}, \ldots, x_{n}\right)} \ =\frac{P(y) P\left(x_{1} \mid y\right) P\left(x_{2} \mid y\right) \ldots P\left(x_{n} \mid y\right)}{P\left(x_{1}, \ldots, x_{n}\right)} \ =\frac{P(y) \prod_{i=1}^{n} P\left(x_{i} \mid y\right)}{P\left(x_{1}, \ldots, x_{n}\right)}$$

上式分母为定值, 则:

$$P\left(y \mid X \right) \propto P(y) \prod_{i=1}^{n} P\left(x_{i} \mid y\right)$$

所以最终预测类别 $\hat{y}$ 为分子部分值最大对应的类别:

$$\hat{y}=\arg \max_{y} P(y) \prod_{i=1}^{n} P\left(x_{i} \mid y\right)$$

不同的朴素贝叶斯算法, 主要是对 $P\left(x_{i} \mid y\right)$ 的分布假设不同, 进而采取不同的参数估计方式. 最终主要就是通过计算 $P\left(x_{i} \mid y\right)$ 的概率来计算结果

例: 预测第 11 条记录, 学生是否上课

序号 天气 上课距离 成绩 课程 上课情况
1 选修 逃课
2 必修 上课
3 必修 上课
4 选修 逃课
5 选修 上课
6 必修 上课
7 选修 逃课
8 必修 上课
9 必修 逃课
10 选修 逃课
11 选修 ?
12 选修 ?

分别计算上课和逃课情况下, 各自的概率:

$$P(y=\text { 上课 }) \prod_{i=1}^{n} P\left(x_{i} \mid y=\text { 上课 }\right) \ =P(y=\text { 上课 }) P\left(x_{1}=\text { 阴 } \mid y=\text { 上课 }\right) P\left(x_{2}=\text { 近 } \mid y=\text { 上课 }\right) \ P\left(x_{3}=\text {差 } \mid y=\text { 上课 }\right) P\left(x_{4}=\text { 选修 } \mid y=\text { 上课 }\right) \ =0.5 \times 0.4 \times 1 \times 0.2 \times 0.2 \ =0.008$$ $$P(y=\text { 逃课 }) \prod_{i=1}^{n} P\left(x_{i} \mid y=\text { 逃课 }\right) \ =P(y=\text { 逃课 }) P\left(x_{1}=\text { 阴 } \mid y=\text { 逃课 }\right) P\left(x_{2}=\text { 近 } \mid y=\text { 逃课 }\right) \ P\left(x_{3}=\text { 差 } \mid y=\text { 逃课 }\right) P\left(x_{4}=\text { 选修 } \mid y=\text { 逃课 }\right) \ =0.5 \times 0.2 \times 0.2 \times 0.8 \times 0.8 \ =0.0128$$

可得预测结果为: 逃课

3, 平滑改进

当我们预测上例中, 第 12 条记录所属的类别时, 因为样本不是总体, 会出现上课的前提下, 距离远的概率为 0, 造成计算结果也为 0, 影响了预测结果, 因此需要平滑改进:

$$ P\left(x_{i} \mid y\right)=\frac{\text { 类别 } y \text { 中 } x_{i} \text { 取某个值出现的次数 }+ \alpha}{\text { 类别别 } y \text { 的总数 }+k * \alpha} $$

其中, k 为特征 $x_{i}$ 可能的取值数, α (α ≥ 0) 称为平滑系数, 当 α = 1 时, 称拉普拉斯平滑( Laplace smoothing)

4, 算法优点

即使训练集数据较少, 也能实现不错的预测; 算法训练速度非常快

因此算法假设特征之间是独立的, 可以单独考虑. 如果训练集有 N 个特征, 每个特征需要 M 个样本来训练, 则只需要训练 N*M 的样本数量, 而不是笛卡儿积的形式指数级增加

常用的朴素贝叶斯有: 高斯朴素贝叶斯, 伯努利朴素贝叶斯, 多项式朴素贝叶斯

5, 高斯朴素贝叶斯

适用于连续变量, 其假定各个特征 x 在各个类别 y 下服从正态分布:

$$x_{i} \sim N\left(\mu_{y}, \sigma_{y}^{2}\right)$$

算法使用概率密度函数来计算 $P\left(x_{i} \mid y\right)$ 的概率:

$$P\left(x_{i} \mid y\right)=\frac{1}{\sqrt{2 \pi \sigma_{y}^{2}}} \exp \left(-\frac{\left(x_{i}-\mu_{y}\right)^{2}}{2 \sigma_{y}^{2}}\right) $$

$\mu_{y}$: 在类别为 y 的样本中, 特征 $x_{i}$ 的均值
$\sigma_{y}$: 在类别为 y 的样本中, 特征 $x_{i}$ 的标件差

import numpy as np  
import pandas as pd  
from sklearn.naive_bayes import GaussianNB  

np.random.seed(0)  
x = np.random.randint(0, 10, size=(8, 3))  
y = np.array([0, 1, 0, 0, 0, 1, 1, 1])  
data = pd.DataFrame(np.concatenate([x, y.reshape(-1, 1)], axis=1),  
                   columns=['x1', 'x2', 'x3', 'y'])  
display(data[:3])  

gnb = GaussianNB()  
gnb.fit(x, y)  

print('类别标签:', gnb.classes_)  
print('每个类别的先验概率:', gnb.class_prior_)  
print('样本数量:', gnb.class_count_)  
print('每个类别下特征的均值:', gnb.theta_)  
print('每个类别下特征的方差:', gnb.sigma_)  

# 测试集  
x_test = np.array([[5, 7, 2]])  
print('预测结果:', gnb.predict(x_test))  
print('预测结果概率:', gnb.predict_proba(x_test))  
  x1	x2	x3	y
0	5	0	3	0
1	3	7	9	1
2	3	5	2	0


类别标签: [0 1]
每个类别的先验概率: [0.5 0.5]
样本数量: [4. 4.]
每个类别下特征的均值: [[5.   5.   3.  ]
 [6.5  5.75 7.5 ]]
每个类别下特征的方差: [[3.50000001 9.50000001 3.50000001]
 [5.25000001 7.68750001 2.75000001]]
预测结果: [0]
预测结果概率: [[0.99567424 0.00432576]]

6, 伯努利朴素贝叶斯

设实验 E 只有两个可能的结果, A 与 $\bar{A}$, 则称 E 为伯努利试验

伯努利朴素贝叶斯, 适用于离散变量, 其假设各个特征 x 在各个类别 y 下服从 n 重伯努利分布(二项分布), 因伯努利试验仅有两个结果, 算法会首先对特征值进行二值化处理(假设二值化结果为 1 和 0 )

$P\left(x_{i} \mid y\right)$ 的概率为:

$$P\left(x_{i} \mid y\right)=P\left(x_{i}=1 \mid y\right) x_{i}+\left(1-P\left(x_{i}=1 \mid y\right)\right)\left(1-x_{i}\right)$$

在训练集中, 会进行如下评估:

$$ P\left(x_{i}=1 \mid y\right)=\frac{N_{y i}+\alpha}{N_{y}+2 * \alpha} \ P\left(x_{i}=0 \mid y\right)=1-P\left(x_{i}=1 \mid y\right) $$

$N_{y i}$: 第 i 特征中, 属于类别 y, 数值为 1 的样本个数
$N_{y}$: 属于类別 y 的所有样本个数
$\alpha$: 平滑系数

from sklearn.naive_bayes import BernoulliNB  

np.random.seed(0)  
x = np.random.randint(-5, 5, size=(8, 3))  
y = np.array([0, 1, 0, 0, 0, 1, 1, 1])  
data = pd.DataFrame(np.concatenate([x, y.reshape(-1, 1)], axis=1),  
                   columns=['x1', 'x2', 'x3', 'y'])  
display(data[:3])  

bnb = BernoulliNB()  
bnb.fit(x, y)  

# 统计每个类别下, 特征中二值化后, 每个特征下值 1 出现的次数  
print('值 1 出现的次数:', bnb.feature_count_)  

# 每个类别的先验概率, 算法得到的该概率值是取对数后的结果,  
# 需要取指数还原  
print('每个类别的先验概率:', np.exp(bnb.class_log_prior_))  

# 每个类别下, 每个特征的概率(也需要取指数还原)  
print('每个特征的概率:', np.exp(bnb.feature_log_prob_))  

# 测试集  
x_test = np.array([[-5, 0, 2]])  
print('预测结果:', bnb.predict(x_test))  
print('预测结果概率:', bnb.predict_proba(x_test))  
  x1	x2	x3	y
0	0	-5	-2	0
1	-2	2	4	1
2	-2	0	-3	0


值 1 出现的次数: [[1. 2. 1.]
 [3. 3. 3.]]
每个类别的先验概率: [0.5 0.5]
每个特征的概率: [[0.33333333 0.5        0.33333333]
 [0.66666667 0.66666667 0.66666667]]
预测结果: [0]
预测结果概率: [[0.6 0.4]]

7, 多项式朴素贝叶斯

多项式朴素贝叶斯, 适用于离散变量, 其假设各个特征 x 在各个类别 y 下服从多项式分布(每个特征下的值之和, 就是该特征发生(出现)的次数), 因此每个特征值不能是负数

$P\left(x_{i} \mid y\right)$ 的概率为:

$$P\left(x_{i} \mid y\right)=\frac{N_{y i}+\alpha}{N_{y}+\alpha n} $$

$N_{y i}$: 特征 i 在类别 y 的样本中发生(出现)的次数
$N_{y}$: 类别 y 的样本中, 所有特征发生(出现)的次数
$n$: 特征数量
$\alpha$: 平滑系数

from sklearn.naive_bayes import MultinomialNB  

np.random.seed(0)  
x = np.random.randint(1, 5, size=(8, 3))  
y = np.array([0, 1, 0, 0, 0, 1, 1, 1])  
data = pd.DataFrame(np.concatenate([x, y.reshape(-1, 1)], axis=1),  
                   columns=['x1', 'x2', 'x3', 'y'])  
display(data[:3])  

mnb = MultinomialNB()  
mnb.fit(x, y)  

# 每个类别的样本数量  
print('每个类别的样本数:', mnb.class_count_)  

# 每个特征在每个类别下发生(出现)的次数  
print('每个特征发生(出现)次数:', mnb.feature_count_)  

# 每个类别下, 每个特征的概率(需要取指数还原)  
print('每个类别下特征的概率:', np.exp(mnb.feature_log_prob_))  

# 测试集  
x_test = np.array([[1, 1, 4]])  
print('预测结果:', mnb.predict(x_test))  
print('预测结果概率:', mnb.predict_proba(x_test))  
  x1	x2	x3	y
0	1	4	2	0
1	1	4	4	1
2	4	4	2	0


每个类别的样本数: [4. 4.]
每个特征发生(出现)次数: [[10. 14. 10.]
 [ 9. 11. 11.]]
每个类别下特征的概率: [[0.2972973  0.40540541 0.2972973 ]
 [0.29411765 0.35294118 0.35294118]]
预测结果: [1]
预测结果概率: [[0.36890061 0.63109939]]

利用鸢尾花数据集比较上述 3 个贝叶斯算法:

对不同的数据集, 根据其分布情况选择适合的算法, 能得到更好的结果

from sklearn.datasets import load_iris  
from sklearn.model_selection import train_test_split  

x, y = load_iris(return_X_y=True)  
x_train, x_test, y_train, y_test = train_test_split(x, y,  
                        test_size=0.25, random_state=0)  
models = [('高斯朴素贝叶斯分值:', GaussianNB()),  
          ('伯努利朴素贝叶斯分值:', BernoulliNB()),  
          ('多项式朴素贝叶斯分值:', MultinomialNB())]  
for name, m in models:  
    m.fit(x_train, y_train)  
    print(name, m.score(x_test, y_test))  
高斯朴素贝叶斯分值: 1.0
伯努利朴素贝叶斯分值: 0.23684210526315788
多项式朴素贝叶斯分值: 0.5789473684210527

决策树

1, 概念理解

决策树: 通过数据特征的差别, 用已知数据训练将不同数据划分到不同分支(子树)中, 层层划分, 最终得到一个树型结构, 用来对未知数据进行预测, 实现分类或回归

例如, 有如下数据集, 预测第 11 条数据能否偿还债务:

序号 有无房产 婚姻状况 年收入 能否偿还债务
1 单身 125
2 已婚 100
3 单身 100
4 已婚 110
5 离婚 60
6 离婚 95 不能
7 单身 85 不能
8 已婚 75
9 单身 90 不能
10 离婚 220
11 已婚 94 ?

我们可以将已知样本作如下划分(训练), 构建一颗决策树, 然后将第 11 条数据代入(测试), 落在哪一个叶子中, 它就是对应叶子的类别: 预测结果是

上例中, 层级已经不可再分, 但如果只划分到婚姻状况就不再划分如何实现预测?

决策树实现预测:
对于分类树, 叶子节点中, 哪个类别样本数量最多, 就将其作为未知样本的类别
对于回归树, 使用叶子节点中, 所有样本的均值, 作为未知样本的结果

对于上例, 如果只划分到婚姻状况, 那对于婚姻状况这个叶子中, 不能偿还的最多, 预测结果就是 不能

2, 分类决策树

对上例出现的情况, 我们会有如下问题:
我们为什么以年收入开始划分, 依据是什么? 划分顺序怎么定?
年收入为什么选 97.5 为划分阈值?
要划分多少层才好, 是否越多越好?
等等…

下面一步步来作讨论:

2.01, 信息熵

信息熵 : 用来描述信源的不确定度, 不确定性越大, 信息熵越大. 例如, 明天海南要下雪, 不确定性非常小, 信息熵很小, 明天海南要下雨, 不确定性大, 信息熵就大

设随机变量 X 具有 m 个特征值, 各个值出现的概率为 $p_{1}$, …, $p_{m}$, 且

$$p_{1}+p_{2}+\cdots+p_{m} = 1$$

则变量 X 的信息熵(信息期望值)为:

$$H(X)=-p_{1} \log_{2} p_{1} -p_{2} \log_{2} p_{2}-\cdots -p_{m} \log_{2} p_{m}$$ $$ =-\sum_{i=1}^{m}p_{i}\log_{2}p_{i}$$

2.02, 概率分布与信息商的关系

假设明天下雨的概率从 0.01 ~ 0.99 递增, 那么不下雨的概率就从 0.99 ~ 0.01 递减, 看看概率分布和信息熵的关系:

import numpy as np  
import matplotlib.pyplot as plt  

plt.rcParams['font.family'] = 'SimHei'  
plt.rcParams['font.size'] = 14  

# 下雨的概率  
p = np.linspace(0.01, 0.99, 100)  

# 信息熵  
h = -p * np.log2(p) - (1-p) * np.log2(1-p)  

# 绘制关系图  
plt.plot(p, h)  
plt.xlabel('概率分布')  
plt.ylabel('信息熵')  
plt.title('概率分布和信息熵关系图')  
plt.show()  

png

可见, 概率分布越均衡, 不确定性越大, 信息熵越大, 在所有概率都相等(p下雨=p不下雨)时, 信息熵最大

如果把概率分布转换到决策树的数据集上, 信息熵体现的就是数据的 不纯度 , 即样本类别的均衡程度. 因为数据集是未分类的, 要把它分类, 样本类别越均衡, 各个类别的占比(概率)分布越均衡, 不纯度越高, 信息熵越大

2.03, 信息增益

信息增益的定义如下:

$$I G\left(D_{p}, f\right)=I\left(D_{p}\right)-\sum_{j=1}^{n} \frac{N_{j}}{N_{p}} I\left(D_{j}\right) $$

$f$: 划分的特征
$D_{p}$: 父节点, 即使用特征 f 分割之前的节点
$I G\left(D_{p}, f\right)$: 父节点 $D_{p}$ 使用特征 f 划分下, 获得的信息增益
$I\left(D_{p}\right)$:父节点不纯度, 信息熵是度量标准之一
$D_{j}$: 父节点 $D_{p}$ 经过分割之后, 会产生 n 个子节点, $D_{j}$ 为第 j 个子节点
$I\left(D_{j}\right)$:子节点不纯度
$N_{p}$: 父节点 $D_{p}$ 包含样本的数量
$N_{j}$: 第 j 个子节点 $D_{j}$ 包含样本的数量

如果是二叉树, 即父节点最多分为左右两个子节点, 此时, 信息增益为:

$$I G\left(D_{p}, f\right)=I\left(D_{p}\right)-\frac{N_{l e f t}}{N_{p}} I\left(D_{l e f t}\right)-\frac{N_{r i g h t}}{N_{p}} I\left(D_{r i g h t}\right)$$

可见, 信息增益就是父节点的不纯度减去所有子节点的(加权)不纯度

父节点的不纯度是不变的, 在选择特征进行类别划分时, 应该让子节点的不纯度尽可能低, 这样训练可以更快完成, 信息增益也最大. 这正是训练决策树时, 选择特征顺序的依据

以开头的例子为例, 不纯度使用信息熵度量, 则根节点的信息熵为:

$$I\left(D_{p}\right)=-0.7 * \log _{2} 0.7-0.3 * \log _{2} 0.3=0.88$$

如果以”有无房产”划分, 则可计算得子节点信息熵:

$$\begin{array}{l} I\left(D_{\text {有房产 }}\right)=0 \ I\left(D_{\text {无房产 }}\right)=1 \end{array}$$

从而可得根节点信息增益为:

$$I G(\text { 有无房产 })=0.88-0.4 * 0-0.6 * 1=0.28 $$

同理,

$$I G(\text { 婚姻状况 })=0.205 $$

而对于年收入, 将年收入排序后, 取不同类别的分界点年收入(75 与 85, 95 与 100)的均值进行划分, 比较哪一个信息增益大:

$$\begin{array}{l} I\left(D_{\text {年收入 }< 80}\right)=0 \ I\left(D_{\text { 年收入 } >=80}\right)=0.954 \ I G(\text { 年收入 }=80)=0.88-0.2 * 0-0.8 * 0.954=0.117 \end{array}$$

同理,

$$I G(\text { 年收入 }=97.5)=0.395$$

可见, 以 年收入=97.5 划分时, 信息增益最大, 故首先选它进行划分

根节点划分结束, 第二层的父节点以同样的方式计算之后再次划分, 一直到划分停止

2.04, 过拟合与欠拟合

如果不设置条件, 是不是划分深度越大越好呢?

下面以鸢尾花数据集为例, 看看划分深度对模型效果的影响:

from sklearn.datasets import load_iris  
from sklearn.model_selection import train_test_split  
from sklearn.tree import DecisionTreeClassifier  

x, y = load_iris(return_X_y=True)  
x = x[:, :2]  
x_train, x_test, y_train, y_test = train_test_split(  
    x, y, test_size=0.25, random_state=0)  
'''  
参数介绍:  
criterion: 不纯度度量标准, 默认基尼系数 gini, 信息熵为 entropy  
splitter: 选择划分节点的方式, 默认最好 best, 随机 random  
max_depth: 划分深度, 默认 None 不设限  
min_samples_split: 划分节点的最小样本数, 默认 2  
min_samples_leaf: 划分节点后, 叶子节点的最少样本数, 默认 1  
max_features: 划分节点时, 考虑的最大特征数, 默认 None 考虑所有, 设置数量后会随机选择  
random_state: 随机种子, 控制模型的随机行为  
'''  
tree = DecisionTreeClassifier()  

# 定义列表, 用来储存不同深度下, 模型的分值  
train_score = []  
test_score = []  

# 设置深度 1~12 开始训练  
for depth in range(1, 13):  
    tree = DecisionTreeClassifier(criterion='entropy',  
                    max_depth=depth, random_state=0)  
    tree.fit(x_train, y_train) 

 

    train_score.append(tree.score(x_train, y_train))  
    test_score.append(tree.score(x_test, y_test))  

plt.plot(train_score, label='训练集分值')  
plt.plot(test_score, label='测试集分值')  
plt.xlabel('划分深度')  
plt.ylabel('分值')  
plt.legend()  
plt.show() 

png

可见, 划分深度小, 训练集和测试集的分值都小, 容易欠拟合
随着划分深度的增加, 分值都在增加, 模型预测效果也在变好
当深度增加到一定程度, 深度再增加, 训练集分值随着增加, 但造成了模型过分依赖训练集数据特征, 从而测试集分值减小, 容易过拟合

3, 不纯度度量标准

不纯度度量标准有:

信息熵

$$I_{H}(D)=-\sum_{i=1}^{m} p(i \mid D) \log _{2} p(i \mid D) $$

m: 节点 D 中含有样本的类别数量
$p(i \mid D)$: 节点 D 中, 属于类别 i 的样本占节点 D 中样本总数的比例(概率)

基尼系数

$$I_{G}(D)=1-\sum_{i=1}^{m} p(i \mid D)^{2}$$

错误率

$$I_{E}(D)=1-\max {p(i \mid D)}$$

看看各个度量标准与概率分布的关系:

def entropy(p):  
    return -p * np.log2(p) - (1-p) * np.log2(1-p)  

def gini(p):  
    return 1 - p**2 - (1-p)**2  

def error(p):  
    return 1 - np.max([p, 1-p], axis=0)  

p = np.linspace(0.0001, 0.9999, 200)  

en = entropy(p)  
er = error(p)  
g = gini(p)  

for i, lab, ls in zip([en, g, er],  
                      ['信息熵', '基尼系数', '错误率'],  
                      ['-', ':', '--']):  
    plt.plot(p, i, label=lab, linestyle=ls, lw=2)  

plt.legend()  
plt.xlabel('概率分布')  
plt.ylabel('不纯度')  
plt.show()  

png

可见, 无论选哪一种度量标准, 样本属于同一类, 不纯度都是 0; 样本中不同类别占比相同时, 不纯度最大

4, 决策树常用算法介绍

ID3

ID3 (Iterative Dichotomiser3), 迭代二分法特点:
-使用多叉树结构
-使用信息熵作为不纯度度量, 选择信息增益最大的特征划分
-经典算法, 简单, 训练快
局限:
-不支持连续特征
-不支持缺失值处理
-不支持回归
-倾向选择特征取值多的特征来划分, 例如按身份证号划分, 一个号码就是一个特征

C4.5

ID3算法改进而来, 特点:
-使用多叉树结构
-不支持回归
优化:
-支持缺失值处理
-连续值进行离散化处理
-信息熵作为不纯度度量, 但选择 信息增益率 最大的特征划分

信息增益率:

$$I G_{\text {Ratio}}\left(D_{p}, f\right)=\frac{I G_{H}\left(D_{p}, f\right)}{I_{H}(f)} $$

$I_{H}(f)$: 在特征 $f$ 下, 取各个特征值计算得到的信息熵之和, 其实就是特征 $f$ 的不纯度, 特征值越多, 特征不纯度越大

选择信息增益最大的特征来划分, 父节点的信息熵不变, 就要求信息增益的第二项 $\sum_{j=1}^{n} \frac{N_{j}}{N_{p}} I\left(D_{j}\right)$ 最小, 从而会倾向选择特征取值多的特征

因为, 特征取值多, 通常划分之后子节点的不纯度(信息熵)就更低, 例如极端情况, 选身份证划分, 划分之后不管什么类别, 子节点都只有一种类别, 不纯度都是 0, 第二项就是 0, 信息增益就最大

因此, 采用信息增益率, 将 信息增益/特征不纯度, 就避免了 特征不纯度大 造成 信息增益大 而选择类别多的特征来划分的情况

看看类别数量与信息熵的关系:

en = lambda p: np.sum(-p * np.log2(p))  

a1 = np.array([0.3, 0.7])  
a2 = np.array([0.3, 0.3, 0.4])  
a3 = np.array([0.25] * 4)  
a4 = np.array([0.1] * 10)  

print(en(a1), en(a2), en(a3), en(a4), sep='\n')  
0.8812908992306927
1.5709505944546684
2.0
3.321928094887362

CART

CART (Classification And Regression Tree), 分类回归树, 特点如下:
-使用二叉树结构
-支持连续值与缺失值处理
-分类时, 使用基尼系数作为不纯度度量, 选择基尼增益最大的特征划分
-回归时, 使用 MSE 或 MAE 最小的特征划分

5, 回归决策树

回归决策树因变量 y 是连续的, 使用叶子节点的均值来预测未知样本, 使用 MSE 或 MAE 作为特征划分的评估指标

在 scikit-learn 中, 使用的是优化的 CART 算法来实现决策树

以波士顿房价为例来实现(参数参考分类决策树):

from sklearn.datasets import load_boston  
from sklearn.tree import DecisionTreeRegressor  
from sklearn.model_selection import train_test_split  

x, y = load_boston(return_X_y=True)  
x_train, x_test, y_train, y_test = train_test_split(  
            x, y, test_size=0.25, random_state=1)  

tree = DecisionTreeRegressor(max_depth=5, random_state=0)  
tree.fit(x_train, y_train)  
print(tree.score(x_train, y_train))  
print(tree.score(x_test, y_test))  
0.9204825770764915
0.8763987309111113

K-Means 算法

1, 聚类

前面接触的算法, 都是 监督学习 , 即训练数据中自变量(特征)和因变量(结果)都是已知的, 用含有结果的训练集建立模型, 然后对未知结果的数据进行预测

聚类属于 无监督学习 , 训练数据中没有”已知结果的监督”. 聚类的目的, 就是通过已知样本数据的特征, 将数据划分为若干个类别, 每个类别成一个类簇, 使得同一个簇内的数据相似度越大, “物以类聚”, 不同簇之间的数据相似度越小, 聚类效果越好

聚类的样本相似度根据距离来度量

2, K-Means

即 K 均值算法, 是常见的聚类算法, 该算法将数据集分为 K 个簇, 每个簇使用簇内所有样本的均值来表示, 该均值称为”质心”

K-Means 算法的目标, 就是选择适合的质心, 使得每个簇内, 样本点距质心的距离尽可能的小, 从而保证簇内样本有较高相似度

算法实现步骤:

a, 从样本中选择 K 个点作为初始质心
b, 计算每个样本点到各个质心的距离, 将样本点划分到距离最近的质心所对应的簇中
c, 计算每个簇内所有样本的均值, 使用该均值作为新的质心
d, 重复 b 和 c, 重复一定次数质心一般会趋于稳定, 如果达到以下条件, 重复结束:
– 质心位置变化小于指定的阈值
– 达到最迭代环次数

对于算法的实现步骤, 我们有几个重要的疑问:

– 1.怎么评价质心是否达到了最佳位置?
– 2.初始质心随机选, 还是选在哪里?
– 3. K 值怎么定?

3, 算法优化目标

样本的相似度是根据距离来度量的, 一般使用簇内 误差平方和 (within-cluster SSE 簇惯性) 来作为优化算法的目标函数, 距离常用欧氏距离, 优化目标就是使 SSE 最小化:

$$S S E=\sum_{i=1}^{k} \sum_{j=1}^{m_{i}}\left(\left|x_{j}-\mu_{i}\right|^{2}\right)$$

k: 族的数量

$m_{i}$: 第 i 个簇含有的样本数量

${\mu}_{i}$: 第 i 个族的质心

$\left|x_{j}-\mu_{i}\right|$: 第 i 个族中,每个样本 $x_{j}$ 与质心 $\mu_{i}$ 的距离

同一个数据集, 相同的簇数, SSE 越小, 通常质心位置更佳, 算法模型更好

4, 初始质心的影响

初始质心可以随机选择, 但由于算法是通过迭代初始质心一步步实现, 初始质心的位置受随机性影响, 算法训练的最终结果也会受到影响

import numpy as np  
from sklearn.cluster import KMeans  
from sklearn.datasets import make_blobs  
import matplotlib.pyplot as plt  

plt.rcParams['font.family'] = 'Microsoft YaHei'   
plt.rcParams['font.size'] = 12   
plt.rcParams['axes.unicode_minus'] = False   

'''  
生成数据:  
n_samples: 样本数量  
n_features: 特征数  
centers: 聚类中心  
cluster_std: 簇的标准差, 可以统一指定, 也分别指定  
'''  
centers = [[1, 1], [5, 2], [2, 5]]  
x, y = make_blobs(n_samples=90,  
                  n_features=2,  
                  centers=centers,  
                  cluster_std=[2.2, 2.5, 2],  
                  random_state=0)  
# x 是特征, y 是类别标签  

# 绘制原始数据  
plt.figure(figsize=(12,8))  
plt.subplot(221)  
colors = np.array(['Coral', 'SeaGreen', 'RoyalBlue'])  
plt.scatter(x[:, 0], x[:, 1], c=colors[y], marker='.', label='原始数据')  
plt.title('原始数据')  

# 定义绘制聚类结果的函数  
def plot_cluster(model, train, test=None):  
    global colors  # 使用上面的颜色  
    cc = model.cluster_centers_ # 获取质心  
    label = model.labels_ # 获取聚类结果的标签  
    # 绘制质心  
    plt.scatter(cc[:, 0], # 质心的 x 坐标  
                cc[:, 1], # 质心的 y 坐标  
                marker='*',  
                s=150,  
                c=colors)  
    # 绘制训练集  
    plt.scatter(train[:, 0], train[:, 1], marker='.', c=colors[label])  
    # 绘制测试集  
    if test is not None:  
        y_hat = model.predict(test)  
        plt.scatter(test[:, 0], test[:, 1], marker='+',  
                    s=150, c=colors[y_hat])          
    # 标题  
    plt.title(f'SSE:{model.inertia_:.1f} 迭代次数:{model.n_iter_}')  

# 测试集  
test = np.array([[6, 5]])      
# 绘制不同初始质心的聚类结果  
seed = [1, 10, 100]  
for i in range(2, 5):  
    plt.subplot(2, 2, i)  
    kmeans = KMeans(n_clusters=3, # 簇数  
                    init='random', # 初始化方式  
                    n_init=1, # 初始化质心组数  
                    random_state=seed[i-2])  
    kmeans.fit(x)  
    plot_cluster(kmeans, x)  
    # 测试结果  
    plot_cluster(kmeans, x, test)  

png

从上图可以看出受初始化质心的影响, 聚类效果(SSE) 与 收敛速度(迭代次数) 会不同, 也即是可能会收敛到局部最小, 而不是整体最优; 同时, 也可以看出 SSE 越小, 整体结果越优, 越接近原始数据

5, K-Means++ 优化

针对上述初始化质心造成的问题, 设置初始化多组质心可以得到缓解, 但通常限于聚类簇数较少的情况, 如果簇数较多, 可能就不会有效

于是有了 K-Means++, 选择初始化质心时, 不在随机选, 而是按下述步骤进行选择:

– 1, 从训练数据中随机选择一个样本点, 作为初始质心
– 2, 对任意一个非质心样本点 $x^{(i)}$, 计算 $x^{(i)}$ 与现有最近质心的距离 $D\left(x^{(i)}\right)$
– 3, 根据概率 $\frac{D\left(x^{(i)}\right)^{2}}{\sum_{j=1}^{m}D\left(x^{(j)}\right)^{2}}$ 最大, 来选择最远的一个样本点 $x^{(i)}$ 作为质心, m 为非质心样本点数量
– 4, 重复 2 和 3, 直到选择了 K 个质心为止

做了优化之后, 保证了初始质心不会集中, 而是分散在数据集中

下面试试 K-Means++ 的聚类效果:

kmeans = KMeans(n_clusters=3, init='k-means++', n_init=1)  
kmeans.fit(x)  
plot_cluster(kmeans, x)  

png

6, 确定 K 值

K 是超参数, 需要预先人为指定

有时需要按照建模的需求和目的来选择聚类的个数, 但是 K 值选择不当, 聚类效果可能不佳. 例如实际 3 类, K 选了 10, 或者 K 无限制, 取值和样本点个数一样, 最后每个点一个类, SEE 为 0, 但是聚类已经毫无意义

如果不是硬性要求 K 的取值, 怎么确定最佳的 K 值呢? 一个比较好的方法就是 肘部法则 :

SEE 需要越小越好, K 又不能取太大, 我们可以看看他们之间的关系:

# 设置列表储存 SSE  
sse = []  
# K 值从 1~9 变化  
scope = range(1, 10)  
for k in scope:  
    kmeans = KMeans(n_clusters=k)  
    kmeans.fit(x)  
    sse.append(kmeans.inertia_)  

plt.xticks(scope)  
plt.plot(scope, sse, marker='o')  
plt.show()  

png

从上图可以看出, K 增加, SSE 减小, 但当 K > 3 时, K 再增加, SSE 减小变得缓慢, 所以 K 选择 3, 实际情况也是 3

6, Mini Batch K-Means

K-Means 每次迭代都会使用所有数据参与运算, 当数据集较大时, 会比较耗时. Mini Batch K-Means (小批量 K-Means) 算法每次迭代使用小批量样本训练, 逐批次累计的方式进行计算, 从而大大减少计算时间. 效果上, 通常只是略差于 K-Means

Mini Batch K-Means 算法实现步骤:

a, 从数据集中随机选择部分数据, 使用 K-Means 算法在这部分数据上聚类, 获取质心
b, 再从数据集中随机选择部分数据, 分别分配给最近的质心
c, 每个簇根据现有的数据集更新质心
d, 重复 b 和 c, 直到质心变化小于指定阈值或达到最大迭代次数

下面比较一下两个算法:

import time  
import pandas as pd  
from sklearn.cluster import MiniBatchKMeans  
from sklearn.metrics.pairwise import pairwise_distances_argmin  

import warnings
warnings.filterwarnings('ignore')

# 生成数据  
centers = [[1, 1], [400, 100], [100, 400]]  
x, y = make_blobs(n_samples=8000, n_features=2, centers=centers,  
                  cluster_std=120, random_state=0)  

# 定义函数, 用于计算模型训练时间  
def elapsed_time(model, data):  
    start = time.time()  
    model.fit(data)  
    end = time.time()  
    return end - start  

n_clusters = len(centers)  
kmeans = KMeans(n_clusters=n_clusters)  
mbk = MiniBatchKMeans(n_clusters=n_clusters,  
                      batch_size=200, # 小批量的大小  
                     n_init=10 # 和 KMeans 统一为 10  
                     )  
kmeans_time = elapsed_time(kmeans, x)  
mbk_time = elapsed_time(mbk, x)  

print('K-Means耗时:', kmeans_time)  
print('Mini Batch K-Means耗时:', mbk_time)  

# 绘制聚类效果  
plt.figure(figsize=(12, 5))  
model = [kmeans, mbk]  
for i, m in enumerate(model, start=1):  
    plt.subplot(1, 2, i)  
    plot_cluster(m, x)  
K-Means耗时: 0.051812171936035156
Mini Batch K-Means耗时: 0.04886937141418457

png

可见, 聚类耗时 K-Means 更多, 如果数据量很大, 耗时会更明显, 而聚类效果基本一样. 但发现颜色对不上, 这是因为质心的随机性, 聚类之后质心虽然最终落在相同的位置, 但是顺序不一致, 从而聚类的结果标签不一致, 即使是同一个算法, 运行几次, 标签结果也会不一致

我们将相同簇用相同的颜色绘制:

plt.figure(figsize=(12, 5))  
# 定义列表, 用来保存两个模型预测结果  
y_hat_list = []  
for i, m in enumerate(model, start=1):  
    plt.subplot(1, 2, i)  
    y_hat = m.predict(x)  
    if m == mbk:  
        '''  
        因为输出的质心顺序就是训练结果标签的顺序  
        故可以按 mbk 训练的质心, 去找 kmeans 训练的相同簇的质心  

        pairwise_distances_argmin(x, y) 解释:  
        依次取出数组 X 中的元素 x,   
        计算找到数组 Y 中与 x 距离最近的元素 y 的索引,   
        返回索引构成的数组  
        '''  
        # 将两者相同簇的质心一一对应并按 mbk 质心的顺序封装成字典  
        ar = pairwise_distances_argmin(  
        mbk.cluster_centers_, kmeans.cluster_centers_)  
        dict_ = dict(enumerate(ar))  
        # 用 mbk 的训练结果标签 y_hat 就可以寻找到对应的 kmeans 的质心  
        y_hat = pd.Series(y_hat).map(dict_).values  
    # 将预测结果加入到列表中  
    y_hat_list.append(y_hat)  

    plt.scatter(x[:, 0], x[:, 1], c=colors[y_hat], marker='.')  

png

比较两个算法聚类结果的差异:

same = y_hat_list[0] == y_hat_list[1]  
diff = y_hat_list[0] != y_hat_list[1]  
plt.scatter(x[same, 0], x[same, 1], c='g', marker='.', label='预测相同')  
plt.scatter(x[diff, 0], x[diff, 1], c='r', marker='.', label='预测不同')  
plt.legend()  
print('相同数量:', x[same].shape[0])  
print('不同数量:', x[diff].shape[0])  
相同数量: 7967
不同数量: 33

png

两个算法聚类结果只有 33 个样本点不同