浅谈Python多线程

我认为Python语言并不是真正意义上的编程语言级别多线程,Python的多线程是在操作系统级别上实现的,因为Python标准库中的threading模块使用了操作系统的原生线程,它可以创建多个线程(一个主线程+多个副线程)并行执行任务,但由于 Python 全局解释器锁(GIL)的存在,在多核 CPU 上它的多线程在任何时刻只有一个线程能够执行 Python 字节码。这就意味着 Python 多线程在 CPU 密集型任务上并不能实现性能的提升,但对于 I/O 密集型任务(如网络请求、文件操作等),多线程依然是有效的。

当然可以使用 multiprocessing 模块,该模块支持在多个进程之间并发执行任务,每个进程都有自己独立的内存空间,从而避免了全局解释器锁(GIL)的限制,并能够充分利用多核 CPU。
参考:
YouTube-码农高天:【python】听说Python的多线程是假的?它真的没有存在的价值么?

IO密集型任务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
words = list(range(6))

class Task(threading.Thread):
def __init_ (self, words):
self.words = words
self.total word = 0
super()._init_()

def run(self):
for word in self.words:
requests. get(f"https: //en.wikipedia.org/wiki/{words}")

t = Task(words)
t1 = Task(words[: len(words) // 2])
t2 = Task(words[len(words) // 2: ])

start = time.time()
# 启动一个新的线程
t.start()
# 用于线程间的协调,相当于主线程让权给新线程,等待执行完毕,主线程继续
t.join()

print(time.time() - start)

start = time.time()
t1.start()
t2.start()
# 此时主线程会等待两个线程执行完毕。主线程才会继续
t1.join()
t2.join()
print(time.time() - start)

'''
执行结果:
$ python example.py
1.0070123672485352
0.6659753322601318
$ python example.py
1.1642413139343262
0.6097736358642578
$ python example.py
1.044523000717163
0.6331624984741211
'''

以上代码其实更适合使用协程来完成,并且比操作系统级别的线程切换更轻便,没有竞争冒险问题(例:读者-写者问题、哲学家就餐问题、等),可见协程在网络传输的应用里更合适(例:爬虫)。

start() 方法

  • start() 方法是 Thread 类的一个方法,用于启动一个新的线程。
  • 当您调用一个线程对象的 start() 方法时,系统会为该线程分配资源并在新的线程上执行其 run() 方法中的代码。
  • 一旦调用了 start() 方法,线程将会进入“就绪”状态,表示它已准备好运行。系统会在合适的时间点自动调度线程并执行其 run() 方法。

join() 方法

  • join() 方法是 Thread 类提供的另一个方法,用于线程之间的协调。
  • 当一个线程调用另一个线程的 join() 方法时,它会等待目标线程执行完毕,然后再继续执行。
  • 例如通过调用 producer.join() 和 consumer.join(),主线程会等待producer线程和consumer线程都执行完毕才继续往下执行。

协程

协程就是单线程,相对于操作系统的多线程,有很多优势,但是协程之间的协作必须通过事件循环(Event Loop)来监控不同任务之间的放权,因每个不同的任务之间切换需要其他任务闲置CPU,例:IO、网络请求响应、sleep休眠

Python多线程用途

那么Python的多线程就没有意义了吗?并非如此,可以将协程和Python的多线程结合起来处理 大量计算密集任务 同时处理 低延迟的小任务
但是,如果单纯的使用协程处理以上的问题,就会出现执行大量计算密集型的时候导致无法放权给小的低延迟任务,这时就需要操作系统级别的线程来处理让权问题

例如以下代码中的fib()是一个计算密集型任务,此任务会造成计算过程无法放权导致其他低asyncio.sleep(0)延迟任务无法被执行,这会导致在处理大量密集型任务的时候没有同时处理sleep()操作,从而导致总处理时间相比同时执行两种任务的时间更长。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
async def long_task():
async def fib(n):
if n <= 1:
return 1
return await fib(n - 1) + \
await fib(n - 2)
while True:
await fib(25)
await asyncio.sleep(0)

async def short_task():
while True:
await asyncio.sleep(0.01)

async def main():
task_list = [short_task() for _ in range(4)]
task_list.append(long_task())
await asyncio.gather(*task_list)

asyncio.run(main())

如果加入多线程,将计算密集型任务扔给一个操作系统级别的线程,此时的情况就会得到改善,虽然依旧同一时刻只能处理一个线程,但操作系统级别的多线程会放权给另一个处理权给小的低延迟任务线程,从而实现执行大量密集型任务的同时也在处理小的低延迟任务(IO、请求响应等…)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def long_task():
while True: :
Fib(25)

async def short_task():
while True:
await asyncio.sleep(@.01) :

async def main():
task_list = [short_task() for _ in range(4)] :
t = threading. Thread(target=long_task)
t.start()
await asyncio.gather(*task_list)

asyncio. run(main()) |

基础语法

  • 代码快

    • 代码块不用{}来约束,同一个代码块的语句必须包含相同的缩进空格数,同一代码快缩进不一致,会导致运行错误

    • 通常是一行写完一条语句,如果语句很长,我们可以使用\ 来实现多行语句,[], {}, 或 () 中的多行语句,不需要使用反斜杠 \

  • 四种类型

    • int (整数),只有一种整数类型 int,表示为长整型,去除 python2 中的 Long
    • bool (布尔), 如 True=1,false=0,继承于整形
    • float (浮点数)
    • complex (复数)
  • 字符串

    • Python 中单引号 ‘ 和双引号 “ 使用完全相同
    • 使用三引号(‘’’“””)可以指定一个多行字符串
    • 使用 r (raw string)可以让反斜杠不发生转义。 如 r”this is a line with \n”\n 会显示,并不是换行
    • + 运算符连接在一起,用 ***** 运算符重复
    • 两种索引方式,从左往右以 0 开始,从右往左以 -1 开始
    • 字符串不能改变,如
    • 没有单独的字符类型,一个字符就是长度为 1 的字符串
    • 截取语法:变量[头下标:尾下标:步长]

函数之间或类的方法之间用空行分隔,表示一段新的代码的开始

类和函数入口之间也用一行空行分隔,以突出函数入口的开始

空行的作用在于分隔两段不同功能或含义的代码,便于日后代码的维护或重构

Python 可以在同一行中使用多条语句,语句之间使用分号 ; 分割

缩进相同的一组语句构成一个代码块,我们称之代码组

print 默认输出是换行的,如果要实现不换行需要在变量末尾加上 end=”” print( x, end=" " )

  • import 与 from…import
    • 整个模块(somemodule)导入,格式为: import somemodule
    • 从某个模块中导入某个函数,格式为: from somemodule import somefunction
    • 从某个模块中导入多个函数,格式为: from somemodule import firstfunc, secondfunc, thirdfunc
    • 将某个模块中的全部函数导入,格式为: from somemodule import *

基本数据类型

  • Python 中的变量不需要声明,每个变量在使用前都必须赋值,变量赋值以后该变量才会被创建

  • Python允许你同时为多个变量赋值

    1
    a = b = c = 1
  • 也可以为多个对象指定多个变量

    1
    a, b, c = 1, 2, "runoob"
  • 六个基本的数据类型

    • Number(数字)
    • String(字符串)
    • List(列表)
    • Tuple(元组)
    • Set(集合)
    • Dictionary(字典)
  • 可变数据类型

    • List(列表)
    • Dictionary(字典)
    • Set(集合)
  • 不可变数据类型

    • Number(数字)
    • String(字符串)
    • Tuple(元组)

执行脚本方式

以下运行环境均是在linux环境下执行

普通执行方式

1
python hello.py        #安装Python解释器即可运行

Linux环境下的执行方式

直接添加到hello.py的第一行:#!/usr/bin/python

直接添加到hello.py的第一行:#!/usr/bin/env python

以上两种方式区别:第一种是直接知道在/usr/bin/目录下有Python解释器,第二种是配置了Python解释器的环境变量(Python解释器可以在其他目录),推荐使用第二种。

添加第一行代码完成后:

1
2
chmod +x hello.py       #将Python执行脚本对用户、用户组、其他用户添加执行权限
./hello.py #Linux环境下直接执行Python脚本

字符串

使用方法修改字符串的大小写

打印输出的英文所有单词首字母大写

1
2
name = "ada lovelace"
print(name.title())

要将字符串改为全部大写或全部小写

1
2
3
name = "Ada Lovelace"
print(name.upper())
print(name.lower())

合并(拼接)字符串

使用+号拼接

1
2
3
4
5
6
first_name = "ada"
last_name = "lovelace"
full_name = first_name + " " + last_name
message = "Hello, " + full_name.title() + "!"
print(message)

学习python字符串这节经常使用\t制表符\n换行:

1
print("Languages:\nPython\nC\nJavaScript")

运行:

Languages:
        Python
        C
        JavaScript

去除空白

以下方式只能暂时的去除空白,如果要使变量fu永久改变则要重新赋值fu变量

1
2
3
4
5
6
7
8
>>> fu="       fu immortal   "
>>> fu.lstrip() #去除前面(左)的空白
'fu immortal '
>>> fu.rstrip() #去除后面(右)的空白
' fu immortal'
>>> fu.strip() #左右空白都去除
'fu immortal'

数字

乘方运算

1
2
3
4
5
6
7
>>> 3 ** 2
9
>>> 3 ** 3
27
>>> 10 ** 6
1000000

函数 str()

将非字符串类型的变量转换为字符串:

1
2
3
4
age = 23
message = "Happy " + str(age) + "rd Birthday!"
print(message) #如果不转换则会报错,由于+是用来连接两个字符串的

python3中的除法不同于python2

1
2
3
4
5
>>> 3/2     #如果是Python2值会是1,要用以下方式解决
1.5
>>> 3/2.0
1.5

注释

1
2
3
4
5
6
7
8
9
10
#!/usr/bin/env python

'''多行
注释'''

# print("Hell") #单行注释

"""多行
注释"""

列表

列表由一系列按特定顺序排列的元素组成。可以创建包含字母表中所有字母、数字0~9或所有家庭成员姓名的列表;也可以将任何东西加入列表中,其中的元素之间可以没有任何关系。
列表的索引从0开始,各元素索引依次往后+1

列表简单操作

打印列表:

1
2
bicycles = ['trek', 'cannondale', 'redline', 'specialized']
print(bicycles) #输出:['trek', 'cannondale', 'redline', 'specialized']

访问列表元素:

1
2
3
bicycles = ['trek', 'cannondale', 'redline', 'specialized']
print(bicycles[0]) #输出:trek

1
2
3
bicycles = ['trek', 'cannondale', 'redline', 'specialized']
print(bicycles[-1]) #输出:specialized(最后一个元素),,索引-2返回倒数第二个列表元素,索引 -3返回倒数第三个列表元素,以此类推

修改添加删除元素

修改元素:

1
2
3
4
motorcycles = ['honda', 'yamaha', 'suzuki']
print(motorcycles) #输出:['honda', 'yamaha', 'suzuki']
motorcycles[0] = 'ducati'
print(motorcycles) #输出:['ducati', 'yamaha', 'suzuki']

添加元素:

1
2
3
4
5
6
7
8
9
10
11
motorcycles = ['honda', 'yamaha', 'suzuki']
print(motorcycles) #输出:['honda', 'yamaha', 'suzuki']
motorcycles.append('ducati') #往列表的最后添加元素ducati
print(motorcycles) #输出:['honda', 'yamaha', 'suzuki', 'ducati']

motorcycles = []
motorcycles.append('honda')
motorcycles.append('yamaha')
motorcycles.append('suzuki')
print(motorcycles) #输出:['honda', 'yamaha', 'suzuki']

在列表中插入元素:

1
2
3
4
5
>>> motorcycles = ['honda', 'yamaha', 'suzuki']
>>> motorcycles.insert(1, 'ducati')
>>> print(motorcycles)
['honda', 'ducati', 'yamaha', 'suzuki']
#元素的插入可以是任何位置,但插入元素之后的位置全部向右移且索引全部+1

删除元素:

1
2
3
4
5
# 方式1(del删)
motorcycles = ['honda', 'yamaha', 'suzuki']
print(motorcycles) #输出原样
del motorcycles[1] #永久删除,无法访问原来的元素
print(motorcycles) #输出:['honda', 'suzuki']
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 方式2(pop删)
motorcycles = ['honda', 'yamaha', 'suzuki']
print(motorcycles) #输出原样
popped_motorcycle = motorcycles.pop() #取出最后的元素赋值给变量popped_motorcycle
print(popped_motorcycle) #打印输出最后的元素suzuki
print(motorcycles) #输出:['honda', 'yamaha']


>>> print(motorcycles)
['honda', 'ducati', 'yamaha', 'suzuki']
>>> motorcycles.pop(1) #可以指定任何位置的元素,取出后删除
'ducati'
>>> print(motorcycles)
['honda', 'yamaha', 'suzuki']

1
2
3
4
5
6
# 方式3(remove)
>>> print(motorcycles)
['honda', 'yamaha', 'suzuki']
>>> motorcycles.remove('yamaha') #指定元素方式删除而非索引方式
>>> print(motorcycles)
['honda', 'suzuki']

组织列表

永久排序列表sort():

1
2
3
4
5
cars = ['bmw', 'audi', 'toyota', 'subaru']
cars.sort()
print(cars) #输出:['audi', 'bmw', 'subaru', 'toyota']顺序排序
cars.sort(reverse=True) #首字母倒序排序
print(cars) #输出:['toyota', 'subaru', 'bmw', 'audi']

临时排序列表:

1
2
3
4
5
6
7
8
9
10
11
#Python 3.9.3中报错:

>>> cars.sorted()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'list' object has no attribute 'sorted'

cars = ['bmw', 'audi', 'toyota', 'subaru']
print(cars) #打印原列表
print(sorted(cars)) #打印临时按字母排序的列表['audi', 'bmw', 'subaru', 'toyota'];也可向函数sorted()传递参数 reverse=True
print(cars) #原列表未被更改['bmw', 'audi', 'toyota', 'subaru']

倒着打印列表reverse():

1
2
3
4
cars = ['bmw', 'audi', 'toyota', 'subaru']

cars.reverse() #方法reverse() 永久性地修改列表元素的排列顺序,但可随时恢复到原来的排列顺序,为此只需对列表再次调用reverse()即可
print(cars) #输出['subaru', 'toyota', 'audi', 'bmw']

确定列表的长度:

1
2
3
>>> cars = ['bmw', 'audi', 'toyota', 'subaru']
>>> len(cars)
4

Python3 开发常用知识点

参考:

菜鸟教程:Python3

数据类型

Python3 中数据类型 4种

  • int (整数), 如 1, 只有一种整数类型 int,表示为长整型,没有 python2 中的 Long
  • bool (布尔), 如 True,父为整int
  • float (浮点数), 如 1.23、3E-2
  • complex (复数), 如 1 + 2j、 1.1 + 2.2j

字符串

  • Python 中单引号 和双引号 使用完全相同
  • 反斜杠可以用来转义,使用 r 可以让反斜杠不发生转义
  • 字符串不能改变
  • 单独的字符类型,一个字符就是长度为 1 的字符串

字符串截取:截头不截尾

空行:不插入空行,Python 解释器运行不会出错。空行的作用在于分隔两段不同功能或含义的代码,便于代码的维护或重构。

不换行输出:print( x, end=” “ ),其实就是将换行替换为” “(空格),也可以替换为”,”:print(b, end=',')

import 引入

import 语法会首先把 item 当作一个包定义的名称,如果没找到,再试图按照一个模块去导入。如果还没找到,抛出一个 :exc:ImportError 异常

  • 整个模块导入 import somemodule,必须使用全名(路径+函数名)去访问
  • 模块中导入某个函数from somemodule import somefunction
  • 模块导入多个函数:from somemodule import firstfunc, secondfunc, thirdfunc
  • 导入全部函数from somemodule import *,在 Windows 平台上工作的就不是非常好(Windows 是一个不区分大小写的系统),需包定义文件 init.py 存在一个叫做 all 的列表变量,把这个列表中的所有名字作为包内容导入即可解决Windows不区分大小写问题

基本数据类型

  • 对象有不同类型的区分,变量是没有类型的
  • 变量不需要声明(只是指针)。每个变量在使用前都必须赋值,变量赋值以后该变量才会被创建

标准数据类型

  • Number(数字),包含4种数据类型(int bool float complex)

  • String(字符串)

  • List(列表)

  • Tuple(元组)

  • Set(集合)

  • Dictionary(字典)

  1. 不可变数据(3 个):Number(数字)改变数字数据类型的值,将重新分配内存空间、String(字符串)Tuple(元组)

    **变量赋值 a=5 后再赋值 a=10,这里实际是新生成一个 int 值对象 10,再让 a 指向它,而 5 被丢弃,不是改变 a 的值,相当于新生成了 a

  2. 可变数据(3 个):List(列表)、Dictionary(字典)、Set(集合)

    变量赋值: la=[1,2,3,4] 后再赋值 la[2]=5 则是将 list la 的第三个元素值更改,本身la没有动,只是其内部的一部分值被修改了

tuple 元组

string、list 和 tuple 都属于 sequence(序列)

  • 1、与字符串一样,元组的元素不能修改
  • 2、元组也可以被索引和切片,方法一样。
  • 3、注意构造包含 0 或 1 个元素的元组的特殊语法规则。
  • 4、元组也可以使用+操作符进行拼接。

Set 集合

集合无序不重复

  • 基本功能是进行成员关系测试删除重复元素

  • 由一个或数个形态各异的大小整体组成的,构成集合的事物或对象称作元素或是成员

  • { } 或者 set() 函数创建集合,空集合必用set()

  • set可以进行集合运算,-差集、|并集、&交集、^左右集合不同时存在的元素

创建一个空集合必须用 set() 而不是 **{ }**,因为 { } 是用来创建一个空字典

常用方法

  • 添加元素s.add( x )s.update( x )(参数可以是列表,元组,字典等)
  • 移除元素s.remove( x )(为空报错),s.discard( x )为空不报错
  • 随机删除并取出s.pop()

dictionary 字典

列表有序,字典无序(3.5及之前版本)

  • 字典用 { } 标识,它是一个无序的 键(key) : 值(value) 的集合
  • 键(key)必须使用不可变类型,键(key)唯一

Python推导式

从一个数据序列构建另一个新的数据序列的结构体

支持4种推导式:

  • 列表(list)推导式
  • 字典(dict)推导式
  • 集合(set)推导式
  • 元组(tuple)推导式

推导式格式:

  • [表达式 for 变量 in 列表 if 条件],列表
  • { key_expr: value_expr for value in collection if condition },字典
  • { expression for item in Sequence if conditional },集合
  • (expression for item in Sequence if conditional ),元组

解释器

Linux/Unix系统中,在脚本顶部添加以下命令让Python脚本可以像SHELL脚本一样可直接执行:

1
#! /usr/bin/env python3

需修改执行权限

运算符

Python3.8 新增运算符

海象运算符,它的作用是在表达式中赋值:

1
2
if (n := len(a)) > 10:
print(f"List is too long ({n} elements, expected <= 10)")

位运算

运算符 描述 实例
& 按位与运算符:参与运算的两个值,如果两个相应位都为1,则该位的结果为1,否则为0 (a & b) 输出结果 12 ,二进制解释: 0000 1100
| 按位或运算符:只要对应的二个二进位有一个为1时,结果位就为1。 (a | b) 输出结果 61 ,二进制解释: 0011 1101
^ 按位异或运算符:当两对应的二进位相异时,结果为1 (a ^ b) 输出结果 49 ,二进制解释: 0011 0001
~ 按位取反运算符:对数据的每个二进制位取反,即把1变为0,把0变为1。**~x** 类似于 -x-1 (~a ) 输出结果 -61 ,二进制解释: 1100 0011, 在一个有符号二进制数的补码形式。
<< 左移动运算符:运算数的各二进位全部左移若干位,由”<<”右边的数指定移动的位数,高位丢弃,低位补0。 a << 2 输出结果 240 ,二进制解释: 1111 0000
>> 右移动运算符:把”>>”左边的运算数的各二进位全部右移若干位,”>>”右边的数指定移动的位数 a >> 2 输出结果 15 ,二进制解释: 0000 1111

身份运算

运算符 描述 实例
is is 是判断两个标识符是不是引用自一个对象 x is y, 相对 id(x) == id(y) , 如果引用的是同一个对象则返回 True,否则返回 False
is not is not 是判断两个标识符是不是引用自不同对象 x is not y , 相对 **id(x) != id(y)**。如果引用的不是同一个对象则返回结果 True,否则返回 False。

is 与 == 区别

is 用于判断两个变量引用对象是否为同一个, == 用于判断引用变量的值是否相等。

逻辑运算符

运算符 描述
and 逻辑与,如果两个操作数都为 True,则返回 True。
or 逻辑或,如果两个操作数中至少有一个为 True,则返回 True。
not 逻辑非,用于对操作数进行取反操作。

条件控制

match…case

注意 Python 3.10新加的,相当于Switch case语句

  • case 也可以设置多个匹配条件,条件使用 | 隔开
  • case _: 类似于 C 和 Java 中的 default:

迭代器

  • 迭代是Python最强大的功能之一,是访问集合元素的一种方式
  • 迭代器是一个可以记住遍历的位置的对象
  • 迭代器从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能单向向前
  • 两个基本的方法:iter()next()
  • 字符串、列表、元组对象(即:sequence 序列),都可用于创建迭代器

函数

参数

说明:自定义函数printme()

  • 必须参数,def printme( str ):
  • 关键字参数,printme( str = "绿盟")
  • 默认值参数:def printme( name, age = 35 )
  • 不定长参数:def printinfo( arg1, *args )
    • *args 的参数会以元组形式导入
    • ** 的参数会以字典的形式导入
    • def f(a,b,*,c)形式必须关键字传入,即:f(1,2,c=3)

lambda表达式(匿名函数)

语法:

1
lambda [arg1 [,arg2,.....argn]]:expression
  • lambda 函数拥有自己的命名空间,且不能访问自己参数列表之外或全局命名空间里的参数
  • lambda 的主体是一个表达式,而不是一个代码块。仅仅能在 lambda 表达式中封装有限的逻辑进去

Python3 模块

用 python 解释器来编程,如果你从 Python 解释器退出再进入,那么你定义的所有的方法和变量就都消失了,为此 Python 提供了一个办法,把这些定义存放在文件中,为一些脚本或者交互式的解释器实例使用,这个文件被称为模块

  • 模块是一个包含所有你定义的函数变量的文件,其后缀名是.py。模块可以被别的程序引入,这也是使用 python 标准库的方法
  • 一个模块只会被导入一次,不管你执行了多少次 import
  • 模块除了方法定义,还可以包括可执行的代码。这些代码一般用来初始化这个模块。这些代码只有在第一次被导入时才会被执行
  • 每个模块有各自独立的符号表(可理解为变量函数名称),在模块内部为所有的函数当作全局符号表来使用
  • 模块是可以导入其他模块的
  • 每个模块都有一个__name__属性,当其值是’__main__‘时,表明该模块自身在运行,否则是被引入
  • dir() 函数可以找到模块内定义的所有名称,以一个字符串列表的形式返回,未给定参数将返回自身所有名称

标准模块:有些模块直接被构建在解析器里,这些虽然不是一些语言内置的功能,但是他却能很高效的使用,甚至是系统级调用也没问题。这些组件会根据不同的操作系统进行不同形式的配置,比如 winreg 这个模块就只会提供给 Windows 系统,意思就是解析器会根据操作系统来生效不同的模块。再比如sys.ps1=’>>> ‘、sys.ps2=’… ‘,可被修改自定义

搜索路径

使用 import 语句的时候,这就涉及到 Python 的搜索路径,搜索路径是由一系列目录名组成的,Python 解释器就依次从这些目录中去寻找所引入的模块,搜索路径类似于环境变量,可用环境变量来定义,搜索路径是在 Python 编译或安装的时候确定的,安装新的库应该也会修改,sys.path即可打印搜索路径

  • 如果在当前目录下存在与要引入模块同名的文件,就会把要引入的模块屏蔽掉

包是一种管理 Python 模块命名空间的形式,比如一个模块的名称是 A.B, 那么他表示一个包 A中的子模块 B 。采用点模块名称这种形式也不用担心不同库之间的模块重名的情况。

  • 导入一个包的时候,Python 会根据 sys.path 中的目录来寻找这个包中包含的子目录
  • 目录只有包含一个叫做 __init__.py 的文件才会被认作是一个包,主要是为了避免一些滥俗的名字(比如叫做 string)不小心的影响搜索路径中的有效模块,最简单的情况,放一个空的 :file:init.py就可以了。当然这个文件中也可以包含一些初始化代码或者为__all__变量(提供一个精确包的索引)赋值,为Windows提供稳定。

异常

try---except---else---finally

前后两段都会执行,中间类似 if(异常执行)---else(无异常执行)

raise,抛出异常

with as

with语句创建了一个上下文管理器,确保了即使在代码块中发生异常,也会执行必要的清理操作,比如关闭文件.当with代码执行完毕后,无论正常/异常,它都会调用文件对象的_exit_方法。

  • 等效try…finally,提高易用性
  • 处理文件对象时使用 with 关键字是一种很好的做法

实例:

1
2
3
4
5
6
7
8
9
10
with open('./test_runoob.txt', 'w') as my_file:
my_file.write('hello world!')

# 等效于

file = open('./test_runoob.txt', 'w')
try:
file.write('hello world')
finally:
file.close()

上下文管理器

1
2
3
4
5
6
7
8
9
10
11
12
13
class MyContextManager:
def __enter__(self):
print('Entering the context.')
return self

def __exit__(self, exc_type, exc_value, traceback):
print('Exiting the context.')
if exc_type:
print(f'An exception occurred: {exc_value}')
return False # 可以修改为True来阻止异常传播

with MyContextManager() as manager:
print('Inside the with statement.')

Python3 面向对象

参考:菜鸟教程:Python3 面向对象

  • 类:创建对象的蓝图
  • 实例:再次被创建的类
  • 实例化:将类再加载一份进内存的过程
  • 对象:Python一切皆对象,包括类、实例、模块…
  • 构造方法:__init__()在类实例化时会自动调用

类方法必须包含参数 self, 且为第一个参数,self 代表的是类的实例,但self 的名字并不是规定死的,也可以使用 this,但是最好还是按照约定使用 self

Python面向对象编程

子类(超类 派生类 DerivedClassName)父类(基类 BaseClassName)

  • 多继承
  • 子类可覆盖父类的所有方法
  • 可回调父类的同名方法(被子类覆盖的方法)

方法重写:子类重写父类方法,子类调用重写,也可以调用被重写方法

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/usr/bin/python3

class Parent: # 定义父类
def myMethod(self):
print ('调用父类方法')

class Child(Parent): # 定义子类
def myMethod(self):
print ('调用子类方法')

c = Child() # 子类实例
c.myMethod() # 子类调用重写方法
super(Child,c).myMethod() #用子类对象调用父类已被覆盖的方法
  • c.myMethod() # 子类调用重写方法

  • super(Child,c).myMethod() #用子类对象调用父类已被覆盖的方法

  • Python3.x 和 Python2.x 的一个区别是: Python 3 可以使用直接使用 super().xxx 代替 super(Class, self).xxx :

    Python 2 中的示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    pythonCopy code# 定义一个父类
    class Parent(object):
    def __init__(self, name):
    self.name = name

    def say_hello(self):
    print("Hello, I am", self.name)

    # 定义一个子类,继承自父类
    class Child(Parent):
    def __init__(self, name, age):
    # Python 2 中使用 super() 调用父类方法
    super(Child, self).__init__(name)
    self.age = age

    def say_hello(self):
    # Python 2 中也可以使用 super() 来调用父类的同名方法
    super(Child, self).say_hello()
    print("I am", self.age, "years old")

    # 创建子类对象并调用方法
    child = Child("Alice", 5)
    child.say_hello()

    Python 3 中的示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    pythonCopy code# 定义一个父类
    class Parent:
    def __init__(self, name):
    self.name = name

    def say_hello(self):
    print("Hello, I am", self.name)

    # 定义一个子类,继承自父类
    class Child(Parent):
    def __init__(self, name, age):
    # Python 3 中不需要传递参数给 super()
    super().__init__(name)
    self.age = age

    def say_hello(self):
    # Python 3 中也可以使用 super() 来调用父类的同名方法
    super().say_hello()
    print("I am", self.age, "years old")

    # 创建子类对象并调用方法
    child = Child("Alice", 5)
    child.say_hello()

多继承示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#!/usr/bin/python3

#类定义
class people:
#定义基本属性
name = ''
age = 0
#定义私有属性,私有属性在类外部无法直接进行访问
__weight = 0
#定义构造方法
def __init__(self,n,a,w):
self.name = n
self.age = a
self.__weight = w
def speak(self):
print("%s 说: 我 %d 岁。" %(self.name,self.age))
#单继承示例
class student(people):
grade = ''
def __init__(self,n,a,w,g):
#调用父类的构函
people.__init__(self,n,a,w)
self.grade = g
#覆写父类的方法
def speak(self):
print("%s 说: 我 %d 岁了,我在读 %d 年级"%(self.name,self.age,self.grade))
#另一个类,多重继承之前的准备
class speaker():
topic = ''
name = ''
def __init__(self,n,t):
self.name = n
self.topic = t
def speak(self):
print("我叫 %s,我是一个演说家,我演讲的主题是 %s"%(self.name,self.topic))
#多重继承
class sample(speaker,student):
a =''
def __init__(self,n,a,w,g,t):
student.__init__(self,n,a,w,g)
speaker.__init__(self,n,t)
test = sample("Tim",25,80,4,"Python")
test.speak() #方法名同,默认调用的是在括号中参数位置排前父类的方法

输出:

1
我叫 Tim,我是一个演说家,我演讲的主题是 Python

积累

静态网页服务器

使用Python内置的HTTP服务器,来快速搭建一个静态网站

转码

参考:

维基百科:ASCII

CSDN-墨痕诉清风:Python3bytes、hex、字符串之间相互转换

博客园-时光不改:Python3 字符串与hex之间的相互转换

hashlib官方文档

知乎-Python 学习者:python字符串前加r、f、u、l 的区别

实验

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Python 3.7.15 (default, Oct 11 2022, 13:45:04) 
[Clang 14.0.0 (clang-1400.0.29.102)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> bs=b'\x01#Eg\x89\xab\xcd\xef\x01#Eg\x89\xab\xcd\xef'
>>> bs_hex=bs.hex()
>>> print(bs_hex)
0123456789abcdef0123456789abcdef
>>> b=b'#Eg'
>>> b.hex()
'234567'
>>> bb=b'#'
>>> bb.hex()
'23'
>>> bbb=b'E'
>>> bbb.hex()
'45'
>>>
  • \xyy,yy代表十六进制数
  • 查ASCII,正好对应以上,#:23、E:45、g:56

前加字母含义

  • b 代表二进制形式
  • l 代表宽字符,unicode字符,每个字符占用两个字节
  • u/U 代表是对字符串进行unicode编码,特别是中文,容易被存储的时候混淆成其他格式
  • r 防止被转义,比如\n\t
  • f/F替换值,比如,name="铸鼎" age=18 f"I'm {name}. I'm {age} years old",输出:I’m 铸鼎. I’m 18 years old

Python 内置函数

菜鸟教程:Python3 内置函数

  • locals(),函数会以字典类型返回当前位置的全部局部变量

    1
    2
    3
    4
    5
    6
    7
    >>>def runoob(arg):    # 两个局部变量:arg、z
    ... z = 1
    ... print (locals())
    ...
    >>> runoob(4)
    {'z': 1, 'arg': 4} # 返回一个名字/值对的字典
    >>>

Python 装饰器(Decorators)

Python3.0之后加入新特性Decorators,以@为标记修饰function和class。有点类似c++的宏和java的注解。Decorators用以修饰约束function和class,分为带参数和不带参数。

参考:

CSDN-Shower稻草人:Python中的注解“@”

博客园-神毓·逍遥:python3进阶之*args与**kwargs用法

Python Enhancement Proposals:Decorators for Functions and Methods

菜鸟教程:Python 函数装饰器

我的第一个装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 创建装饰器
def a_new_decorator(a_func):
def wrapTheFunction():
print("I am doing some boring work before executing a_func()")
a_func()
print("I am doing some boring work after executing a_func()")
return wrapTheFunction

# 定义一个需要被装饰器装饰的函数
def a_function_requiring_decoration():
print("I am the function which needs some decoration to remove my foul smell")

# 运行
a_function_requiring_decoration()
#outputs: "I am the function which needs some decoration to remove my foul smell"

# 说明装饰器执行原理:
# 1. 将 需要被装饰器装饰的函数 的名称(指针)放进装饰器
# 2. 装饰器接收到它命名a_func,放置到wrapTheFunction代码中a_func()(不执行,可理解为a_func()代码放置)
# 3. 返回wrapTheFunction,即a_function_requiring_decoration
a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration)
#now a_function_requiring_decoration is wrapped by wrapTheFunction()

# 等于说执行装饰器内部函数wrapTheFunction
a_function_requiring_decoration()
#outputs:I am doing some boring work before executing a_func()
# I am the function which needs some decoration to remove my foul smell
# I am doing some boring work after executing a_func()

不带参数的单一使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def spamrun(fn):
def sayspam(*args):
print("spam,spam,spam")
fn(*args)
return sayspam
@spamrun
def useful(a,b):
print(a*b)

if __name__ == "__main__"
useful(2,5)

# 运行结果
spam,spam,spam
10

理解为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def spamrun(fn):
def sayspam(*args):
print("spam,spam,spam")
fn(*args)
return sayspam

def useful(a,b):
print(a*b)

if __name__ == "__main__"
# 将useful函数名称(指针)传入修饰器spamrun,修饰器中sayspam(*args)可接收任意多个无名参数。等于说fn(*args)将useful改写成可传任意无名参数的useful(*args),改写后的代码放置到修饰器fn(*args)位置,返回sayspam,即useful=sayspam
useful = spamrun(useful)
# 运行userful(a,b)等于说运行填入useful(a,b)代码的sayspam
useful(a,b)

不带参数的多次使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def spamrun(fn):
def sayspam(*args):
print("spam,spam,spam")
fn(*args)
return sayspam


def spamrun1(fn):
def sayspam1(*args):
print("spam1,spam1,spam1")
fn(*args)
return sayspam1

@spamrun
@spamrun1
def useful(a,b):
print(a*b)

if __name__ == "__main__":
useful(2,5)

# 运行结果
spam,spam,spam
spam1,spam1,spam1
10

理解为

1
2
3
if __name__ == "__main__":
useful = spamrun(spamrun1(useful))
useful(a,b)

带参数的单次使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# **kwds接收传进来的 versionadded="2.2" author="Guido van Rossum"
# 我觉得还传进来了mymethod,decorate才能收到f=mymethod
def attrs(**kwds):
def decorate(f):
# 使用Python 内置函数为mymethod赋值versionadded="2.2" author="Guido van Rossum"
for k in kwds:
setattr(f, k, kwds[k])
# print(f)
# 返回修饰过(加代码)的mymethod
return f

return decorate


@attrs(versionadded="2.2",
author="Guido van Rossum")
def mymethod(f):
print(getattr(mymethod,'versionadded',0))
print(getattr(mymethod,'author',0))
print(f)

if __name__ == "__main__"
mymethod(2)

# 运行结果:
2.2
Guido van Rossum
2

理解为

1
2
3
if __name__ == "__main__":	
mymethod = attrs(versionadded="2.2",author="Guido van Rossum).(mymethod)
mymethod(2)

内部类 函数

内部类在Python中常用于提供附加的元数据或配置选项,它们通常嵌套在其他类或函数中。在上述代码中,Meta是一个内部类,用于提供模型的元数据配置。

以下是Django项目中的总结。

  1. 指定数据库表名:通过设置db_table属性,可以指定模型在数据库中对应的表名。这对于自定义表名或与现有数据库表进行映射非常有用。
  2. 设置后台管理界面显示名称:通过设置verbose_name属性,可以指定模型在后台管理界面中的显示名称。这对于使后台管理界面更加友好和易于理解非常有用。
  3. 设置复数显示名称:通过设置verbose_name_plural属性,可以指定模型的复数显示名称。通常,它与verbose_name属性的值相同。它在后台管理界面中的一些地方使用,如显示模型的复数名称的列表标题。
  4. 添加额外的配置选项:内部类还可以包含其他配置选项,用于控制模型的行为。例如,可以添加ordering属性来指定模型查询结果的默认排序方式。

函数

装饰器代码中有内部类,关于装饰器,查看本文装饰器部分

在Python中,可以在一个函数的内部定义另一个函数,这被称为内部函数(inner function)或嵌套函数(nested function)。以下是一个示例:

1
2
3
4
5
6
7
8
9
pythonCopy codedef outer_function():
def inner_function():
print("This is the inner function.")

print("This is the outer function.")
inner_function()

# 调用外部函数
outer_function()

在上面的例子中,outer_function是外部函数,inner_function是内部函数。内部函数被定义在外部函数的代码块中,并且只能在外部函数内部访问。

当外部函数被调用时,它会打印出 “This is the outer function.” 的消息,并立即调用内部函数 inner_function,这将打印出 “This is the inner function.” 的消息。

通过在函数内部定义另一个函数,可以将内部函数用作外部函数的辅助功能,或者根据需要在特定范围内封装一些逻辑。

需要注意的是,内部函数的作用域被限制在外部函数的范围内。这意味着内部函数只能在外部函数内部被调用,而无法在外部函数之外的地方直接调用。