Skip to content

python 学习

Life is short, you need Python.

人生苦短,我用Python。

python官网

python官方教程

Python教程-菜鸟教程

Python教程-廖雪峰

其他相关学习网站

流畅的python【电子书】

前言

python的特点

Pythonic(Python风格)

1

简单易学

python 能做的事

  • 爬虫
  • 数据分析
  • 机器学习
  • web开发
  • 自动化测试
  • 脚本处理

python 之禅

  • Simple is better than complex.

简洁胜于复杂

  • Now is better than never. Although never is often better than right now.

做肯定要比不做强,但还没准备好就去做也并非是一件好事。

Python 的特性

Python的特性

  1. Python是一门编程语言,它只是众多编程语言中的一种

  2. 语法简洁、优雅、编写的程序容易阅读。

  3. 跨平台,可以运行在Windows、Linux 以及 MacOS 等操作系统上。

  4. 易于学习。站在非计算机专业的角度来讲,如果把编程语言当做解决问题的工具,Python确实相较于C++、Java、 JavaScript等语言要易于学习和掌握。

  5. 极为强大而丰富的标准库与第三方库,比如电子邮件,比如图形GUI界面。

  6. Python是面向对象的语言。

我为什么喜欢Python

  1. 简洁,灵活,优雅,哲学(Python之禅)

  2. 易于上手难于精通

  3. Python即有动态脚本的特性,又有面向对象的特性,非常具有自己的特点

Python的缺点

  1. 运行速度慢,适合处理大数据量的计算任务。

  2. 内存管理困难,容易出现内存泄漏。

  3. 动态类型,不支持多重继承。

  4. 缺乏静态类型,不支持类型检查。

  5. 解释型语言,运行速度慢。(运行效率与开发效率,鱼与熊掌不可兼得)

编译型语言 (C、C++)、解释型语言 (Javascript、Python)

一个经典误区

世界不是只有网站,还有很多问题需要使用编程来解决。

Web是基础

  • 爬虫
  • 数据服务提供
  • 数据分析

本质在于这是一个互联网的时代,有网络的地方就需要Web,Web编程确实是最好的语言学习实践。

python能做些什么?

当你遇到问题时,随手拿起Python,编写一个工具,这才是 Python正确的打开方式。

  1. 爬虫

  2. 大数据与数据分析(Spark)

  3. 自动化运维与自动化测试

  4. Web开发:Flask、Django

  5. 机器学习:TensorFlow、Scikit-learn

  6. 胶水语言:混合其他如C++、Java等来编程。能够把用其他语言制作的各种模块(尤其是C/C++)很轻松地联结在一起。

  7. 脚本处理:Python可以用来写各种脚本,比如自动化运维脚本、自动化测试脚本、数据处理脚本等。

Python的前景

编程排名

1

Python的环境安装

Python的虚拟环境

关于python的虚拟环境

安装Python

IDLE与第一段Python代码

IDLE是“Integrated Development and Learning Environment”的缩写,中文意思是“集成开发与学习环境”。IDLE是Python编程语言的官方集成开发环境(IDE),提供了用于编写、运行和调试Python代码的功能。

IDLE

可能默认MacOS是python2版本,打开终端,输入IDLE,自动弹出IDLE。

如果是python3版本,在终端输入IDLE3

执行 print ('Hello, World')

1

什么是代码?

代码是现实世界事物在计算机世界中的映射

什么是写代码?

写代码是将现实世界中的事物用计算机语言来描述

Python的基本类型

数字类型

  • 整型int
  • 浮点型float
  • 布尔型bool
  • 复数complex

整型

Python的整型有四种:

  • 无符号整型(Unsigned Integers):无符号整型的范围是0到2^32-1,可以表示的数值范围更大。
  • 有符号整型(Signed Integers):有符号整型的范围是-2^31到2^31-1,可以表示的数值范围更大。
  • 长整型(Long Integers):长整型的范围是-2^63到2^63-1,可以表示的数值范围更大。
  • 布尔型(Boolean):布尔型只有True和False两个值。
布尔型(属于数字类型的一种)

布尔型只有True和False (首字母必须大写)两个值,可以用and、or、not运算符进行逻辑运算。

在python中,0、None、空字符串''、空列表[]、空元组()、空字典{}都被视为False,其他值都视为True。

1

各进制的表示与转换

二进制表示:0b开头

八进制表示:0o开头

十六进制表示:0x开头

bin(num)函数:将num转换为二进制字符串,如bin(10)返回'0b1010'

oct(num)函数:将num转换为八进制字符串,如oct(10)返回'0o12'

hex(num)函数:将num转换为十六进制字符串,如hex(10)返回'0xa'

int(num)函数,将num转换为十进制整数,如int(10.5)返回10,int(0b10)返回2,int(0x10)返回16。

int(str, base)函数,将字符串str转换为整数,base为进制数,如int('1010', 2)返回10。

base默认为10,如int('10')返回10。

如果字符串以0b开头、0o开头或0x开头,则base默认为2、8或16。如int('0b1010')返回10,int('0o12')返回10,int('0xa')返回10。

1

浮点型

浮点型的精度问题:

  • 由于计算机的存储机制,浮点数的精度受限于计算机的浮点数表示法。
  • 对于很小的数,计算机无法精确表示,会出现误差。
    • 例如:0.1 + 0.2 = 0.30000000000000004
    • 解决方法:使用decimal模块,可以精确表示小数。
    • 或者使用科学计数法表示。
    • 或者使用字符串来表示。

注意python3中,整数的除法结果默认是浮点型,需要使用//来进行整数除法。(// 结果仅保留整数部分)

1

1

复数

在Python中,复数(Complex Numbers)是一种数值类型,用于表示具有实部和虚部的数值。复数由一个实部和一个虚部组成,形式为 a + bj,其中 a 表示实部,b 表示虚部,而 j 表示虚数单位,满足 j^2 = -1。

Python中的复数可以通过内置的complex()函数创建,也可以直接使用虚数单位j来创建。

创建复数:

使用complex()函数创建复数,或者直接使用虚数单位j来创建:

python
num1 = complex(2, 3)  # 2 + 3j
num2 = 1 + 2j

获取实部和虚部:

使用.real属性获取复数的实部,使用.imag属性获取虚部:

python
real_part = num1.real  # 2.0
imag_part = num1.imag  # 3.0

复数运算:

复数可以进行基本的算术运算,包括加法、减法、乘法和除法:

python
sum_result = num1 + num2
diff_result = num1 - num2
prod_result = num1 * num2
div_result = num1 / num2

复数操作:

Python提供了许多操作来处理复数,例如共轭复数(.conjugate()方法)、复数的模(abs()函数)、复数的相位角(使用cmath.phase()函数)等。

复数的相位角,也称作辐角,是指复数在复平面上的位置角度。对于复数 z = a + bi (其中 a, b 属于实数集 R ),其相位角 W 的计算方法是 tan(W) = b / a 。相位角有多个可能的值,因为相位是可以周期性变化的,通常可以加上 2πk ( k 是整数)来表示所有可能的相位值。

复数的模是指复数在复平面上的距离,对于复数 z = a + bi (其中 a 和 b 是实数),其模可以通过公式 sqrt(a^2 + b^2) 直接计算得出。

python
conjugate_num = num1.conjugate()  # 共轭复数
abs_num = abs(num1)  # 复数的模
phase_angle = cmath.phase(num1)  # 复数的相位角

字符串类型

和JavaScript一样,字符串可以用单引号或双引号括起来,但不能同时使用。某些情况下,单引号和双引号的使用可以互换。仅使用单引号或者双引号情况下,可以使用转义字符\来转义单引号或双引号。

1

Python 3版本中,字符串是以Unicode编码的,也就是说,Python的字符串支持多语言。

Python提供了ord()函数获取字符的整数表示,chr()函数把编码转换为对应的字符。

如果知道字符的整数编码,还可以用十六进制这么写str。

由于Python的字符串类型是str,在内存中以Unicode表示,一个字符对应若干个字节。如果要在网络上传输,或者保存到磁盘上,就需要把str变为以字节为单位的bytes。

Python对bytes类型的数据用带b前缀的单引号或双引号表示。

注意区分'ABC'和b'ABC',前者是str,后者虽然内容显示得和前者一样,但bytes的每个字符都只占用一个字节。

以Unicode表示的str通过encode()方法可以编码为指定的bytes

纯英文的str可以用ASCII编码为bytes,内容是一样的,含有中文的str可以用UTF-8编码为bytes。含有中文的str无法用ASCII编码,因为中文编码的范围超过了ASCII编码的范围,Python会报错。

在bytes中,无法显示为ASCII字符的字节,用\x##显示。

反过来,如果我们从网络或磁盘上读取了字节流,那么读到的数据就是bytes。要把bytes变为str,就需要用decode()方法。

要计算str包含多少个字符,可以用len()函数。

len()函数计算的是str的字符数,如果换成bytes,len()函数就计算字节数。

1个中文字符经过UTF-8编码后通常会占用3个字节,而1个英文字符只占用1个字节。

1

由于Python源代码也是一个文本文件,所以,当你的源代码中包含中文的时候,在保存源代码时,就需要务必指定保存为UTF-8编码。当Python解释器读取源代码时,为了让它按UTF-8编码读取,我们通常在文件开头写上这两行。

python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

第一行注释是为了告诉Linux/OS X系统,这是一个Python可执行程序,Windows系统会忽略这个注释;

第二行注释是为了告诉Python解释器,按照UTF-8编码读取源代码,否则,你在源代码中写的中文输出可能会有乱码。

申明了UTF-8编码并不意味着你的.py文件就是UTF-8编码的,必须并且要确保文本编辑器正在使用UTF-8编码。

如果.py文件本身使用UTF-8编码,并且也申明了# -- coding: utf-8 --,打开命令提示符测试就可以正常显示中文。

多行字符串(三引号)

在Python中,可以使用三引号('''或""")来表示多行字符串。

1

转义字符

Python中的转义字符主要用于插入一些特殊字符或者行为,不能直接通过普通的键入输入。以下是一些常见的Python转义字符及其用途:

  1. \n:换行符。
  2. \\:反斜杠符号。
  3. \':单引号。
  4. \":双引号。
  5. \\a:响铃。
  6. \\b:退格。
  7. \\t:水平制表符。
  8. \\r:回车符。
  9. \\f:换页符。
  10. \\v:垂直制表符。

这些转义字符在编写字符串时非常有用,尤其是在引用字符串。

在Python中,\a 是一个转义字符,代表 响铃字符(Bell character)。这个字符的功能是通过计算机的扬声器发出一个短促的“嘟”的声音,通常用于提醒用户某个事件发生。

python
print("Hello, World!\a")

运行上述代码时,如果你的系统支持响铃字符,你可能会听到一个嘟的声响。

并不是所有的操作系统和终端都支持响铃字符,所以实际效果可能因环境而异。

在一些现代计算机和环境中,响铃功能可能被禁用或未实现,因此有可能听不到声音。

原始字符串

在Python中,有一种字符串前面加上r或R,表示原始字符串,即字符串中的字符串表示方式不发生转义。

python
print(r'\n')  # 输出 \n

字符串运算

Python支持字符串的拼接、重复、切片、比较、成员关系测试等运算。

拼接

字符串的拼接可以使用加号(+)运算符,也可以使用join()方法。

python
str1 = "Hello"
str2 = "World"
str3 = str1 + " " + str2
print(str3)  # 输出 "Hello World"

str4 = "Python"
str5 = "is" + " " + "awesome"
str6 = str4 + str5
print(str6)  # 输出 "Python is awesome"

str7 = "Hello"
str8 = "World"
str9 = " ".join([str7, str8])
print(str9)  # 输出 "Hello World"

重复

字符串的重复可以使用乘号(*)运算符。

python
str1 = "Hello"
str2 = str1 * 3
print(str2)  # 输出 "HelloHelloHello"

切片

字符串的切片可以使用方括号([])运算符。 [a🅱️c]表示从索引a开始,到索引b-1结束,每隔c取一个元素。 a、b、c可以是整数、负数、字符串、None。 如果a省略,表示从头开始;如果b省略,表示到结尾;如果c省略,表示取一个元素。 c为负数时,表示逆序切片。

python
str1 = "Hello, World!"
str2 = str1[0:5]  # 输出 "Hello"
str3 = str1[7:12]  # 输出 "World"

# 表示从头到尾取字符串str1的元素,步长为-1,即从右向左每次取一个元素,最终得到的结果为字符串str1的反向字符串。
str4 = str1[::-1]  # 输出 "!dlroW ,olleH"

# 每隔一个字符取一个子串
str5 = "abcdefg"  
str6 = str5[::2]  
print(str6)  # 输出 "aceg"

# 从倒数第二位开始每隔两个字符取一个子串
str7 = "123456789"  
str8 = str7[-2::-2]  
print(str8)  # 输出 "8642"

# 从第二位开始到倒数第二位,每隔三个字符取一个子串:
str9 = "abcdefghij"  
str10 = str9[1:-1:3]  
print(str10)  # 输出 "beh"

# 取字符串的奇数索引位置字符
str11 = "abcdefg"  
str12 = str11[1::2]  
print(str12)  # 输出 "bdf"

成员关系

字符串的成员关系测试可以使用 in 和 not in 运算符。

python
str1 = "Hello, World!"
if "H" in str1:
    print("H is in str1")  # 输出 "H is in str1"
if "M" not in str1:
    print("M is not in str1")  # 输出 "M is not in str1"

比较

Python的字符串类型支持比较运算符,可以比较两个字符串的大小。字符串的比较基于字符的 ASCII 值。

  1. ==:比较两个字符串是否相等。
  2. !=:比较两个字符串是否不相等。
  3. <:比较两个字符串的大小,如果第一个字符串小于第二个字符串,则返回True。
  4. :比较两个字符串的大小,如果第一个字符串大于第二个字符串,则返回True。

  5. <=:比较两个字符串的大小,如果第一个字符串小于等于第二个字符串,则返回True。
  6. =:比较两个字符串的大小,如果第一个字符串大于等于第二个字符串,则返回True。

  7. is:比较两个字符串是否指向同一个内存地址。
  8. n:判断字符串是否包含在另一个字符串中。
  9. not in:判断字符串是否不包含在另一个字符串中。
python
str1 = "Hello, World!"
str2 = "Hello, World!"
if str1 == str2:
    print("str1 is equal to str2")  # 输出 "str1 is equal to str2"

# 字符 'P' 的 ASCII 值为 80
# 字符 'J' 的 ASCII 值为 74
# 在字符串比较中,确实是从第一个字符开始逐个比较它们的 ASCII 值。根据 ASCII 值,'J' (74) < 'P' (80)。因此,"Python" 大于 "Java"。
str3 = "Python"
str4 = "Java"
if str3 > str4:
    print("str3 is more than str4")  # 输出 "str3 is more than str4"

字符串格式化

字符串的格式化可以使用%运算符。

在字符串内部,%s表示用字符串替换,它会把任何数据类型转换为字符串,%d表示用整数替换,有几个%?占位符,后面就跟几个变量或者值,顺序要对应好。如果只有一个%?,括号可以省略。

python
name = "John"
age = 30
print("My name is %s and I am %d years old." % (name, age))  # 输出 "My name is John and I am 30 years old."

'Age: %s. Gender: %s' % (25, True) # 输出 'Age: 25. Gender: True'

# 格式化整数和浮点数还可以指定是否补0和整数与小数的位数
print('%.2f' % 3.1415926) # 输出 3.14
print('%06d' % 12) # 输出 000012
print('%2d-%02d' % (3, 1)) # 输出 3-01
占位符替换内容
%c单个字符
%d整数
%f浮点数
%s字符串
%x十六进制整数
%o八进制整数

另一种格式化字符串的方法是使用字符串的format()方法,它会用传入的参数依次替换字符串内的占位符{0}、{1}……,不过这种方式写起来比%要麻烦得多。

python
name = "John"
age = 30
print("My name is {} and I am {} years old.".format(name, age))  # 输出 "My name is John and I am 30 years old."

'Age: {}. Gender: {}'.format(25, True) # 输出 'Age: 25. Gender: True'

# 格式化整数和浮点数还可以指定是否补0和整数与小数的位数
print('{:.2f}'.format(3.1415926)) # 输出 3.14
print('{0:06d}'.format(12)) # 输出 000012
print('{0:2d}-{1:02d}'.format(3, 1)) # 输出 3-01

字符串方法

Python的字符串类型提供了很多方法,可以对字符串进行各种操作。

python
str1 = "Hello, World!"
print(str1.upper())  # 输出 "HELLO, WORLD!"
print(str1.lower())  # 输出 "hello, world!"
print(str1.replace("H", "J"))  # 输出 "Jello, World!"

str2 = "Python is awesome"
print(str2.split())  # 输出 ['Python', 'is', 'awesome']
print(str2.split("o"))  # 输出 ['Pythn', ' is awe', 'm']

序列类型

有三种基本序列类型:list, tuple 和 range 对象。

列表的定义

list是一种有序的集合,是用方括号标注,逗号分隔的一组值。列表 可以包含不同类型的元素,list元素也可以是另一个list,但一般情况下,各个元素的类型相同。列表可以随时添加和删除其中的元素。

列表的基本操作
索引和切片

和字符串(及其他内置 sequence 类型)一样,列表也支持索引和切片

python
squares = [1, 4, 9, 16, 25]
squares[0]  # 索引操作将返回条目
# 1
squares[-1]
# 25
squares[-3:]  # 切片操作将返回一个新列表
# [9, 16, 25]
合并

列表还支持合并操作,用加号(+)运算符将两个列表合并成一个新的列表。

python
squares + [36, 49, 64, 81, 100]
# [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
更改列表中的元素
python
cubes = [1, 8, 27, 65, 125]
cubes[3] = 64
print(cubes)  # [1, 8, 27, 64, 125]
添加元素

可以在通过使用 list.append() 方法,在列表末尾添加新条目

python
cubes.append(216)  # 添加 6 的立方
cubes.append(7 ** 3)  # 和 7 的立方
print(cubes)  # [1, 8, 27, 64, 125, 216, 343]
删除元素

要删除list末尾的元素,用pop()方法。

要删除指定位置的元素,用pop(i)方法,其中i是索引位置。

python
cubes.pop()  # 删除末尾元素
print(cubes)  # [1, 8, 27, 64, 125, 216]

cubes.pop(2)  # 删除索引为 2 的元素
print(cubes)  # [1, 8, 64, 125, 216]
插入元素

要在指定位置插入元素,用insert(i, x)方法,其中i是索引位置,x是要插入的元素。

python
cubes.insert(2, 3 ** 3)  # 在索引为 2 的位置插入 3 的立方
print(cubes)  # [1, 8, 27, 64, 125, 216]
移除元素

要移除指定元素,用remove()方法。 注意,remove()方法只删除第一个匹配的元素。

python
cubes.remove(27)  # 移除索引为 2 的元素
print(cubes)  # [1, 8, 64, 125, 216]
排序

列表还支持排序操作,用sort()方法。

  • 对于数字(整数或浮点数),排序是按照数值大小进行的。
  • 对于字符串,排序是按照字母顺序(即 ASCII 值)进行的。
python
numbers = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
numbers.sort()
print(numbers)  # 输出 [1, 1, 2, 3, 3, 4, 5, 5, 5, 6, 9]

fruits = ['banana', 'apple', 'cherry', 'orange']
fruits.sort()
print(fruits)  # ['apple', 'banana', 'cherry', 'orange']
反转

列表还支持反转操作,用reverse()方法。

python
fruits.reverse()
print(fruits)  # ['orange', 'cherry', 'banana', 'apple']
遍历

列表支持遍历操作,用for循环或其他迭代器。

python
fruits = ['banana', 'apple', 'cherry', 'orange']
for fruit in fruits:
    print(fruit)
列表推导式

列表推导式是一种创建新列表的简洁方式。 它由一个表达式、一个或多个循环和可选的过滤条件组成。 列表推导式与普通循环相比,代码更简洁,更易于阅读。

python
# 计算 1 到 10 的平方
squares = [x ** 2 for x in range(1, 11)]
print(squares)  # [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

fruits = ['banana', 'apple', 'cherry', 'orange']
new_fruits = [fruit.upper() for fruit in fruits]
print(new_fruits)  # ['BANANA', 'APPLE', 'CHERRY', 'ORANGE']

元组

元组是另一种有序的集合,是用圆括号标注,逗号分隔的一组值。元组与列表类似,但元组是不可变的,不能添加或删除元素,一旦初始化就不能修改。

列出同学的名字的元组:

python
# 当你定义一个tuple时,在定义的时候,tuple的元素就必须被确定下来
students = ('Alice', 'Bob', 'Charlie', 'David')
# 可以正常地使用classmates[0],classmates[-1],但不能赋值成另外的元素。

# 只有1个元素的tuple定义时必须加一个逗号,来消除歧义,以免你误解成数学计算意义上的括号。
single_tuple = ('a',)
python
# tuple定义的时候有3个元素,分别是'a','b'和一个list
t = ('a', 'b', ['A', 'B'])
t[2][0] = 'X'
t[2][1] = 'Y'
print(t)
# ('a', 'b', ['X', 'Y'])
# tuple所谓的“不变”是说,tuple的每个元素,指向永远不变。即指向'a',就不能改成指向'b',指向一个list,就不能改成指向其他对象,但指向的这个list本身是可变的。
# 要创建一个内容也不变的tuple怎么做?那就必须保证tuple的每一个元素本身也不能变。

range 对象

range 类型表示不可变的数字序列,通常用于在 for 循环中循环指定的次数。

class range(start, stop[, step]) 创建一个 range 对象,包含从 start 到 stop-1 的整数,步长为 step。

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

python
# 创建一个从 0 到 9 的 range 对象
r = range(10)
print(list(r))
# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

# 创建一个从 1 到 10 的 range 对象,步长为 2
r = range(1, 11, 2)
print(list(r))
# [1, 3, 5, 7, 9]

# 创建一个从 0 到 10 且步长为 3 的 range 对象
r = range(0, 11, 3)
print(list(r))
# [0, 3, 6, 9]

# 创建一个从 0 到 -10 且步长为 -1 的 range 对象
list(range(0, -10, -1))
# [0, -1, -2, -3, -4, -5, -6, -7, -8, -9]

range 对象支持迭代,可以用 for 循环来遍历其中的元素。

python
for i in range(5):
    print(i)
# 0
# 1
# 2
# 3
# 4

集合类型

序列是有序的,集合是无序的,集合中的元素不能重复。

目前有两种内置集合类型,set 和 frozenset。

set 类型是可变的,其内容可以使用 add() 和 remove() 这样的方法来改变。 由于是可变类型,它没有哈希值,且不能被用作字典的键或其他集合的元素。

frozenset 类型是不可变并且为 hashable, 其内容在被创建后不能再改变;因此它可以被用作字典的键或其他集合的元素。

set 集合

set 是一种无序的集合,元素是唯一的,且不可变。

创建 set

python
# 定义一个空集合
empty_set = set()
print(empty_set)
# set()

# 显式定义一个有元素的集合
set1 = {1, 2, 3}
print(set1)
# {1, 2, 3}

# 提供一个字符串作为输入集合
set2 = set('hello')
print(set2)
# {'e', 'h', 'o', 'l'}

# 提供一个list作为输入集合
set3 = set([1, 2, 3])
# {1, 2, 3}

# 提供一个元组作为输入集合
set4 = set((1, 2, 3))
# {1, 2, 3}

基本操作

python
# 计算集合的长度
len(set1)
# 3

# 计算集合的并集(两个集合的并集)
set_sum = set1.union(set2) # 等同于 set1 | set2
print(set_sum)
# {1, 2, 3, 'e', 'h', 'o', 'l'}

# 计算集合的交集(两个集合的共同元素)
set_sum.intersection(set2) # 等同于 set_sum & set2
# {'e', 'h', 'o', 'l'}

# 计算集合的差集(集合1中有而集合2中没有的元素)
set_sum.difference(set2) # 等同于 set1 - set2
# {1, 2, 3}

# 计算集合的对称差集:对称差集是指两个集合中不同时存在的元素组成的集合。
# A = {1, 2, 3, 4},B = {3, 4, 5, 6},A - B = {1, 2},B - A = {5, 6} ,它们的对称差集为{1, 2, 5, 6}。
set1.symmetric_difference(set2) # 等同于 set1 ^ set2
# {1, 2, 3, 'h', 'e', 'l', 'o'}

# 判断元素是否在集合中
'h' in set2
# True

# 添加元素到集合
set1.add(4)
# {1, 2, 3, 4}

# 删除元素从集合
set1.remove(2)
# {1, 3, 4}

# 清空集合
set1.clear()
# set()

映射类型

目前仅有一种标准映射类型:字典。

dict 字典

dict 是一种无序的键值对集合,键必须是不可变且唯一的,值可以是任意的。

键可以是数字、字符串、元组,即key必须是不可变的,但不能是列表、字典、集合。

值可以是任意类型。

花括号 {} 用于创建空字典。

另一种初始化字典的方式是,在花括号里输入逗号分隔的键值对,这也是字典的输出方式。

字典的主要用途是通过关键字存储、提取值。

用 del 可以删除键值对。用已存在的关键字存储值,与该关键字关联的旧值会被取代。

通过不存在的键提取值,则会报错。

对字典执行 list(d) 操作,返回该字典中所有键的列表,按插入次序排列(如需排序,请使用 sorted(d))。

len(d) 返回字典 d 中的项数。

检查字典里是否存在某个键,使用关键字 in。

python
# 定义一个空字典
empty_dict = {}
print(empty_dict)
# {}

# 显式定义一个有元素的字典
dict1 = {'name': 'Alice', 'age': 20, 'city': 'Beijing'}
print(dict1)
# {'name': 'Alice', 'age': 20, 'city': 'Beijing'}

# 定义一个字典,使用关键字初始化
dict([('sape', 4139), ('guido', 4127), ('jack', 4098)])
# {'sape': 4139, 'jack': 4098, 'guido': 4127}
dict2 = dict(name='Bob', age=25, city='Shanghai')
print(dict2)
# {'name': 'Bob', 'age': 25, 'city': 'Shanghai'}

# 访问字典中的值
print(dict1['name'])
# Alice

# 修改字典中的值
dict1['age'] = 21
print(dict1)
# {'name': 'Alice', 'age': 21, 'city': 'Beijing'}

# 添加键值对
dict1['gender'] = 'female'
print(dict1)
# {'name': 'Alice', 'age': 21, 'city': 'Beijing', 'gender': 'female'}

# 删除键值对
del dict1['age']
print(dict1)
# {'name': 'Alice', 'city': 'Beijing', 'gender': 'female'}

# 遍历字典
for key in dict1:
    print(key, dict1[key])
# name Alice
# city Beijing
# gender female

# 字典推导式
dict3 = {x: x ** 2 for x in range(1, 6)}
print(dict3)
# {1: 1, 2: 4, 3: 9, 4: 16, 5: 25}

# 排序
sorted_dict = dict(sorted(dict1.items()))
print(sorted_dict)
# {'city': 'Beijing', 'gender': 'female', 'name': 'Alice'}
# dict1.items()返回一个包含字典 dict1 中所有键值对的视图对象,即 (('city', 'Beijing'), ('gender', 'female'), ('name', 'Alice'))。
# 默认情况下,sorted 函数会根据键(即每个元组的第一个元素)进行排序。

# list()方法可以将字典的键转换为列表
list(dict1)
# ['city', 'gender', 'name']

# in 运算符可以检查字典是否包含某个键
'name' in dict1
# True

变量

变量是程序中用来存储数据的内存单元,用来保存数据。

变量的命名规则

变量在程序中就是用一个变量名表示了,变量名必须是大小写英文、数字和_的组合,且不能用数字开头。

在Python中,等号=是赋值语句,可以把任意数据类型赋值给变量,同一个变量可以反复赋值,而且可以是不同类型的变量。

python
# 在内存中创建了一个'ABC'的字符串;在内存中创建了一个名为a的变量,并把它指向'ABC'。
a = 'ABC'
# 解释器创建了变量b,并把b指向a指向的字符串'ABC'
b = a
# 解释器创建了字符串'XYZ',并把a的指向改为'XYZ',但b并没有更改。
a = 'XYZ'
print(b)
# 输出'ABC'

常量

所谓常量就是不能变的变量,比如常用的数学常数π就是一个常量。

在Python中,通常用全部大写的变量名表示常量。

python
PI = 3.141592653589793

但事实上PI仍然是一个变量,Python根本没有任何机制保证PI不会被改变,所以,用全部大写的变量名表示常量只是一个习惯上的用法,如果你一定要改变变量PI的值,也没人能拦住你。

值类型与引用类型

Python中,值类型和引用类型是两种主要的变量类型。

值类型:

  • 数字、字符串、元组都是值类型,它们在内存中占据固定大小的空间,值类型变量在赋值或传参时,会将原有的值复制一份给新的变量。
  • 值类型变量在内存中存储的是值本身,因此,当变量的值发生变化时,变量本身不会改变。

引用类型:

  • 列表、字典、集合都是引用类型,它们在内存中占据可变大小的空间,引用类型变量在赋值或传参时,会将原有的值的引用复制一份给新的变量。
  • 引用类型变量在内存中存储的是值的引用,因此,当变量的值发生变化时,变量本身也会改变。
python
# 值类型
a = 10
id(a) # id()函数可以查看变量的内存地址
# 输出140727206727488
b = a
b = a
a = 20
id(a)
# 输出xxxxxxxxxxxx,但不是140727206727488
print(b)
# 输出10

# 引用类型
c = [1, 2, 3]
d = c
c[0] = 4
print(d)
# 输出[4, 2, 3]

运算符

运算符是一种特殊的符号,它代表着对两个或多个操作数的一种操作。

Python支持多种运算符,包括算术运算符、赋值运算符、比较运算符、逻辑运算符、成员运算符、身份运算符、位运算符等。

优先级

运算符的优先级决定了运算顺序,优先级高的运算符先进行计算。

Python的运算符优先级从高到低依次为:

  • 指数 (**)
  • 乘除法、取模 (%)
  • 加减法
  • 位运算符 (<<, >>, &, ^, |)
  • 比较运算符 (>, <, >=, <=, ==, !=)
  • 逻辑运算符 (not, and, or)
  • 成员运算符 (in, not in)
  • 身份运算符 (is, is not)
  • 赋值运算符 (=, +=, -=, *=, /=, %=, &=, ^=, |=)
  • 逗号运算符 (,)
  • 条件运算符 (?)

算术运算符

加法运算符(+)

python
# 加法运算符
2 + 3 
# 5

减法运算符(-)

python
# 减法运算符
5 - 3 
# 2

乘法运算符(*)

python
# 乘法运算符
2 * 3 
# 6

除法运算符(/)

python
# 除法运算符
7 / 3 
# 2.3333333333333335

取模运算符(%)

python
# 取模运算符
7 % 3 
# 1

幂运算符(**)

python
# 幂运算符
2 ** 3 
# 8

整除运算符(//)

python
# 整除运算符
7 // 3 
# 2

自然对数运算符(math.log)

python
# 自然对数运算符
import math
math.log(10)
# 2.302585092994046

取整运算符(math.floor, math.ceil)

python
# 取整运算符
import math
# 向下取整运算符
math.floor(3.14)
# 3

# 向上取整运算符
math.ceil(3.14)
# 4

赋值运算符

简单赋值运算符(=)

python
# 简单赋值运算符
a = 10
print(a)
# 输出10

自增运算符(+=)

没有a++或a--的情况下,Python提供了自增运算符和自减运算符。

python
# 自增运算符
a = 1
a += 1
print(a)
# 输出2

自减运算符(-=)

python
# 自减运算符
a = 1
a -= 1
print(a)
# 输出0

乘方赋值运算符(**=)

python
# 乘方赋值运算符
a = 2
a **= 3
print(a)
# 输出8

除法赋值运算符(/=)

python
# 除法赋值运算符
a = 10
a /= 2
print(a)
# 输出5.0

取模赋值运算符(%=)

python
# 取模赋值运算符
a = 10
a %= 3
print(a)
# 输出1

幂赋值运算符(**=)

python
# 幂赋值运算符
a = 2
a **= 3
print(a)
# 输出8

整除赋值运算符(//=)

python
# 整除赋值运算符
a = 10
a //= 3
print(a)
# 输出3

比较运算符

等于运算符(==)

python
# 等于运算符
1 == 1 
# True

不等于运算符(!=)

python
# 不等于运算符
1 != 1 
# False

大于运算符(>)

python
# 大于运算符
2 > 1 
# True

小于运算符(<)

python
# 小于运算符
1 < 2 
# True

大于等于运算符(>=)

python
# 大于等于运算符
2 >= 2 
# True

小于等于运算符(<=)

python
# 小于等于运算符
1 <= 2 
# True

字符串、列表、元组也可以进行比较运算

python
# 字符串也可以做比较运算
# 原因是字符串是由字符组成的序列,每个字符都有自己的ASCII码,因此可以比较两个字符串的ASCII码值。
'abc' > 'def'
# True

# 列表也可以做比较运算
# 会逐个比较两个列表中的元素。
[1, 2, 3] > [1, 2, 4]
# True

# 元组也可以做比较运算
# 会逐个比较两个元组中的元素。
(1, 2, 3) > (1, 2, 4)
# True

逻辑运算符

逻辑运算符操作和返回的是布尔值(True或False)。

int 0、空字符串''、空列表[]、空元组()、None、False都被视为False,其他值都视为True。

逻辑非运算符(not)

python
# 逻辑非运算符
not True 
# False

not not True 
# True

逻辑与运算符(and)

python
# 逻辑与运算符
True and True 
# True

逻辑或运算符(or)

python
# 逻辑或运算符
True or False 
# True

成员运算符

成员运算符是用来判断某个值或变量是否在序列(如字符串、列表、元组、集合等)中。

in运算符

python
# in运算符
'a' in 'abc' 
# True

# 列表也可以使用in运算符
1 in [1, 2, 3] 
# True

# 元组也可以使用in运算符
1 in (1, 2, 3) 
# True

# 集合也可以使用in运算符
1 in {1, 2, 3} 
# True

# 字典也可以使用in运算符
'name' in {'name': 'Alice', 'age': 20} 
# True

not in运算符

python
# not in运算符
'a' not in 'abc' 
# False

身份运算符

在Python中,is 运算符用于检查两个对象是否是同一个对象(即它们是否引用相同的内存地址)。

注意,对于小整数(通常在范围 -5 到 256 之间),Python 会进行整数缓存(integer interning),这意味着这些范围内的整数对象在第一次创建后会被缓存并重用。

所以不要依赖 is 来比较值:虽然在这个例子中 a is b 返回 True,但这只是因为整数缓存机制。如果你需要比较两个变量的值是否相等,应该使用 == 操作符,而不是 is。

is 的正确使用场景:is 主要用于检查对象的身份,最常见的是用于检查 None:

python
x = None
print(x is None)
# True

is运算符

python
# is运算符
# 由于a和b都是小整数,因此它们的内存地址相同。所以 a 和 b 引用了同一个整数对象,所以它们的内存地址是相同的,因此 a is b 返回 True。
a = 10
b = 10
a is b 
# True

# 超出小整数范围的比较
c = 257
d = 257
c is d 
# False

# 变量的内存地址相同
a = 10
b = a
a is b 
# True

# 列表
a = [1,2,3]
b = [2,1,3]
a == b # False,因为列表是有序且可变的序列类型。当你创建两个列表 a 和 b 时,它们的元素顺序必须完全相同,才能被认为是相等的。
a is b # False,因为两个变量的内存地址不同。

# 元组
a = (1,2,3)
b = (2,1,3)
a == b # False,因为元组是有序且不可变的序列类型。当你创建两个元组 a 和 b 时,它们的元素顺序必须完全相同,才能被认为是相等的。
a is b # False,因为两个变量的内存地址不同。

# 集合
a = {1,2,3}
b = {2,1,3}
a == b # True,因为集合是无序的,即使它们的元素顺序不同,只要它们包含相同的元素,这两个集合就是相等的。
a is b # False,因为两个变量的内存地址不同。

is not运算符

python
# is not运算符
a = 10
b = 20
a is not b 
# True

如何判断变量的值、身份与类型

位运算符

按位与运算符(&)

python
# 按位与运算符
5 & 3 
# 1

按位或运算符(|)

python
# 按位或运算符
5 | 3 
# 7

按位异或运算符(^)

python
# 按位异或运算符
5 ^ 3 
# 6

按位取反运算符(~)

python
# 按位取反运算符
~5 
# -6

左移运算符(<<)

python
# 左移运算符
5 << 1 
# 10

右移运算符(>>)

python
# 右移运算符
5 >> 1 
# 2

无符号右移运算符(>>>)

python
# 无符号右移运算符
-5 >> 1 
# 1073741822

表达式

表达式(Expression) 是运算符(operator)和操作数(operand) 所构成的序列。

表达式的优先级

and 优先级高于or,但是 not 优先级高于and。

1

注释

单行注释

python
# 这是一个单行注释

多行注释

python
'''
这是一个多行注释
可以跨越多行
'''

流程控制

python 使用缩进来表示代码块。

条件控制

if-elif-else语句

if语句执行有个特点,它是从上往下判断,如果在某个判断上是True,把该判断对应的语句执行后,就忽略掉剩下的elif和else。

python
# if-else语句
a = 10
if a > 5:
    print('a大于5')
else:
    print('a不大于5')
python
# if-elif-else语句
a = 10
if a > 5:
    print('a大于5')
elif a < 5: # elif是 else if的缩写,完全可以有多个 elif
    print('a小于5')
else:
    print('a等于5')
input函数

input()函数用于从控制台获取用户输入,并将其转换为字符串。

python
# input函数
a = input('请输入一个数字:')
print(a)

pass语句

  1. pass语句是空语句,是为了保持程序结构完整性。
  2. pass语句什么都不做,可以用作占位符。

让你保持在更抽象的层次进行思考。pass 会被默默地忽略掉,不会对程序的运行产生任何影响。

python
# pass语句
if a > 5:
    pass

match-case语句

Python 3.10 引入了新的语法 match-case,它可以取代 switch 语句。

match 语句接受一个表达式并把它的值与一个或多个 case 块给出的一系列模式进行比较。如果匹配成功,则执行对应的语句。 match 语句从上到下依次匹配,直到找到一个匹配项。 match 语句的每个 case 块可以包含一个或多个变量,这些变量将接收匹配的值。

最后一个代码块:“变量名” _ 被作为 通配符(相当于其他语言的default),并必定会匹配成功。如果没有 case 匹配成功,则不会执行任何分支。

match和其他语言中的switch语句的不同的是,大多数语言的 switch 语句支持 fallthrough 行为,即如果没有 break 语句,控制流会继续执行下一个 case。 Python 的 match 语句没有 fallthrough 行为,每个 case 都是独立的。

但是依旧为模式添加 if 作为守卫子句。如果守卫子句的值为假,那么 match 会继续尝试匹配下一个 case 块。

python
# match-case语句
a = 10
match a:
    case 10:
        print('a等于10')
    case 5:
        print('a等于5')
    case _:
        print('a不等于10或5')

你可以用 | (“或”)将多个字面值组合到一个模式中。

python
# match-case语句
a = 10
match a:
    case 10 | 5:
        print('a等于10或5')
    case _:
        print('a不等于10或5')
python
# match-case守卫子句
a = 7
match a:
    case 5 if a > 5:
        print('a大于5')
    case 10 if a < 10:
        print('a小于5')
    case _:
        print('a大于5且a小于10')
python
# match 语句可以把匹配后的值绑定到变量
age = 15

match age:
    # 第一个case x if x < 10表示当age < 10成立时匹配,且赋值给变量x
    case x if x < 10:
        print(f'< 10 years old: {x}')
    case 10:
        print('10 years old.')
    case 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18:
        print('11~18 years old.')
    case 19:
        print('19 years old.')
    case _:
        print('not sure.')
python
# 用match匹配来解析列表
args = ['gcc', 'hello.c', 'world.c']
# args = ['clean']
# args = ['gcc']

match args:
    # 列表仅有'gcc'一个字符串,没有指定文件名,报错
    case ['gcc']:
        print('gcc: missing source file(s).')
    # 列表第一个字符串是'gcc',第二个字符串绑定到变量file1,后面的任意个字符串绑定到*files(实际上表示至少指定一个文件)
    case ['gcc', file1, *files]:
        print('gcc compile: ' + file1 + ', ' + ', '.join(files))
    # 表示列表仅有'clean'一个字符串
    case ['clean']:
        print('clean')
    # 表示其他所有情况
    case _:
        print('invalid command.')

循环控制

Python的循环有两种,一种是for...in循环,一种是while循环。

for...in循环

for...in循环主要是用来遍历序列或者集合、字典

python
# 把每个元素代入变量x,然后执行缩进块的语句
sum = 0
for x in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]:
    sum = sum + x
print(sum)

# 双重循环
for x in range(1, 4):
    for y in range(1, 4):
        print(x, y)
python
# Python提供一个range()函数,可以生成一个整数序列,再通过list()函数可以转换为list
sum = 0
for x in list(range(1, 11)):
    sum = sum + x
print(sum)

# range()函数可以指定步长
for x in range(1, 11, 2):
    print(x)
# 输出:1 3 5 7 9

# 递减循环
for x in range(10, 0, -2):
    print(x)
# 输出:10 8 6 4 2

# 遍历列表
a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
for i in range(0, len(a), 2):
    print(a[i])
# 输出:1 3 5 7 9

# 遍历字典
d = {'name': 'Alice', 'age': 20, 'city': 'Beijing'}
for key in d:
    print(key, ':', d[key])
# 输出:name : Alice age : 20 city : Beijing

while循环

python
# while循环,只要条件满足,就不断循环,条件不满足时退出循环。
sum = 0
n = 99
while n > 0:
    sum = sum + n
    n = n - 2
print(sum)

break语句

break语句可以提前退出循环。(跳出后不会进入该for循环匹配的else语句块)

python
# break语句
sum = 0
n = 99
while n > 0:
    if n == 33:
        break
    sum = sum + n
    n = n - 2
print(sum)

continue语句

continue语句可以跳过当前循环的剩余语句,直接开始下一轮循环。(跳出后会进入到else语句块)

python
# continue语句
sum = 0
n = 99
while n > 0:
    if n == 33:
        continue
    sum = sum + n
    n = n - 2
print(sum)

循环中的else语句

循环的else语句在循环正常结束时执行,循环被break终止时不执行。

python
# for...in循环中的else语句
for x in range(1, 11):
    print(x)
else:
    print('循环结束')

# while循环中的else语句
n = 1
while n <= 10:
    print(n)
    n += 1
else:
    print('循环结束')

包 和 模块

包 > 模块 > 类 > 函数、变量

包(Package) 是 Python 中组织代码的一种方式。(类比文件夹)

包可以包含模块、子包(类比子文件夹)、子模块等。

包和文件夹的区别

包和文件夹的区别:

  1. 包是文件夹,可以包含模块、子包、子模块等。
  2. 包可以被导入到其他模块中使用。
  3. 包可以包含初始化文件 __init__.py,该文件可以用来控制包的导入行为。(从 Python 3.3 开始,引入了 隐式命名空间包(Implicit Namespace Packages),这意味着 __init__.py 文件不再是必需的。)
  4. 包可以被安装到 Python 的 site-packages 目录。

__init__.py 文件可以为空,这个模块的名称就是包的名称(目录名)。

命名空间

命名空间(Namespace) 是 Python 中用来管理变量名和其他对象的一种方式。

命名空间可以包含模块、函数、类、变量等。

例如,通过包名.模块名.函数名 这样的语法可以访问模块中的函数。

模块

模块(Module) 是 Python 中用来封装代码的一种方式。(类比文件)

模块可以被导入到其他模块中使用。

模块的导入(import)

模块的导入(import)有两种方式:

  1. 导入整个模块:import 模块名
  2. 导入模块中的某个函数或变量:from 模块名 import 函数名from 模块名 import 变量名
python
# 导入整个模块
import math
print(math.pi)

# 导入整个模块并给其别名
import math as m
print(m.pi)

# 导入模块中的某个函数
from math import pi
print(pi)

# 导入模块中的多个函数
from math import pi, sin, cos
# 也可以写作 from math import (pi, sin, cos)
print(pi, sin(pi), cos(pi))

# 导入模块中的某个变量
from math import e
print(e)

# 导入模块中的所有函数和变量
from math import *
print(pi, sin(pi), cos(pi), e)

# 注意,math模块中是需要将pi, sin, cos, e等函数和变量导入到当前命名空间才能使用。
# 导出方式为:__all__ = ['pi','sin', 'cos', 'e']
# 这样就可以直接使用pi, sin, cos, e等函数和变量。
# __all__规定了模块中哪些函数和变量可以被直接导入。
# 类似__all__的变量被称为模块的内置属性。
绝对导入
python
# 绝对导入
import 包名.包名.模块名 #执行入口文件所在的目录下的某个包
from 包名.包名.模块名 import 函数名 #执行入口文件所在的目录下的某个包
相对导入

.表示当前目录,..表示上级目录,...表示更上级目录,....表示更上上级目录,以此类推。

但是往上级是有限制的,不能超过顶级包的位置。否则会报错valueError: attempted relative import beyond top-level package

事实上,相对导入是根据模块内置的 __name__ 变量来判断的。

如果 __name__ 变量的值是 __main__,则表示当前模块是被直接运行的,所以__main__模块是不可以相对导入的,只能使用绝对导入。(除非运行时使用-m参数,把入口文件当作一个模块来运行)

否则表示当前模块是被导入的,其值是导入模块的名称。

python
# 相对导入
from .包名.模块名 import 函数名
from ..包名.模块名 import 函数名
from ...包名.模块名 import 函数名

模块的搜索路径

模块的搜索路径(Module Search Path) 是 Python 用来查找模块的路径。

模块的搜索路径存储在 sys.path 变量中。

sys.path 变量是一个列表,列表中的每个元素是一个字符串,表示一个搜索路径。

搜索路径的顺序:

  1. 当前目录(.
  2. 环境变量 PYTHONPATH 中的路径
  3. 标准库的安装路径

__init__.py 文件

__init__.py 文件是 Python 包的初始化文件,它可以用来控制包的导入行为。

当一个包被导入时,__init__.py文件会自动执行。

Python 会在 __init__.py 文件中查找 __all__ 变量,如果该变量存在,则只导入 __all__ 变量中指定的函数和变量。

如果 __all__ 变量不存在,则导入模块中所有的函数和变量。

__init__.py中适合处理批量导入模块。

python
# __init__.py
from .module1 import *
from .module2 import *
from .module3 import *

python模块导入的注意事项

  • 包和模块不会被重复导入
  • 应当避免循环导入包
  • 导入模块时,模块内的代码会被立即执行。

模块的内置变量

dir() 函数可以查看模块内置的变量、函数和类。

如果dir的变量没传,则默认打印当前模块的变量、函数和类。

__name__变量:

  • 如果模块是被直接运行,则__name__变量等于模块名。
  • 如果模块是被导入,则__name__变量等于模块名。

__package__变量:

  • 包含模块所在的包的名称。
  • 如果模块是顶层模块,则__package__变量等于None
  • 如果模块是子模块,则__package__变量等于父模块的名称。
  • 注意:如果模块是通过python -m命令运行,则__package__变量等于模块的导入路径。

__file__变量:

  • 包含模块的文件路径。
  • 如果模块是被直接运行,则__file__变量等于模块的文件路径。
  • 如果模块是被导入,则__file__变量等于模块的文件路径。
  • 注意:如果模块是通过python -m命令运行,则__file__变量等于模块的路径,而不是模块的导入路径。

__doc__变量:

  • 包含模块的文档注释字符串。
  • 如果模块没有文档字符串,则__doc__变量等于None

__all__变量:

  • 包含模块中所有函数和变量的列表。
  • 如果模块没有__all__变量,则dir()函数默认打印模块的变量、函数和类。
  • 如果模块有__all__变量,则dir()函数只打印__all__变量中指定的函数和变量。
python
# t/test.py
print('package:' + (__package__ or '当前模块不属于任何包'))
print('name:' + __name__)
print('doc:' + (__doc__ or '当前模块没有文档字符串'))
print('file:' + __file__)

# 输出:
# package:t
# name:t.test
# doc:当前模块没有文档字符串
# file: xxx\t\test.py
python
# index.py (当成应用程序入口使用python命令执行)
import t.test

print('package:' + (__package__ or '当前模块不属于任何包'))
print('name:' + __name__)
print('doc:' + (__doc__ or '当前模块没有文档字符串'))
print('file:' + __file__)

# 输出:
# package: 当前模块不属于任何包 (因为当前模块不属于任何包)
# name: __main__
# doc: 当前模块没有文档字符串
# file: index.py (当前模块是被直接运行的,作为入口文件,是不会显示完整路径的,显示的相对路径和执行python命令的目录有关)
python
# 模块内置变量
import math
print(dir(math))

```python
import math
print(dir(math))

print(dir())

__name__变量的经典应用

Make a script both importable and executable.(使脚本既可被其他模块导入又可作为普通模块自己执行)

python
// 判断当前模块是否是入口文件
if __name__ == 'main':
    print'This is a application entry point.'
else:
    print'This is a regular module'

另外,通过 "-m" 参数,python可以把一个文件当作模块来运行。

python
// 运行当前模块
python -m t.test
# 这里 -m 参数告诉python解释器,要运行t目录下的test模块。

标准库

Python 标准库(Standard Library) 是 Python 自带的库,包含了常用的模块和函数。

标准库的安装路径:

  • Windows:C:\PythonXX\Lib
  • Linux:/usr/lib/pythonXX/site-packages
  • macOS:/Library/Frameworks/Python.framework/Versions/XX/lib/pythonXX/site-packages

常用模块

  • os 模块:操作系统相关的功能,如文件和目录的操作、获取环境变量、获取系统信息等。
  • sys 模块:系统相关的功能,如退出程序、获取命令行参数、设置退出状态等。
  • math 模块:数学相关的功能,如对数、三角函数、随机数生成等。
  • json 模块:JSON(JavaScript Object Notation)数据格式的编解码。
  • re 模块:正则表达式。
  • argparse 模块:命令行参数解析。
  • datetime 模块:日期和时间相关的功能。
  • collections 模块:高级容器数据类型,如 OrderedDict、defaultdict、Counter 等。
  • itertools 模块:迭代器相关的功能,如循环、生成器、迭代器等。
  • functools 模块:函数相关的工具,如缓存、偏函数等。
  • operator 模块:操作符相关的函数,如获取操作符对应的函数等。
  • heapq 模块:堆排序算法。
  • bisect 模块:二分查找算法。
  • struct 模块:打包和解包二进制数据。
  • hashlib 模块:哈希算法。
  • hmac 模块:加密哈希算法。
  • base64 模块:Base64 编码。
  • xml 模块:处理 XML 数据。
  • csv 模块:处理 CSV(Comma-Separated Values)数据。
  • configparser 模块:处理配置文件。
  • contextlib 模块:上下文管理器。
  • logging 模块:日志记录。
  • unittest 模块:单元测试。
  • multiprocessing 模块:多进程。
  • concurrent.futures 模块:并发执行。
  • asyncio 模块:异步编程。
  • socket 模块:网络编程。
  • ssl 模块:安全套接字层。
  • select 模块:网络 I/O 多路复用。
  • selectors 模块:事件循环。
  • asyncore 模块:异步网络。
  • smtplib 模块:发送邮件。
  • imaplib 模块:处理邮件。
  • poplib 模块:处理邮件。
  • nntplib 模块:处理新闻组。
  • smtpd 模块:邮件服务器。
  • telnetlib 模块:Telnet 客户端。
  • uuid 模块:UUID 生成器。
  • ctypes 模块:调用 C 语言库。
  • sqlite3 模块:SQLite 数据库。
  • dbm 模块:数据库管理。
  • zlib 模块:压缩和解压数据。
  • gzip 模块:压缩和解压数据。
  • bz2 模块:压缩和解压数据。
  • lzma 模块:压缩和解压数据。
  • zipfile 模块:压缩和解压 ZIP 文件。
  • tarfile 模块:压缩和解压 TAR 文件。
  • csv 模块:处理 CSV 文件。
  • json 模块:处理 JSON 文件。
  • html 模块:处理 HTML 文件。
  • HTMLParser 模块:HTML 解析器。
  • xml 模块:处理 XML 文件。
  • email 模块:处理邮件。
  • cgi 模块:处理 CGI(Common Gateway Interface)请求。
  • webbrowser 模块:打开浏览器。
  • tkinter 模块:Tk 图形界面。
  • turtle 模块:海龟绘图。
  • urllib 模块:URL 处理。
  • venv 模块:创建虚拟环境。
  • asyncio 模块:异步编程。
  • asyncio.tasks 模块:异步任务。
  • asyncio.locks 模块:异步锁。
  • asyncio.queues 模块:异步队列。
  • asyncio.events 模块:异步事件。
  • asyncio.subprocess 模块:异步子进程。
  • asyncio.streams 模块:异步流。
  • asyncio.protocols 模块:异步协议。
  • asyncio.transports 模块:异步传输。
  • asyncio.runners 模块:异步运行器。
  • asyncio.exceptions 模块:异步异常。
  • asyncio.coroutines 模块:异步协程。

函数

命令行终端输入help(函数名) 可以查看函数的文档。

定义函数

在Python中,定义一个函数要使用def语句,依次写出函数名、括号、括号中的参数和冒号:,然后,在缩进块中编写函数体,函数的返回值用return语句返回。

如果没有 return 语句时,函数返回None。

python
# 定义一个简单的函数
def my_abs(x):
    # 对参数类型做检查,只允许整数和浮点数类型的参数。数据类型检查可以用内置函数isinstance()实现
    # 添加了参数检查后,如果传入错误的参数类型,函数就可以抛出一个错误
    if not isinstance(x, (int, float)):
        # raise语句抛出一个异常,并打印异常信息
        raise TypeError('bad operand type')
    if x >= 0:
        return x
    else:
        return -x

print(my_abs(-99))

空函数

如果想定义一个什么事也不做的空函数,可以用pass语句:pass可以用来作为占位符,比如现在还没想好怎么写函数的代码,就可以先放一个pass,让代码能运行起来。

python
def my_func():
    pass

返回多个值

Python的函数返回多值其实就是返回一个tuple,但写起来更方便。 在语法上,返回一个tuple可以省略括号,而多个变量可以同时接收一个tuple,按位置赋给对应的值

python
# 返回一个tuple
def my_func(x, y):
    return x + y, x - y
a, b = my_func(10, 5)
print(a, b) # 15 5

# 接收一个tuple
def my_func(t):
    a, b = t
    return a + b
t = (10, 5)
print(my_func(t)) # 15

序列解包

python
def my_func(a, b, c):
    print(a, b, c)
    return a + b + c

# 序列解包
t = (1, 2, 3)

my_func(*t)

函数参数

Python的函数正常定义的必选参数外,还可以使用默认参数、可变参数和关键字参数,使得函数定义出来的接口,不但能处理复杂的参数,还可以简化调用者的代码。

位置参数

python
# 函数有两个参数:x和n,这两个参数都是位置参数,调用函数时,传入的两个值按照位置顺序依次赋给参数x和n。
def power(x, n):
    s = 1
    while n > 0:
        n = n - 1
        s = s * x
    return s

默认参数

当函数有多个参数时,把变化大的参数放前面,变化小的参数放后面。变化小的参数就可以作为默认参数。

必选参数在前,默认参数在后。

默认参数最大的好处是能降低调用函数的难度。

python
# 调用power(5)时,相当于调用power(5, 2)。对于n > 2的其他情况,就必须明确地传入n。
def power(x, n=2):
    s = 1
    while n > 0:
        n = n - 1
        s = s * x
    return s

也可以不按顺序提供部分默认参数。当不按顺序提供部分默认参数时,需要把参数名写上。

python
def enroll(name, gender, age=6, city='Beijing'):
    print('name:', name)
    print('gender:', gender)
    print('age:', age)
    print('city:', city)

# city参数用传进去的值,其他默认参数继续使用默认值。
enroll('Adam', 'M', city='Tianjin')

默认参数的坑:

Python函数在定义的时候,默认参数L的值就被计算出来了,即[]。 因为默认参数L也是一个变量,它指向对象[]。 每次调用该函数,如果改变了L的内容,则下次调用时,默认参数的内容就变了,不再是函数定义时的[]了。

python
def add_end(L=[]):
    L.append('END')
    return L

add_end() # ['END']
# 第二次调用add_end()时,L已经不是空列表了,而是['END']。
add_end() # ['END', 'END']
python
# 要修改上面的例子,我们可以用None这个不变对象来实现,现在,无论调用多少次,都不会有问题。
def add_end(L=None):
    if L is None:
        L = []
    L.append('END')
    return L
# 不变对象一旦创建,对象内部的数据就不能修改,这样就减少了由于修改数据导致的错误。此外,由于对象不变,多任务环境下同时读取对象不需要加锁,同时读一点问题都没有。我们在编写程序时,如果可以设计一个不变对象,那就尽量设计成不变对象。

可变参数

在参数前面加了一个*号来定义可变参数。可变参数允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple。

python
# 定义可变参数函数
def calc(*numbers):
    sum = 0
    for n in numbers:
        sum = sum + n * n
    return sum
print(calc(1, 2, 3)) # 14

# Python允许你在list或tuple前面加一个*号,把list或tuple的元素变成可变参数传进去。
nums = [1, 2, 3]
print(calc(*nums)) # 14

关键字参数

在参数前面加了一个**号来定义关键字参数,关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。

python
# 定义关键字参数函数
# person函数里,我们保证能接收到name和age这两个参数,但是,如果调用者愿意提供更多的参数,我们也能收到。
def person(name, age, **kw):
    print('name:', name)
    print('age:', age)
    print('other:', kw)
# 例如做一个用户注册的功能,除了用户名和年龄是必填项外,其他都是可选项,利用关键字参数来定义这个函数就能满足注册的需求。
person('Adam', 45, city='Beijing', job='Engineer') # 输出:name: Adam, age: 45, other: {'city': 'Beijing', 'job': 'Engineer'}

# 以先组装出一个dict,然后,把该dict转换为关键字参数传进去
extra = {'city': 'Beijing', 'job': 'Engineer'}
person('Jack', 24, **extra) # 输出:name: Jack, age: 24, other: {'city': 'Beijing', 'job': 'Engineer'}

命名关键字参数

命名关键字参数是为了限制关键字参数的名字,同时可以提供默认值。

命名关键字参数需要一个特殊分隔符*,*后面的参数被视为命名关键字参数。

命名关键字参数必须传入参数名,和位置参数不同,如果没有传入参数名,调用将报错。

python
# 定义命名关键字参数函数
# 例如,只接收city和job作为关键字参数。
def person(name, age, *, city='Beijing', job):
    print(name, age, city, job)
# 由于命名关键字参数city具有默认值,调用时,可不传入city参数。
person('Jack', 24, job='Engineer') # 输出:Jack 24 Beijing Engineer

如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符*了。

如果没有可变参数,就必须加一个*作为特殊分隔符。

python
# 如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符*了
def person(name, age, *args, city, job):
    print(name, age, args, city, job)

person('Jack', 24, 'teacher', 'Engineer', city='Beijing') # 输出:Jack 24 ('teacher', 'Engineer') Beijing Engineer

参数组合

函数的参数可以组合使用,但请注意顺序:必选参数、默认参数、可变参数、命名关键字参数和关键字参数。

对于任意函数,都可以通过类似func(*args, **kw)的形式调用它,无论它的参数是如何定义的。

python
def f1(a, b, c=0, *args, **kw):
    print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw)

def f2(a, b, c=0, *, d, **kw):
    print('a =', a, 'b =', b, 'c =', c, 'd =', d, 'kw =', kw)

args = (1, 2, 3, 4)
kw = {'d': 99, 'x': 'foo'}
f1(*args, **kw) # a = 1 b = 2 c = 3 args = (4,) kw = {'d': 99, 'x': 'foo'}

递归函数

递归函数需要注意防止栈溢出。在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。

解决递归调用栈溢出的方法是通过尾递归优化。尾递归是指,在函数返回的时候,调用自身本身,并且,return语句不能包含表达式。这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。

尾递归的函数调用自身本身,并且,return语句不能包含表达式。这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。

大多数编程语言没有针对尾递归做优化,Python解释器也没有做优化,所以,即使把上面的fact(n)函数改成尾递归方式,也会导致栈溢出。

python
def fact(n):
    return fact_iter(n, 1)

def fact_iter(num, product):
    if num == 1:
        return product
    # 返回递归函数本身
    return fact_iter(num - 1, num * product)

设置递归函数的最大深度(不设置会使用系统默认深度)

python
import sys
print(sys.getrecursionlimit()) // 获取递归深度
sys.setrecursionlimit(100000) // 设置递归深度

变量的作用域

  1. 局部变量:在函数内部定义的变量,只在函数内部可见,在函数执行结束后,该变量会自动销毁。
  2. 全局变量:在函数外部定义的变量,可以在函数内部访问,但函数执行结束后,该变量不会自动销毁。
  3. 模块变量:在模块中定义的变量,可以在模块中访问,但模块执行结束后,该变量不会自动销毁。
  4. 类变量:在类中定义的变量,可以在类的实例中访问,但类的实例执行结束后,该变量不会自动销毁。
python
c = 50
def add(x,y):
    c = x + y
    print(c)

add(10,20) // 输出:30

print(c) // 输出:50

python中没有块级作用域

python
# c = 10 # 全局变量
def demo():
    c = 50 #局部变量
    for i in range0,9):
        a ='a'
        c += 1
    print(c) # 输出:59
    print(a) # 输出:a。 python中不存在块级作域,a 虽然在for循环中定义,但a仍然是局部变量,在函数中可以访问,在函数外无法访问。

demo()

python中的作用域链

函数中的变量,可以访问到函数外的变量,反之不可以。

python
c = 1
def func1():
    c = 2
    print(c) # 输出:2
    def func2():
        c = 3
        print(c) # 输出:3
    func2()
func1() # 输出:3

global 关键字

global 关键字,可以修改函数的变量为全局变量。

python
def func1():
    global c
    c = 2
func1()
print(c) # 输出:2

面向对象编程

面向对象编程——Object Oriented Programming,简称OOP,是一种程序设计思想。OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。

在实例方法中访问实例变量和类变量

在实例方法中,可以通过self访问实例变量,可以通过self.__class__ 或者 类名.类变量名 访问类变量。

python
class Person:
    name = 'John'
    def __init__(self, name):
        self.name = name
    
    def get_name(self):
        print(self.name)
        print(self.__class__.name)
        print(Person.name)
        return self.age

p = Person('Jack')
p.get_name()

定义类方法

在Python中,我们可以使用装饰器 @classmethod 来定义类方法。

python
class Person:
    name = 'John'
    def __init__(self, name):
        self.name = name

    @classmethod
    def get_name(cls):
        print(cls.name)
        return cls.name

p = Person('Jack')
Person.get_name()

定义静态方法

python中的静态方法不需要传递 selfcls 参数。

在Python中,我们可以使用装饰器 @staticmethod 来定义静态方法。

python中的静态方法一般被用作工具方法,比如和类本身无关的方法。

python
class Person:
    name = 'John'
    def __init__(self, name):
        self.name = name
    
    @staticmethod
    def get_name():
        print(Person.name)
        return Person.name

p = Person('Jack')
Person.get_name()

子类方法调用父类方法(super关键字)

在Python中,我们可以使用 super() 关键字来调用父类的方法。

python
class Person:
    def __init__(self, name):
        self.name = name

# 继承Person类
class Student(Person):
    def __init__(self, school, name):
        super().__init__(name)
        self.school = school

s = Student('MIT', 'Jack')
print(s.name) # 输出:Jack
print(s.school) # 输出:MIT

正则表达式和JSON

正则表达式

正则表达式是一种用来匹配字符串的强有力的武器。它的设计思想是用一种描述性的语言来给字符串定义一个规则,凡是符合规则的字符串,我们就认为它“匹配”了,否则,该字符串就是不合法的。

不使用正则表达式和JSON的情况下,我们可以使用字符串的方法来处理字符串。

Python
a = 'C|C++|Java|C#|Python|Javascript'
# 判断字符串中是否包含Python
print(a.index('Python') > -1) # 输出:True

# 判断字符串中是否包含Python
print('Python' in a) # 输出:True
python
# 导入re模块(正则表达式模块)
import re

a = 'C|C++|Java|C#|Python|Javascript'

# 判断字符串中是否包含Python
print(re.search('Python', a)) # 输出:<re.Match object; span=(15, 21), match='Python'>

# 查询字符串中所有的Python
r = re.findall('Python', a)
if len(r) > 0:
    print('字符串中包含Python')
else:
    print('字符串中不包含Python')
print(r) # 输出:['Python']
python
import re
a = 'C1C++2Java4C#8Python7Javascript'

# 从字符串中提取数字
r = re.findall('\d', a)
print(r) # 输出:['1', '2', '4', '8', '7']

元字符和普通字符

元字符表格

元字符描述
.匹配除换行符之外的任意字符
\w匹配字母或数字或下划线
\s匹配任意的空白符
\d匹配数字
\b匹配单词的开始或结束
^匹配字符串的开始
$匹配字符串的结束
\W匹配非字母、数字或下划线
\S匹配非空白符
\D匹配非数字
\B匹配非单词的开始或结束
*重复零次或更多次
\匹配转义字符
+重复一次或更多次
?重复零次或一次
[...]匹配字符组中的字符
[^...]匹配除了字符组中字符的所有字符
[a-z]匹配a到z之间的任意字符
[^a-z]匹配除a到z之间的任意字符
  • {n} 重复n次
  • {n,} 重复n次或更多次
  • {n,m} 重复n到m次
  • a|b 匹配字符a或字符b
  • () 匹配括号内的表达式,也表示一个组
  • (?:pattern) 匹配 pattern 但不获取匹配结果,也就是说这是一个非获取匹配,不进行存储供以后使用。这在使用 "或" 字符 (|) 来组合一个模式的各个部分是很有用。例如, 'industr(?:y|ies) 就是一个比 'industry|industries' 更简略的表达式。
  • (?=pattern) 正向肯定预查(look ahead positive assert),在任何匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如,"Windows(?=95|98|NT|2000)"能匹配"Windows2000"中的"Windows",但不能匹配"Windows3.1"中的"Windows"。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。
  • (?!pattern) 正向否定预查(negative assert),在任何不匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如"Windows(?!95|98|NT|2000)"能匹配"Windows3.1"中的"Windows",但不能匹配"Windows2000"中的"Windows"。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。
  • (?<=pattern) 反向(look behind)肯定预查,与正向肯定预查类似,只是方向相反。例如,"(?<=95|98|NT|2000)Windows"能匹配"2000Windows"中的"Windows",但不能匹配"3.1Windows"中的"Windows"。
  • (?<!pattern) 反向否定预查,与正向否定预查类似,只是方向相反。例如"(?<!95|98|NT|2000)Windows"能匹配"3.1Windows"中的"Windows",但不能匹配"2000Windows"中的"Windows"。

字符集

python
import re
s = 'abc, acc, adc, aec, afc, agc, ahc'
# 匹配acc和afc
r = re.findall('a[cf]c', s)
print(r) # 输出:['acc', 'afc']

k = re.findall('a[^cf]c', s)
print(k) # 输出:['abc', 'aec', 'agc', 'ahc']

# 匹配 c-f 之间的字符
m = re.findall('a[c-f]c', s)
print(m) # 输出:['acc', 'adc', 'aec', 'afc']

概括字符集

\w、\s、\d、\b、\W、\S、\D、\B 等都是概括字符集。

数量词

  • {n} 重复n次
  • {n,} 重复n次或更多次
  • {n,m} 重复n到m次
  • * 重复零次或更多次
  • + 重复一次或更多次
  • ? 重复零次或一次

贪婪与非贪婪

python中,默认是贪婪匹配,即尽可能多的匹配。

如果要使用非贪婪匹配,即在匹配到第一个符合条件的字符后,就停止匹配,可以在数量词后面加上一个 ? 号。

python
import re
a = 'python 11111java678php'

# 贪婪匹配
r = re.findall('[a-z]{3,6}', a)
print(r) # 输出:['python', 'java', 'php']

# 非贪婪匹配
r = re.findall('[a-z]{3,6}?', a)
print(r) # 输出:['pyt', 'hon', 'jav', 'php']

边界匹配符

  • ^ 匹配字符串的开始
  • $ 匹配字符串的结束
  • \b 匹配单词的开始或结束
  • \B 匹配非单词的开始或结束

python
import re
a = 'PythonPythonPythonPythonPython'
# 匹配Python,用括号括起来,表示一个组
r = re.findall('(Python){3}', a)
print(r) # 输出:['PythonPythonPython']

匹配模式参数

findall() 方法的的参数有三个:

  • 第一个参数是正则表达式
  • 第二个参数是要匹配的字符串
  • 第三个参数是匹配模式

匹配模式有:

  • re.I 忽略大小写
  • re.M 多行匹配,影响 ^ 和 $
  • re.S 使 . 匹配包括换行在内的所有字符
  • re.L 本地化识别匹配
  • re.U 根据Unicode字符集解析字符。这个标志影响 \w, \W, \b, \B.
  • re.X 该标志通过给予你更灵活的格式以便你将正则表达式写得更易于理解。
  • re.A 使ASCII字符类 (\w, \W, \b, \B, \d, \D) 匹配字节而不是Unicode字符。
python
import re
language = 'PythonC#\nJavaPHP'

# 忽略大小写
r = re.findall('c#', language, re.I)
print(r) # 输出:['C#']

# 使. 匹配包括换行符在内的所有字符
r = re.findall('C#.{1}', language, re.S)
print(r) # 输出:['C#\n']

# 多个标识符用 | 连接
r = re.findall('c#.{1}', language, re.S | re.I)
print(r) # 输出:['C#\n']

re.sub正则替换

re.sub() 方法用于替换字符串中的匹配项。

  • 第一个参数是正则表达式
  • 第二个参数是要替换的字符串(这个参数也可以是函数)
  • 第三个参数是要搜索的字符串
  • 第四个参数是替换次数,默认为0,表示替换所有匹配项。
  • 第五个参数是匹配模式
python
import re
language = 'PythonC#\nJavaPHP'
# 替换C#为Go
r = re.sub('C#', 'Go', language)
print(r) # 输出:PythonGo\nJavaPHP

常规字符串替换可以使用str.replace()方法。

python
language = 'PythonC#\nJavaPHP'
# 替换C#为Go
r = language.replace('C#', 'Go')
print(r) # 输出:PythonGo\nJavaPHP

把函数作为参数传递

python
import re
language = 'PythonC#\nJavaPHP'

def covert(value):
    matched = value.group()
    return '!!' + matched + '!!'

r = re.sub('C#', covert, language)
print(r) # 输出:Python!!C#!!\nJavaPHP
python
import re 

def covert(value):
    matched = value.group()
    if int(matched) >= 6:
        return '9'
    else:
        return '0'

r = re.sub('\d', covert, '123456789')
print(r) # 输出:123450000

search与match函数

re.search() 函数用于在字符串中搜索正则表达式模式的第一个匹配项。返回的是对象。没有匹配时返回None。 re.match() 函数用于在字符串的开头搜索正则表达式模式的第一个匹配项。返回的是对象。没有匹配时返回None。

search和match一旦搜索到第一个匹配项,就会停止搜索。

python
import re
language = 'PythonC#\nJavaPHP'
# 搜索第一个匹配项
r = re.search('C#', language)
print(r) # 输出:<re.Match object; span=(6, 8), match='C#'>
print(r.group()) # 输出:C#
print(r.span()) # 输出:(6, 8)
# 搜索第一个匹配项
r = re.match('Python', language)
print(r) # 输出:<re.Match object; span=(0, 6), match='Python'>
print(r.group()) # 输出:Python
print(r.span()) # 输出:(0, 6)

group分组

group() 方法用于返回匹配项。 group(0) 表示匹配项的全匹配,group(1) 表示匹配项的第一个分组,以此类推。

python
import re
s = 'life is short, i use python'
r = re.search('life(.*)python', s)
print(r.group()) # 等同于r.group(0)。输出:life is short, i use python
print(r.group(1)) # 输出: is short, i use 

# 使用单个组
t = re.findall('life(.*)python', s)
print(t) # 输出:[' is short, i use']

# 使用多个组
text = "name:Alice age:20, name:Bob age:25"
r3 = re.findall('name:(.*?)age:(.*?)[,]?', text)
print(r3)  # 输出:[('Alice ', '20'), ('Bob ', '25')]

理解JSON

JSON(JavaScript Object Notation)是一种轻量级的数据交换格式。

JSON对象:JSON对象是由键值对组成的对象,键是字符串,值可以是字符串、数字、布尔值、数组、对象、null。

反序列化

json.loads() 方法用于将字符串反序列化为对象。

python
import json
json_str = '{"name": "Jack", "age": 24}'
# 反序列化
json_obj = json.loads(json_str)
print(json_obj) # 输出:{'name': 'Jack', 'age': 24}
print(json_obj['name']) # 输出:Jack

序列化

json.dumps() 方法用于将对象序列化为字符串。

python
import json
json_obj = {'name': 'Jack', 'age': 24}
# 序列化
json_str = json.dumps(json_obj)
print(json_str) # 输出:{"name": "Jack", "age": 24}
print(type(json_str)) # 输出:<class 'str'>

JSON、JSON对象与JSON字符串

  • JSON对象:JSON对象是由键值对组成的对象,键是字符串,值可以是字符串、数字、布尔值、数组、对象、null。
  • JSON字符串:JSON字符串是由键值对组成的字符串,键是字符串,值可以是字符串、数字、布尔值、数组、对象、null。
  • JSON:JSON是一种轻量级的数据交换格式。

python的高级语法与用法

枚举其实是一个类

python
from enum import Enum
class Color(Enum):
    RED = 1
    GREEN = 2
    BLUE = 3
print(Color.RED) # 输出:Color.RED
print(Color.RED.name) # 输出:RED
print(Color.RED.value) # 输出:1

枚举和普通类相比有什么优势

  • 普通类是可变的,枚举是不可变的。
  • 普通类没有防止标签相同的功能,而枚举有防止标签相同的功能。

枚举类型、枚举名称与枚举值

通过name属性可以获取枚举名称,通过value属性可以获取枚举值。

python
from enum import Enum
class Color(Enum):
    RED = 1
    GREEN = 2
    BLUE = 3
print(Color.RED) # 输出:Color.RED
print(Color.RED.name) # 输出:RED
print(Color.RED.value) # 输出:1

枚举的遍历

python
from enum import Enum
class Color(Enum):
    RED = 1
    GREEN = 2
    BLUE = 3
for color in Color:
    print(color) # 输出:Color.RED Color.GREEN Color.BLUE

枚举的比较运算

python
from enum import Enum
class Color(Enum):
    RED = 1
    GREEN = 2
    BLUE = 3
    GREEN_ALIAS = 2

class Fruit(Enum):
    APPLE = 1
    BANANA = 2
    ORANGE = 3

print(Color.RED == Color.RED) # 输出:True
print(Color.RED is Color.RED) # 输出:True
print(Color.RED == Color.GREEN) # 输出:False
print(Color.RED == Color.GREEN_ALIAS) # 输出:False
print(Color.RED is Color.GREEN) # 输出:False
print(Color.RED is Color.GREEN_ALIAS) # 输出:False
print(Color.RED == Fruit.APPLE) # 输出:False
print(Color.RED is Fruit.APPLE) # 输出:False

枚举注意事项: 如果枚举名称相同,但是枚举值不同,那么枚举名称相同的枚举值是不相等的。后定义的枚举值可视为先定义同名枚举值的别名。

python
from enum import Enum
class Color(Enum):
    RED = 1
    GREEN = 2
    BLUE = 3
    GREEN_ALIAS = 2

# 遍历出现重复值的枚举类型,只输出先定义的枚举名称。
for color in Color:
    print(color) # 输出:Color.RED Color.GREEN Color.BLUE

# 如果想遍历出现重复值的枚举类型,输出所有枚举名称。
for color in Color.__members__.items():
    print(color) # 输出:('RED', <Color.RED: 1>) ('GREEN', <Color.GREEN: 2>) ('BLUE', <Color.BLUE: 3>) ('GREEN_ALIAS', <Color.GREEN: 2>)

for color in Color.__members__.values():
    print(color) # 输出:<Color.RED: 1> <Color.GREEN: 2> <Color.BLUE: 3> <Color.GREEN: 2>

for color in Color.__members__.keys():
    print(color) # 输出:RED GREEN BLUE GREEN_ALIAS

for color in Color.__members__:
    print(color) # 输出:RED GREEN BLUE GREEN_ALIAS

枚举转换

python
from enum import Enum
class Color(Enum):
    RED = 1
    GREEN = 2
    BLUE = 3
 
print(Color(1)) # 输出:Color.RED
print(Color(2)) # 输出:Color.GREEN
print(Color(3)) # 输出:Color.BLUE

IntEnum

IntEnum是Enum的子类,它的枚举值必须是整数。

python
from enum import IntEnum
class Color(IntEnum):
    RED = 1
    GREEN = 2
    BLUE = 3
print(Color.RED) # 输出:Color.RED
print(Color.RED.name) # 输出:RED
print(Color.RED.value) # 输出:1
print(Color(1)) # 输出:Color.RED
print(Color(2)) # 输出:Color.GREEN
print(Color(3)) # 输出:Color.BLUE

unique

unique是一个装饰器,它可以限制枚举类型的值不能相同。

python
from enum import Enum, unique
@unique
class Color(Enum):
    RED = 1
    GREEN = 2
    BLUE = 3
    RED_ALIAS = 1

# 报错:
# ValueError: duplicate values found in <enum 'Color'>: RED_ALIAS -> RED

一切皆对象

python
def func():
    pass
print(type(func)) # 输出:<class 'function'>

什么是闭包

闭包是指一个函数对象,它可以访问定义它的函数的局部变量。

python
def outer():
    x = 1
    def inner():
        print(x)
    return inner

f = outer()
x = 2
f() # 输出:1,不是2,因为inner函数访问的是定义它的函数的局部变量。

# __closure__ 属性是一个元组,元组中的元素是一个cell对象。cell对象是一个封装了局部变量的值的对象。
# 如果外层函数没有定义任何变量作为内层函数引用的局部变量(闭包),那么__closure__属性为None。
print(f.__closure__) # 输出:(<cell at 0x000001F42D877588: int object at 0x000000005D2582E0>,)
print(f.__closure__[0].cell_contents) # 输出:1
python
def f1():
    a = 10
    def f2():
        a = 20
        print(a)
    print(a)
    f2()
    print(a)

f1() # 输出:10 20 10
python
origin = 0

def go(step):
    new_pos = origin + step
    origin = new_pos
    return new_pos

print(go(2)) # 输出:UnboundLocalError: local variable 'origin' referenced before assignment
# 报错原因是在函数go中,函数内origin出现在赋值运算符左边的情况,所以origin被认为是一个局部变量,但是在函数go中又对origin进行了赋值,所以会报错。

正确的写法是:

python
origin = 0
def go(step):
    # 使用global声明origin为全局变量
    global origin
    new_pos = origin + step
    origin = new_pos
    return new_pos

print(go(2)) # 输出2

采用闭包的方式:

python
# 定义一个全局变量origin
origin = 0

# pos为工厂函数的参数
def factory(pos):
    def go(step):
        # 因为下面pos出现在赋值运算符左边的情况,不使用nonlocal声明pos为非局部变量的话,pos被认为是一个非局部变量。
        nonlocal pos
        new_pos = pos + step
        pos = new_pos
        return new_pos
    return go

# 传递origin作为参数
tourist = factory(origin)
print(tourist(2)) # 输出:2
print(tourist(3)) # 输出:5
print(tourist(4)) # 输出:9

函数式编程: 匿名函数、高阶函数、装饰器

lambda表达式(匿名函数)

匿名函数:函数名 = lambda 参数列表 : 函数体

python
# 定义一个匿名函数
f = lambda x, y: x + y
print(f(1, 2)) # 输出:3

# 相当于:
def add(x, y):
    return x + y

高阶函数:函数的参数是函数或者函数的返回值是函数。

python
# 定义一个函数,参数是函数
def func(f):
    return f()
# 定义一个函数,返回值是函数
def func2():
    return lambda: 1
    print(func2())
    print(func2()())

三元表达式

python中的三元表达式语法为:

python
x if condition else y
python
x = 1
y = 2
# 三元表达式: 如果x > y,返回x,否则返回y
r = x if x > y else y
print(r) # 输出:2

map

map函数用于将一个函数应用到一个序列的每个元素上,并返回一个迭代器。

python
# 定义一个函数,将列表中的每个元素乘以2
def func(x):
    return x * 2
# 定义一个列表
lst = [1, 2, 3, 4, 5]
# 使用map函数,将func函数应用到lst列表的每个元素上,返回一个迭代器map对象
r = map(func, lst)
# r是一个迭代器map对象
print(r) # 输出:<map object at 0x000001F42D877588>
# 将r转换为列表
print(list(r)) # 输出:[2, 4, 6, 8, 10]

map与lambda

map函数和lambda函数一起使用,可以简化代码,提高代码可读性。

python
# 定义一个列表
lst = [1, 2, 3, 4, 5]
# 使用map函数和lambda函数,将lst列表的每个元素乘以2,返回一个迭代器map对象
r = map(lambda x: x * 2, lst)
# r是一个迭代器map对象
print(r) # 输出:<map object at 0x000001F42D877588>
# 将r转换为列表
print(list(r)) # 输出:[2, 4, 6, 8, 10]
python
# 定义两个列表
list_x = [1, 2, 3]
list_y = [4, 5, 6]

# 计算两个列表对应位置的元素之和
r = map(lambda x, y: x + y, list_x, list_y)
print(list(r)) # 输出:[5, 7, 9]
python
# 两个列表元素个数不相等时,以短的列表为准!
list_x = [1, 2, 3]
list_y = [4, 5, 6, 7, 8]
# 计算两个列表对应位置的元素之和
r = map(lambda x, y: x + y, list_x, list_y)
print(list(r)) # 输出:[5, 7, 9]

reduce

reduce函数用于将一个函数应用到一个序列的每个元素上,并返回一个值。

reduce函数参数有3个参数:

  • func:函数,有2个参数,第一个参数是上一次调用func函数的返回值,第二个参数是当前元素。
  • iterable:可迭代对象。
  • initializer:可选参数,初始值。

python3中,reduce函数已经移除了,可以使用functools.reduce代替。

python
from functools import reduce
# 定义一个函数,将两个参数相加
def func(x, y):
    return x + y
# 定义一个列表
lst = [1, 2, 3, 4, 5]
# 使用reduce函数,将func函数应用到lst列表的每个元素上,返回一个值
r = reduce(func, lst)
print(r) # 输出:15
python
from functools import reduce
# 带初始值的reduce函数
r = reduce(lambda x, y: x + y, [1, 2, 3, 4, 5], 10)
print(r) # 输出:25

filter

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

python
# 定义一个函数,判断元素是否大于3
def func(x):
    return x > 3
# 定义一个列表
lst = [1, 2, 3, 4, 5]
# 使用filter函数,过滤掉lst列表中小于等于3的元素,返回一个迭代器filter对象
r = filter(func, lst)
# r是一个迭代器filter对象
print(r) # 输出:<filter object at 0x000001F42D877588>
# 将r转换为列表
print(list(r)) # 输出:[4, 5]
python
# 定义一个列表
lst = [1, 2, 3, 4, 5]
# 使用filter函数,过滤掉lst列表中小于等于3的元素,返回一个迭代器filter对象
r = filter(lambda x: x > 3, lst)
# r是一个迭代器filter对象
print(r) # 输出:<filter object at 0x000001F42D877588>
# 将r转换为列表
print(list(r)) # 输出:[4, 5]

装饰器

装饰器:装饰器是一种特殊的函数,它的参数是函数,返回值也是函数。

装饰器的使用语法为:

python
@decorator
def decorated_function():
    pass

装饰器的作用是在不改变原函数的情况下,为原函数添加新的功能。 装饰器的使用场景:

  • 日志记录:记录函数的执行时间、执行参数、执行结果等信息。
  • 权限验证:验证用户是否有权限执行某个函数。
  • 缓存:缓存函数的执行结果,避免重复计算。
  • 重试:重试函数执行失败的情况。
  • 异常处理:处理函数执行过程中出现的异常。
  • 输入输出转换:将函数的输入输出转换为其他格式。
  • 性能优化:优化函数的性能,例如缓存、并行计算等。
python
# 自定义一个装饰器
def decorator(func):
    def wrapper():
        print('before')
        func()
        print('after')
    return wrapper
# 定义一个函数
def func():
    print('func')
# 装饰器的使用
func = decorator(func)
func()
# 输出:
# before
# func
# after
python
import time

# 定义一个装饰器
def decorator(func):
    def wrapper():
        print('before')
        start_time = time.time()
        func()
        end_time = time.time()
        print('after')
        # 计算函数执行时间
        print('time: ', end_time - start_time)
    return wrapper

# 定义函数并使用装饰器
@decorator
def func():
    print('func')
    time.sleep(1)
# 调用函数
func()

带参数的装饰器:

python
# 定义一个装饰器
def decorator(func):
    # 定义一个包装函数, 接收任意参数, 并将其传递给原函数。可变参数和关键字(key word)参数来确保传递给原函数的参数数量和类型与原函数相同。
    def wrapper(*args, **kwargs):
        print('before')
        # 调用原函数, 并将参数传递给原函数
        func(*args, **kwargs)
        print('after')
    return wrapper

# 装饰器的使用
@decorator
def func(name, **kwargs):
    print('func', name, kwargs)
# 调用函数
func('Jack', age=18) 
# before 
# func Jack {'age': 18}
# after

flask框架中装饰器的使用:

python
@app.route('/') # 添加路由
@auth.login_required # 添加登录验证
def hello():
    return 'Hello World!'

实战:原生爬虫

  • 明确目的,找到数据的入口地址
  • 分析抓取目的确定抓取页面
  • 模拟HTTP请求,发送请求,获取响应HTML
  • 用正则表达式解析HTML,提取目标数据
python
# 引入正则表达式模块
import re
import ssl

# request 方法可以直接请求url
from urllib import request

ssl._create_default_https_context = ssl._create_unverified_context

class Spider():
    url = 'https://movie.douban.com/top250'
    # 容器的正则:([\s\S]*?)表示匹配任意字符,0或多次,非贪婪模式,如果不加?则为贪婪模式,会匹配到最后一个</li>
    # 在字符串前加r表示不转义,否则会报错
    list_pattern = r'<div class="info">[\s\S]*?<div class="bd">[\s\S]*?</div>[\s\S]*?</div>'
    # 名称的正则
    name_pattern = r'<span class="title">([^&]*?)</span>'
    # 评分的正则
    score_pattern = r'<span class="rating_num" property="v:average">([\s\S]*?)</span>'
    
    # 获取网页内容
    def __fetch_content(self):
        # 创建请求对象并添加headers
        headers = {
            'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36'
        }
        # 创建请求对象,并添加headers,防止触发反爬
        req = request.Request(url=Spider.url,headers=headers,method="GET")
        # request.urlopen() 用来请求url
        r = request.urlopen(req)

        # read() 读取url的内容
        htmls = r.read() 
        # 将bytes转换为str
        htmls = str(htmls, encoding='utf-8')

        # 如果有必要可以将htmls写入文件进行排查
        # with open('output.html', 'w', encoding='utf-8') as f:
            # f.write(htmls)
        
        return htmls
    
    def __analysis(self, htmls):
        # 通过信息容器正则表达式获取容器中的内容
        list_html = re.findall(Spider.list_pattern, htmls)
        
        infoArr = []
        # 遍历list_html, 并通过名称和评分的正则表达式获取名称和评分
        # 将获取的信息封装到字典中,并添加到infoArr中
        for html in list_html:
            name = re.findall(Spider.name_pattern, html)
            score = re.findall(Spider.score_pattern, html)
            info = {'name':name, 'score':score}
            infoArr.append(info)

        return infoArr
    # 细化解析数据
    def __refine(self, arr):
        l =  lambda item: {
            # strip() 方法用于移除字符串头尾指定的字符(默认为空格或换行符)或字符序列。
            'name':item['name'][0].strip(),
            'score':item['score'][0]
            }
        return map(l, arr)
    # 排序
    def __sort(self, arr):
        #sorted() 函数对所有可迭代的对象进行排序操作。
        # key -- 主要是用来进行比较的元素,只有一个参数,具体的函数的参数就是取自于可迭代对象中,指定可迭代对象中的一个元素来进行排序。
        # reverse -- 排序规则,reverse = True 降序 , reverse = False 升序(默认)。
        arr = sorted(arr, key=self.__sort_seed,reverse=True)
        return arr
    # 排序种子: 这里指定按照score排序
    def __sort_seed(self, item):
        r = re.findall(r'[1-9]\d*\.?\d*', item['score'])
        number = float(r[0])
        return number
    def __show(self, arr):
        for rank in range(0, len(arr)):
            print('rank ' + str(rank + 1)
            + '  : '+ arr[rank]['name']
            + '   ' + arr[rank]['score'])
    # 入口函数
    def go(self):
        # 获取网页内容
        htmls = self.__fetch_content()
        # 分析网页内容
        infos = self.__analysis(htmls)
        # 整理数据
        infos = list(self.__refine(infos))
        # 排序
        infos = self.__sort(infos)
        # 展示
        self.__show(infos)
    
# 实例化
spider = Spider()
# 调用spider的go方法
spider.go()

# 输出:
# rank 1  : 肖申克的救赎   9.7
# rank 2  : 霸王别姬   9.6
# rank 3  : 控方证人   9.6
# rank 4  : 泰坦尼克号   9.5
# rank 5  : 阿甘正传   9.5
# rank 6  : 美丽人生   9.5
# rank 7  : 辛德勒的名单   9.5
# rank 8  : 千与千寻   9.4
# rank 9  : 这个杀手不太冷   9.4
# rank 10  : 星际穿越   9.4
# rank 11  : 盗梦空间   9.4
# rank 12  : 楚门的世界   9.4
# rank 13  : 忠犬八公的故事   9.4
# rank 14  : 海上钢琴师   9.3
# rank 15  : 放牛班的春天   9.3
# rank 16  : 机器人总动员   9.3
# rank 17  : 无间道   9.3
# rank 18  : 熔炉   9.3
# rank 19  : 触不可及   9.3
# rank 20  : 教父   9.3
# rank 21  : 三傻大闹宝莱坞   9.2
# rank 22  : 疯狂动物城   9.2
# rank 23  : 大话西游之大圣娶亲   9.2
# rank 24  : 当幸福来敲门   9.2
# rank 25  : 寻梦环游记   9.1

Pythonic 与 Python杂记

用字典映射代替switch case语句

python
switch (day) {
    case 0:
        dayName = "Sunday";
        break;
    case 1:
        dayName = "Monday";
        break;
    case 2:
        dayName = "Tuesday";
        break;
    case 3:
        dayName = "Wednesday";
        break;
    case 4:
        dayName = "Thursday";
        break;
    case 5:
        dayName = "Friday";
        break;
    case 6:
        dayName = "Saturday";
        break;
    default:
        dayName = "Unknown";
        break;
}
python
day = 0

def get_sunday():
    return "Sunday"
def get_monday():
    return "Monday"
def get_tuesday():
    return "Tuesday"
def get_wednesday():
    return "Wednesday"
def get_thursday():
    return "Thursday"
def get_friday():
    return "Friday"
def get_saturday():
    return "Saturday"
def get_default():
    return "Unknown"

# 使用字典映射代替switch case语句
switcher = {
    0: get_sunday,
    1: get_monday,
    2: get_tuesday,
    3: get_wednesday,
    4: get_thursday,
    5: get_friday,
    6: get_saturday
}

# 使用get方法获取字典中的值,如果不存在则返回默认值
dayName = switcher.get(day, get_default)()

回顾列表推导式

列表推导式是一种简洁的语法,用于创建列表、集合、字典等数据结构。

语法为:

python
[expression for item in iterable if condition]

expression:推导表达式,用于生成列表中的元素。

item:可迭代对象中的元素。 iterable:可迭代对象,例如列表、元组、集合、字典等。 condition:条件,用于过滤可迭代对象中的元素。

python
# 列表推导式
# 创建一个列表,列表中包含1到10的数字
lst = [i*i for i in range(1, 11)]
print(lst) # 输出: [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

# 对偶数进行立方
lst2 [i**3 for i in range(1, 11) if i%2==0]
print(lst2) # 输出: [4, 16, 36, 64, 100]

# set 集合推导式
s = {i for i in range(1, 11)}
print(s) # 输出: {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

# dict 字典推导式
d = {i: i**2 for i in range(1, 11)}
print(d) # 输出: {1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81, 10: 100}

# 元组推导式
t = tuple(i for i in range(1, 11))
print(t) # 输出: (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

# 字典推导式-交互key和value
d = {'a': 1, 'b': 2, 'c': 3}
d2 = {v: k for k, v in d.items()}
print(d2) # 输出: {1: 'a', 2: 'b', 3: 'c'}

iterator与generator

iterator:迭代器是一个对象,它实现了迭代器协议,即实现了__iter__()和__next__()方法。可被for循环迭代的对象。

可迭代对象(Iterable)与迭代器(Iterator)的区别 :

  • 可迭代对象:实现了 __iter__() 方法的对象
  • 迭代器:同时实现了 __iter__()__next__() 方法的对象。

列表、元组、字典它们是可迭代的对象,但不是迭代器,因为它们没有实现__next__()方法。

python
# 自定义一个可迭代的对象
class MyIterator:
    def __init__(self, data):
        self.data = data
        self.index = 0
    
    def __iter__(self):
        # 返回迭代器对象(即自身)
        return self

    def __next__(self):
        # 如果索引大于等于列表长度,抛出异常
        if self.index >= len(self.data):
            # 抛出异常
            raise StopIteration
        # 获取当前索引对应的值
        value = self.data[self.index]
        # 索引加1
        self.index += 1
        # 返回值
        return value

# 创建一个可迭代的对象
my_iterator = MyIterator([1, 2, 3, 4, 5])
# 使用for循环迭代
for item in my_iterator:
    print(item)
# 结果:
# 1
# 2
# 3
# 4
# 5

# 迭代器只能迭代一次
for item in my_iterator:
    print(item)
# 结果:
# 无输出

# 如果需要重新迭代,需要重新创建一个可迭代的对象。
my_iterator2 = MyIterator([1, 2, 3, 4, 5]) 

# 或者使用copy模块的deepcopy方法
import copy
# 深拷贝
my_iterator3 = copy.deepcopy(my_iterator)
# 浅拷贝
my_iterator4 = copy.copy(my_iterator)

generator:生成器是一个函数,它使用yield关键字来返回一个值,每次调用生成器函数时,它会从上次返回的位置继续执行。

语法为:

python
def my_generator():
    yield 1
    yield 2
    yield 3

yield关键字:

  • yield关键字用于返回一个值,并且暂停函数的执行。
  • yield关键字可以在函数中多次使用。

生成器的优点:

  • 生成器可以节省内存,因为它只在需要时才生成值。不需要一次性生成所有值,而是按需生成。
  • 生成器可以实现无限序列,因为它可以无限生成值。
  • 生成器可以迭代多次,因为它会记住上次的位置。
  • 生成器可以返回多个值,因为它可以一次返回多个值。
python
# 自定义一个生成器
def gen(max):
    n = 0
    while n <= max :
        n += 1
        yield n

# 创建一个生成器
g = gen(3)
# 使用for循环迭代
for item in g:
    print(item)
# 结果:
# 1
# 2
# 3

# 列表推导式得到生成器:使用()代替[]
g2 = (i for i in range(1, 4))
# 使用for循环迭代
for item in g2:
    print(item)

None

None不等于0、False、空字符串、空列表、空字典、空元组、空集合、空函数、空类、空对象等。 None是一个特殊的常量,表示没有值。 None是一个对象,它的类型是NoneType。 None是一个单例对象,即只有一个实例。 None是一个不可变对象,即不能修改。

python
print(None) # None
print(type(None)) # <class 'NoneType'>
print(id(None)) # 140709434444160
print(id(None)) # 140709434444160
print(None == 0) # False

对象存在并不一定是True && __len__与__bool__内置方法

影响自定义对象的bool值的因素:

  • 类中是否定义了__bool__方法,如果定义了,则返回__bool__方法的返回值。
  • 类中是否定义了__len__方法,如果定义了,则返回__len__方法的返回值。
  • 类中是否定义了__nonzero__方法,如果定义了,则返回__nonzero__方法的返回值。
  • 否则,返回True。

默认情况下,__len__方法返回0,__nonzero__方法返回True。

python3中,__len__方法和__nonzero__方法被__bool__方法取代。

如果要自定义对象的bool值,需要定义__bool__方法。

python
class A:
    pass
a = A()
print(a) # <__main__.A object at 0x104e00070>
print(bool(a)) # True
print(a == 0) # False
print(a == None) # False
print(a == []) # False
print(a == ()) # False
print(a == {}) # False
print(a == '') # False
print(a == {}) # False
print(a == {}) # False
python
class A:
    def __bool__(self):
        print('__bool__')
        return False
    def __len__(self):
        print('__len__')
        return 0
a = A()
print(a) # <__main__.A object at 0x104e00070>
print(bool(a)) # False

装饰器的副作用

python
import time

# 定义一个装饰器
def decorator(func):
    def wrapper():
        start_time = time.time()
        print('start time:', start_time)
        func()
    return wrapper

# 定义函数并使用装饰器
@decorator
def func():
    '''
        这是func函数
    '''
    print(func.__name__)
# 调用函数
func()
# 结果:
# start time: 1682319803.034595
# wrapper
print(help(func))
# Help on function wrapper in module __main__:
# wrapper()
# None

# 如果不使用decorator,func.__name__结果为:func。
# 如果不使用decorator,help(func)结果为:help on function func in module __main__:
# func()
#     这是func函数
# None


# 可以看到,使用了decorator后func的name已经变成了wrapper,这是因为装饰器的副作用。
# help(func)的结果也变成了wrapper的帮助文档。
# 装饰器的副作用:装饰器会改变被装饰函数的一些属性,例如__name__、__doc__等。

要解决装饰器的副作用,我们可以使用functools.wraps装饰器,它会将被装饰函数的属性复制到wrapper函数中。

python
import time

# 也可以使用 from functools import wraps
import functools

# 定义一个装饰器
def decorator(func):
    @functools.wraps(func)
    def wrapper():
        start_time = time.time()
        print('start time:', start_time)
        func()
    return wrapper
# 定义函数并使用装饰器
@decorator
def func():
    '''
        这是func函数
    '''
    print(func.__name__)
# 调用函数
func()
# 结果:
# start time: 1682319803.034595
# func
print(help(func))
# Help on function func in module __main__:
# func()
#     这是func函数
# None

py3.8新增海象运算符

walrus operator:海象运算符,也叫海象表达式。 语法为:

python
if (a := expression) is not None:
    print(a)

海象运算符的作用是在表达式中同时进行赋值和判断。 例如:if (a := expression) is not None: 等价于:a = expression; if a is not None:

python
a = 'python'
if (b := len(a)) > 5:
    print('长度大于5,' + '真实长度为' + str(b))
# 结果:
# 长度大于5,真实长度为6

f关键字做字符串拼接

f关键字做字符串拼接,语法为:

python
f'字符串{表达式}'

f关键字做字符串拼接,会将表达式的值替换到字符串中。

python
name = 'python'
age = 18
print(f'name: {name}, age: {age}')
# 结果:    
# name: python, age: 18

py3.7新增数据类dataclass装饰器

dataclass装饰器:数据类装饰器,也叫数据类。 语法为:

python
@dataclass
class Person:
    name: str
    age: int

dataclass装饰器会自动生成__init____repr____eq__等方法。 例如:@dataclass 等价于:

python
class Person:
    def __init__(self, name: str, age: int):
        self.name = name
        self.age = age
    
    # __repr__方法的作用是返回一个字符串,用于表示对象。
    def __repr__(self):
        return f'Person(name={self.name}, age={self.age})'
    
    # __eq__方法的作用是判断两个对象是否相等。
    def __eq__(self, other):
        if isinstance(other, Person):
            return self.name == other.name and self.age == other.age
        return False
python
from dataclasses import dataclass

# 定义一个数据类
@dataclass
class Person:
    # 定义两个属性
    name: str
    age: int

p1 = Person('python', 18)
p2 = Person('python', 18)
print(p1)
# 结果:
# Person(name='python', age=18)
print(p1 == p2)
# 结果:
# True