Python基础知识

Python里有多线程吗?

Python里的多线程是假的多线程。

Python解释器由于设计时有GIL全局锁,导致了多线程无法利用多核,只有一个线程在解释器中运行。

对于I/O密集型任务,Python的多线程能起到作用,但对于CPU密集型任务,Python的多线程几乎占不到任何优势,还有可能因为争夺资源而变慢。

对所有面向I/O的(会调用内建的操作系统C代码的)程序来说,GIL会在这个I/O调用之前被释放,以允许其它的线程在这个线程等待I/O的时候运行。

如果是纯计算的程序,没有 I/O 操作,解释器会每隔 100 次操作就释放这把锁,让别的线程有机会执行(这个次数可以通过 sys.setcheckinterval 来调整)如果某线程并未使用很多I/O 操作,它会在自己的时间片内一直占用处理器和GIL。

缓解GIL锁的方法:多进程和协程(协程也只是单CPU,但是能减小切换代价提升性能)

GIL (全局解释器锁 - Global Interpreter Lock):

  • 是什么: CPython 解释器中的一个互斥锁 (mutex)。它的作用是保护对 Python 对象的访问,确保在任何给定时刻,只有一个线程能持有 GIL 并执行 Python 字节码
  • 关键: 它锁的是解释器执行字节码这个动作,而不是整个线程。

Python中列表和元组的区别?

  1. 列表是可变的,在创建之后可以对其进行任意的修改。
  2. 元组是不可变的,元组一旦创建,便不能对其进行更改,可以元组当作一个只读版本的列表。
  3. 元组无法复制。
  4. Python将低开销的较大的块分配给元组,因为它们是不可变的。对于列表则分配小内存块。与列表相比,元组的内存更小。当你拥有大量元素时,元组比列表快。

常用的深度学习框架有哪些,都是哪家公司开发的?

  1. PyTorch:Facebook 研究首选,灵活易用,调试方便,社区活跃。(Caffe:UC Berkeley)
  2. TensorFlow:Google 生产部署强大,生态完善,工具链丰富,跨平台能力强。(Keras:Google )
  3. MxNet:Amazon
  4. PaddlePaddle:百度

PyTorch动态图和TensorFlow静态图的区别?

PyTorch动态图:计算图的运算与搭建同时进行;其较灵活,易调节。

TensorFlow静态图:计算图先搭建图,后运算;其较高效,不灵活。

img

PyTorch 的计算图是随着你的 Python 代码一行一行执行时才被建立起来的(动态构建),所以计算流程和你的 Python 代码逻辑是紧密同步的。

这带来了巨大的调试优势

  1. 错误定位准确: 如果计算中出现错误(比如维度不匹配、数值错误 NaN),错误发生的地方通常就是你 Python 代码中执行相应操作的那一行。Python 的错误回溯 (Traceback) 会直接指向你的源代码。
  2. 可使用标准 Python 工具: 你可以在任何一行 PyTorch 代码前设置一个断点(比如用 import pdb; pdb.set_trace() 或者 IDE 的断点功能)。当程序执行到断点时,它会暂停。
  3. 检查中间值: 在断点处,你可以像检查普通 Python 变量一样,直接查看任何 PyTorch 张量(Tensor)的当前值、形状、数据类型等。这对于理解计算过程中数据的变化、找出逻辑错误非常有帮助。
  4. 逐行执行: 你可以使用调试器逐行执行 PyTorch 代码,观察每一步操作后张量的变化,以及计算图是如何被构建的(虽然你看不到图本身,但能看到执行效果)。

对比静态图的调试困难:

在静态图框架中,你先定义整个图,然后启动执行。如果执行过程中出错,错误信息可能指向图内部的一个底层操作,很难直接关联回你定义图的那部分 Python 代码。你也不能轻易地在图的“中间”设置断点来检查运行时的中间张量值,因为图的执行可能已经脱离了 Python 解释器的直接控制,被优化并交给后端(如 C++ 或 CUDA)执行了。

Python中的可变对象和不可变对象?

可变对象与不可变对象的区别在于对象本身是否可变。

可变对象:list(列表) dict(字典) set(集合)

不可变对象:tuple(元组) string(字符串) int(整型) float(浮点型) bool(布尔型)

Python中None代表什么含义?

在 Python 中,None 是一个特殊的常量,表示 “没有值”“空值” 。它通常用来表示以下几种场景:

  • 一个函数没有显式返回值。
  • 一个变量的值尚未被定义。
  • 用来表示某种占位符意义的“无”值。

通过合理使用 None,可以提高代码的可读性和鲁棒性。

Python中常见的切片操作

[:n]代表列表中的第一项到第n项。我们看一个例子:

1
2
3
4
5
6
example = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

print(example[:6])

---------结果---------
[1, 2, 3, 4, 5, 6]

[n:]代表列表中第n+1项到最后一项:

1
2
3
4
5
6
example = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

print(example[6:])

---------结果---------
[7, 8, 9, 10]

总结:为什么看起来 n 代表的项数不一样?

  • [:n] 中,n 定义了切片的结束边界(不包含)。因为索引从 0 开始,所以到索引 n 之前正好是 n 个元素(第 1 到第 n 项)。
  • [n:] 中,n 定义了切片的起始边界(包含)。因为索引从 0 开始,索引 n 本身对应的是列表中的第 n+1 个元素。

这种设定是 Python 切片设计的一部分,虽然初看可能有点绕,但它使得基于索引的操作非常一致和方便,例如:

  • list[:n] + list[n:] 总是等于原始的 list
  • len(list[:n]) 总是等于 n (如果 n 不超过列表长度)。

记住核心规则:start 包含,stop 不包含,索引从 0 开始。这样就能准确理解各种切片操作了。

[-1]代表取列表的最后一个元素:

1
2
3
4
5
6
example = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

print(example[-1])

---------结果---------
10

[:-1]代表取除了最后一个元素的所有元素:

1
2
3
4
5
6
example = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

print(example[:-1])

---------结果---------
[1, 2, 3, 4, 5, 6, 7, 8, 9]

[::-1]代表取整个列表的相反列表:

1
2
3
4
5
6
example = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

print(example[::-1])

---------结果---------
[10, 9, 8, 7, 6, 5, 4, 3, 2, 1]

[1:]代表从第二个元素意指读取到最后一个元素:

1
2
3
4
5
6
example = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

print(example[1:])

---------结果---------
[2, 3, 4, 5, 6, 7, 8, 9, 10]

[4::-1]代表取下标为4(即第五个元素)的元素和之前的元素反转读取:

1
2
3
4
5
6
example = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

print(example[4::-1])

---------结果---------
[5, 4, 3, 2, 1]

Python中如何进行异常处理?

一般情况下,在Python无法正常处理程序时就会发生一个异常。异常在Python中是一个对象,表示一个错误。当Python脚本发生异常时我们需要捕获处理它,否则程序会终止执行。

捕捉异常可以使用try,except和finally语句。

try和except语句用来检测try语句块中的错误,从而让except语句捕获异常信息并处理。

1
2
3
4
5
6
7
8
9
10
try:
6688 / 0
except:
'''异常的父类,可以捕获所有的异常'''
print "0不能被除"
else:
'''保护不抛出异常的代码'''
print "没有异常"
finally:
print "最后总是要执行我"

Python中remove,del以及pop之间的区别?

remove,del以及pop都可以用于删除列表、字符串等里面的元素,但是具体用法并不相同。

  1. remove是剔除第一个匹配的值。
  2. del是通过索引来删除当中的元素。
  3. pop是通过索引来删除当中的元素,并且返回该元素;若括号内不添加索引值,则默认删除最后一个元素。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> a = [0, 1, 2, 1, 3] 
>>> a.remove(1)
>>> a
[0, 2, 1, 3]

>>> a = [0, 1, 2, 1, 3]
>>> del a[1]
[0, 2, 1, 3]

>>> a = [0, 1, 2, 1, 3]
>>> a.pop(1)
1
>>> a
[0, 2, 1, 3]

python中如何无损打开图像,并无损保存图像?

在Python中,如果我们想无损地打开和保存图像,关键是选择支持无损保存的图像格式,如PNG、TIFF或BMP。使用Python的Pillow库可以方便地处理这种任务。

使用Pillow无损打开和保存图像

  1. 打开图像:使用Image.open()函数打开图像文件。这个函数不会修改图像数据,因此打开图像本身是无损的。
  2. 保存图像:使用Image.save()函数,并确保选择无损格式,如PNG。

以下是一个具体的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
from PIL import Image

# 打开图像文件
img = Image.open('example.jpg') # 假设原始文件是JPEG格式

# 执行一些图像处理操作(可选)
# 注意,确保这些操作是无损的,如大小调整、裁剪等

# 保存图像为PNG格式,这是无损压缩
img.save('output.png', 'PNG')

# 或者用这种方式进行无损保存
img.save('path_to_save_image.png', format='PNG', optimize=True)

注意事项

  • PNG使用的是无损压缩算法,这意味着图像的所有信息在压缩和解压过程中都被完整保留,不会有任何质量损失。
  • 当从有损压缩格式(如JPEG)转换为无损格式(如PNG)时,虽然保存过程是无损的,但原始从JPEG格式读取的图像可能已经丢失了一些信息。因此,最佳实践是始终从无损格式源文件开始操作,以保持最高质量。
  • 如果我们的图像已经在一个无损格式(如PNG),你只需重新保存它,同样选择无损格式,即可保持图像质量不变。
  • 无损保存与 optimize=True 选项:optimize=True选项会尝试找到更为压缩的存储方式来保存文件,但不会影响图像的质量。具体来说,它在保存文件时会尝试优化图像数据的存储方式,比如重新排列文件中的色块和使用更有效的编码方法,但仍然保持图像数据的完整性和质量。因此,即使使用了 optimize=True 选项,保存的PNG文件也是无损的,不会有质量损失。这使得PNG格式非常适合需要无损压缩的应用,如需要频繁编辑和保存的图像处理任务。

处理其他图像格式

对于其他格式如TIFF或BMP,Pillow同样支持无损操作,保存方法类似,只需改变保存格式即可:

1
2
3
4
5
# 保存为TIFF格式
img.save('output.tiff', 'TIFF')

# 保存为BMP格式
img.save('output.bmp', 'BMP')

使用Pillow库,我们可以轻松地在Python中进行无损图像处理。它提供了广泛的功能,能够处理几乎所有常见的图像格式,并支持复杂的图像处理任务。

Python多进程中的fork和spawn模式有什么区别?

  1. windows和MacOS中默认为spawn模式,unix系统默认为fork模式,其中windows只支持spawn,unix同时支持两者;
  2. spawn模式不会继承父进程的资源,而是从头创建一个全新的进程,启动较慢;
  3. fork模式会继承父进程的资源,即通过复制父进程资源来快速创建子进程,启动较快;

什么是Python中的推导式?Python的推导式一共有多少种?

Python中的推导式(comprehensions)是一种简洁、灵活且高效的构建Python数据结构的方法,包括列表、字典、集合和生成器。推导式允许以表达式的形式快速生成新的数据结构,同时在创建过程中可以直接应用条件筛选或操作。下面详细介绍Python中四种主要的推导式:

1. 列表推导式(List Comprehensions)

功能:用于创建列表,可以通过应用表达式自动处理并生成新列表。

基本语法

1
[expression for item in iterable if condition]
  • expression 是对 item 的操作或者应用表达式。
  • item 是从 iterable 中逐个取出的元素。
  • condition 是一个可选的条件语句,用于过滤。

示例

1
2
3
4
5
# 生成0-9每个数字的平方的列表
squares = [x**2 for x in range(10)]

# 结果
{0, 1, 64, 4, 36, 9, 16, 49, 81, 25}

2. 字典推导式(Dictionary Comprehensions)

功能:用于创建字典,允许通过迭代可迭代对象来生成键值对。

基本语法

1
{key_expression : value_expression for item in iterable if condition}
  • key_expression 表示字典的键的表达式。
  • value_expression 表示字典的值的表达式。
  • item 是从 iterable 中逐个取出的元素。
  • condition 是一个可选的条件语句,用于过滤。

示例

1
2
3
4
5
# 使用数字作为键,其平方作为值
squares_dict = {x: x**2 for x in range(5)}

# 结果
{0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

3. 集合推导式(Set Comprehensions)

功能:用于创建集合,类似于列表推导式,但结果是一个集合,自动去重。

基本语法

1
{expression for item in iterable if condition}
  • expression 是对 item 的操作或者应用表达式。
  • item 是从 iterable 中逐个取出的元素。
  • condition 是一个可选的条件语句,用于过滤。

示例

1
2
3
4
5
# 创建一个包含0-9每个数字平方的集合
square_set = {x**2 for x in range(10)}

# 结果
{0, 1, 64, 4, 36, 9, 16, 49, 81, 25}

4. 生成器推导式(Generator Expressions)

功能:生成器推导式是一种类似于列表推导式的结构,用于创建生成器(一种迭代器),不会一次性生成所有元素,而是按需产生,节约内存。

基本语法

1
(expression for item in iterable if condition)
  • expression 是对 item 的操作或者应用表达式。
  • item 是从 iterable 中逐个取出的元素。
  • condition 是一个可选的条件语句,用于过滤。

示例

1
2
3
4
5
# 创建一个生成器,包含0-9每个数字的平方
square_gen = (x**2 for x in range(10))

# 结果
<generator object <genexpr> at 0x7f0827a98660>

推导式提供了一种高效和直观的方式来创建数据结构,使代码更加简洁易读。在合适的情况下,使用推导式可以有效提升编程效率和执行性能。

Python中函数传参时会改变参数本身吗?

在Python中,函数传参是否会改变参数本身取决于参数的数据类型和传递方式。这涉及到Python的参数传递机制,通常被称为“传引用的值”(pass-by-reference value)或者“传对象引用”(pass-by-object-reference)。这里的一些基本规则和示例将帮助大家理解这一概念:

可变与不可变对象

  1. 不可变对象:包括整数、浮点数、字符串、元组等。这些类型的数据不允许被修改。

    • 当我们传递一个不可变对象给函数时,虽然函数内部可以使用这个对象的值,但任何试图改变该对象的操作都将在本地创建一个新对象。外部原始对象不会被改变。
    1
    2
    3
    4
    5
    6
    7
    def modify(x):
    x = 10
    return x

    a = 5
    modify(a)
    print(a) # 输出 5,原始值未改变
  2. 可变对象:包括列表、字典、集合等。这些类型的数据可以被修改。

    • 当你传递一个可变对象给函数时,函数内部对这个对象的任何修改都会反映到原始对象上。
    1
    2
    3
    4
    5
    6
    def modify(lst):
    lst.append(3)

    my_list = [1, 2]
    modify(my_list)
    print(my_list) # 输出 [1, 2, 3],原始列表被修改

函数参数的工作方式

  • 在Python中,所有的函数参数都是按“引用传递”的。但实际上,这意味着当对象传递给函数时,传递的是对象的引用(内存地址),而不是对象的实际拷贝。对于不可变对象,由于不能被改变,所以任何修改都会导致创建一个新的本地对象;而对于可变对象,则可以在原地址上进行修改。
  • 对于列表和字典这样的可变对象,如果你不希望函数中的操作影响到原始数据,你可以传递一个拷贝给函数,而不是原始对象本身。
1
2
3
4
5
6
7
8
9
10
import copy

def modify(lst):
lst.append(3)

original_list = [1, 2]
new_list = copy.deepcopy(original_list)
modify(new_list)
print(original_list) # 输出 [1, 2]
print(new_list) # 输出 [1, 2, 3]

总结

在Python中,函数的行为取决于传入参数的类型。不可变对象(如整数、字符串和元组)不会在函数调用中被修改,而可变对象(如列表和字典)可以被修改。了解这些差异有助于我们更好地管理函数中数据的状态和行为。

什么是python的全局解释器锁GIL?

在Python中,全局解释器锁(Global Interpreter Lock,简称GIL)是一个重要的概念,特别是在涉及多线程执行时。GIL 是一个互斥锁,保证同一时间内只有一个线程可以执行Python字节码。简而言之,尽管在多核处理器上运行,Python 的标准实现 CPython 在执行多线程应用时,并不能有效地利用多核处理器的优势。

GIL 的目的

  1. 简化内存管理:CPython 使用引用计数来管理内存,这种方法在多线程环境中容易产生问题。GIL 通过确保一次只有一个线程运行,避免了常见的并发访问问题,如竞态条件。
  2. 保护CPython的内部数据结构:没有GIL,程序员必须采用其他并发控制技术,如细粒度锁,这可能会使CPython的实现更复杂。

GIL 的影响

尽管GIL简化了内存管理和内部数据结构的保护,但它也限制了Python程序在多核处理器上的并行执行能力:

  • 多线程局限性:在CPU密集型程序中,GIL成为性能瓶颈,因为线程不能在多个CPU核心上同时执行计算任务。
  • I/O密集型应用的表现更好:I/O操作不需要大量CPU计算,线程可能会在等待I/O操作完成时释放GIL,从而让其他线程有机会执行。

绕过GIL

虽然GIL在多线程编程中存在局限性,但Python社区提供了多种方法来绕过这些限制:

  1. 使用多进程:通过multiprocessing模块,可以创建多个进程,每个进程拥有自己的Python解释器和内存空间,从而不受GIL的限制。
  2. 使用其他实现:如Jython和IronPython,这些Python实现没有GIL,可以更好地利用多核处理器。
  3. 使用特定库:一些库设计可以在底层进行多线程或多进程操作,从而绕过GIL的限制,例如NumPy和其他与C语言库交互的扩展。

什么是python的字符串格式化技术?

在Python中,字符串格式化是一项重要的技能,能帮助我们高效地生成和处理字符串。Python提供了多种字符串格式化的方法,包括旧式的百分号(%)格式化、新式的str.format()方法以及最新的f-string(格式化字符串字面量)。

1. 百分号(%)格式化

这是Python中最古老的字符串格式化方法,使用%符号进行占位符替换。

基本用法:

1
2
3
4
name = "Alice"
age = 30
formatted_string = "Name: %s, Age: %d" % (name, age)
print(formatted_string) # 输出: Name: Alice, Age: 30
  • %s 用于字符串
  • %d 用于整数
  • %f 用于浮点数

控制浮点数精度:

1
2
3
pi = 3.14159
formatted_string = "Pi: %.2f" % pi
print(formatted_string) # 输出: Pi: 3.14

2. str.format() 方法

str.format()方法更加灵活和强大,允许指定占位符的位置和格式。

基本用法:

1
2
3
4
name = "Alice"
age = 30
formatted_string = "Name: {}, Age: {}".format(name, age)
print(formatted_string) # 输出: Name: Alice, Age: 30

使用索引指定占位符的位置:

1
2
formatted_string = "Name: {0}, Age: {1}".format(name, age)
print(formatted_string) # 输出: Name: Alice, Age: 30

使用命名参数:

1
2
formatted_string = "Name: {name}, Age: {age}".format(name=name, age=age)
print(formatted_string) # 输出: Name: Alice, Age: 30

控制浮点数精度:

1
2
3
pi = 3.14159
formatted_string = "Pi: {:.2f}".format(pi)
print(formatted_string) # 输出: Pi: 3.14

3. f-string(格式化字符串字面量)

f-string是Python 3.6引入的一种新的格式化方法,提供了简洁和高效的方式。

基本用法:

1
2
3
4
name = "Alice"
age = 30
formatted_string = f"Name: {name}, Age: {age}"
print(formatted_string) # 输出: Name: Alice, Age: 30

控制浮点数精度:

1
2
3
pi = 3.14159
formatted_string = f"Pi: {pi:.2f}"
print(formatted_string) # 输出: Pi: 3.14

字符串格式化技术的应用场景

  • 日志记录:格式化日志信息以便调试和分析。
  • 数据输出:生成报告或导出数据。
  • 用户界面:动态显示信息。

Python中is和==的区别

Python 中,对于任意的变量都具有三个基本要素:分别是 id,type,value。其中 id 为身份标识,即唯一能识别变量的标志,type 为数据类型,value 为数据值。在定义变量之后,可以看到这几个基本要素:

1
2
3
4
5
6
7
>>> a = 1
>>> id(a)
1779264528
>>> type(a)
<class 'int'>
>>> a
1

id(): 在Python中变量创建的时候,会为其分配一个内存地址,id()返回的是变量的内存地址。
is比较的是对象,即id(),==比较的是值

在Python中,整型对象和字符串对象是不可变对象,Python 会很高效地对它们进行缓存,这两个类型变量具有相同value时,id也是相等的。

1
2
3
4
5
6
7
8
9
10
11
12
>>> a = 1
>>> b = 1
>>> a == b
True
>>> a is b
True
>>> a = "a"
>>> b = "a"
>>> a == b
True
>>> a is b
True

Python中type()和isinstance()的区别?

type() 函数用于获取对象的类型,返回对象的类型对象。它还可以用于动态创建类。

1
2
3
4
5
6
7
8
# 获取对象类型
x = 42
print(type(x)) # 输出: <class 'int'>

# 动态创建类
MyDynamicClass = type('MyDynamicClass', (), {'x': 42})
obj = MyDynamicClass()
print(obj.x) # 输出: 42

这行代码展示了 type() 函数的第二种、也是更高级的用法:动态地创建类 (class)

通常我们使用 type(object) 来获取一个对象的类型(就像你的第一个例子 type(x))。但是,当 type() 接收三个参数时,它的作用就变成了创建一个新的类对象

这三个参数分别是:

  1. name (字符串): 新创建的类的名字。
    • 在这个例子中是 'MyDynamicClass'。这相当于你在用 class 关键字定义类时写下的名字:class MyDynamicClass:
  2. bases (元组): 新创建的类所继承的父类(基类)的元组。
    • 在这个例子中是 (),一个空元组。这表示 MyDynamicClass 不继承任何特定的父类,它将默认继承自 Python 的根类 object(在 Python 3 中,即使省略,类也默认继承自 object)。这相当于 class MyDynamicClass:class MyDynamicClass(object):
    • 如果想继承其他类,比如 BaseClass1BaseClass2,这里会写成 (BaseClass1, BaseClass2),相当于 class MyDynamicClass(BaseClass1, BaseClass2):
  3. dict (字典): 包含新创建类的属性和方法的字典。字典的键是属性名或方法名(字符串),值是对应的属性值或函数对象。
    • 在这个例子中是 {'x': 42}。这表示 MyDynamicClass 类将拥有一个名为 x类属性,其值为整数 42。这相当于在类定义内部写 x = 42
  • type() 返回对象的类型对象,例如 <class ‘int’>。
  • type() 主要用于获取对象的类型,以及在动态创建类时使用。
  • type() 不考虑继承关系,仅比较确切的类型。 isinstance() 函数用于判断一个对象是否是一个已知的类型,返回 True 或 False。
1
2
3
4
5
6
7
# 判断对象类型
x = 42
print(isinstance(x, int)) # 输出: True

# 判断对象是否属于多个类型中的任意一个
y = "Hello"
print(isinstance(y, (int, float, str))) # 输出: True
  • isinstance() 返回布尔值,表示对象是否是指定类型或类型元组中任意类型的实例。
  • isinstance() 主要用于判断对象是否是指定类型,适用于检查对象是否属于某一类或其子类。
  • isinstance() 考虑继承关系,如果对象是指定类型或其子类的实例,返回 True

Python中switch-case语句的实现?

在Python3.10中引入了新的match-case语法,它是一种用于模式匹配的结构。它类似于 switch-case 语句,可以根据不同的模式匹配执行不同的代码块。

  1. 常量匹配
1
2
3
4
5
6
7
match x:
case 0:
print("0")
case 1:
print("1")
case _:
print("_")
  1. 变量匹配
1
2
3
4
5
6
7
match x:
case 'a':
print("变量为'a'")
case n:
print("变量为{}".format(n))
case _:
print("其他情况")
  1. 类型匹配
1
2
3
4
5
6
7
match value:
case str_val as str:
print("字符串类型")
case int_val as int:
print("整数类型")
case _:
print("其他类型")
  1. 结构化匹配
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Point:
def __init__(self, x, y):
self.x = x
self.y = y

p = Point(1, 2)
match p:
case Point(0, 0):
print("原点")
case Point(x, 0):
print(f"在 x 轴上,x 坐标为{x}")
case Point(0, y):
print(f"在 y 轴上,y 坐标为{y}")
case Point(x, y):
print(f"在坐标系中,坐标为({x}, {y})")
  1. 区间匹配
1
2
3
4
5
6
7
match value:
case 0..10:
print("值在 0 到 10 之间")
case 11..20:
print("值在 11 到 20 之间")
case _:
print("值在其他区间")

case 后面的模式使用了区间表示。0..10 表示闭区间,包括 0 和 10;11..20 同样是闭区间,包括 11 和 20。如果匹配成功,相应的代码块将被执行。

需要注意的是,在区间匹配中,左边界必须小于或等于右边界。如果不满足这个条件,将会引发 SyntaxError 错误。

区间匹配也可以与其他类型的匹配结合使用

1
2
3
4
5
6
7
8
9
10
11
12
match value:
case str_val as str:
print("字符串类型")
case int_val as int:
case 0..10:
print("整数在 0 到 10 之间")
case 11..20:
print("整数在 11 到 20 之间")
case _:
print("其他整数")
case _:
print("其他类型")

示例中,首先匹配原始值的类型,然后再根据整数值的区间进行匹配

match-case和switch-case的不同:

  1. 模式匹配:match-case 结构支持更灵活的模式匹配,可以匹配常量、变量、类型、结构化数据以及区间。这使得在匹配逻辑更加清晰,并且可以消除大量的连续的 if-elif 语句。
  2. 穿透:在 switch-case 语句中,一旦匹配到某个 case,默认会从匹配的 case 开始执行代码块,并且在每个 case 结束后终止整个 switch 结构。而在 match-case 结构中,默认是不会穿透的,也就是说只会执行匹配成功的 case 对应的代码块,并在执行完后立即退出 match-case 结构,不会执行其他 case 对应的代码块。
  3. 缺省情况:在 match-case 结构中可以使用 _ 作为默认模式,用于处理无法匹配到其他模式的情况。而在 switch-case 结构中,如果没有匹配到任何 case,需要自己另外处理这种情况。
  4. 可迭代对象:在 match-case 结构中,可以使用 match 对可迭代对象进行解构匹配,匹配其中的每个元素。而在 switch-case 结构中,需要手动遍历可迭代对象进行匹配。

介绍一下Python中耦合和解耦的代码设计思想

在AI行业的Python使用中,耦合和解耦的思想是设计良好的AI算法系统的重要原则。耦合(coupling)指的是模块或组件之间的依赖关系,而解耦(decoupling)指的是减少或消除这种依赖性,使AI算法系统的各部分可以独立开发、测试和维护。 下面是Python中耦合和解耦的详细方法(使用依赖注入、接口和抽象类、事件驱动架构等),提高AI算法系统的灵活性、可维护性和可扩展性。

1. 耦合(Coupling)

耦合表示不同模块或组件之间的依赖关系。当两个模块高度耦合时,一个模块的变化可能会影响另一个模块,导致系统维护和扩展的难度增加。耦合有两种主要形式:紧耦合和松耦合。

紧耦合

紧耦合是指模块之间的依赖性很强,任何一个模块的变化都会导致其他模块的变化。紧耦合系统难以维护和扩展。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Database:
def connect(self):
print("Connecting to the database")

class UserService:
def __init__(self):
self.db = Database()

def get_user(self, user_id):
self.db.connect()
print(f"Getting user {user_id}")

user_service = UserService()
user_service.get_user(1)

在这个示例中,UserService 直接依赖于 Database,这使得它们高度耦合。如果 Database 类发生变化,UserService 也需要相应地修改。

2. 解耦(Decoupling)

解耦指的是减少或消除模块或组件之间的依赖关系,使它们能够独立地开发、测试和维护。解耦可以通过以下几种方法实现:

依赖注入(Dependency Injection)

依赖注入(松耦合)是一种设计模式,允许将依赖项从外部传递给一个对象,而不是在对象内部创建依赖项。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Database:
def connect(self):
print("Connecting to the database")

class UserService:
def __init__(self, db):
self.db = db

def get_user(self, user_id):
self.db.connect()
print(f"Getting user {user_id}")

db = Database()
user_service = UserService(db)
user_service.get_user(1)

在这个示例中,UserService 不再直接创建 Database 实例,而是通过构造函数接收一个 Database 实例。这减少了模块之间的耦合度。

使用接口和抽象类

通过使用接口或抽象类,可以将具体实现与接口分离,从而实现解耦。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from abc import ABC, abstractmethod

class DatabaseInterface(ABC):
@abstractmethod
def connect(self):
pass

class Database(DatabaseInterface):
def connect(self):
print("Connecting to the database")

class UserService:
def __init__(self, db: DatabaseInterface):
self.db = db

def get_user(self, user_id):
self.db.connect()
print(f"Getting user {user_id}")

db = Database()
user_service = UserService(db)
user_service.get_user(1)

在这个示例中,Database 实现了 DatabaseInterface 接口,UserService 依赖于 DatabaseInterface 而不是 Database 的具体实现。这种方式提高了系统的灵活性和可维护性。

  • 设计角度看,UserService 的编写是面向接口编程的,它只关心传入的对象是否满足 DatabaseInterface 定义的契约。
  • 类型系统(通过类型提示)看,UserService 明确要求一个符合 DatabaseInterface 类型的对象。
  • 这使得 UserService 不依赖于任何特定的实现细节,从而实现了与具体数据库实现的解耦。

虽然运行时执行的是 Database 类中的代码,但这种设计上的依赖关系是建立在抽象接口 DatabaseInterface 之上的。

使用事件驱动架构

事件驱动架构通过事件和消息来解耦模块。模块通过事件总线进行通信,而不需要直接依赖其他模块。

示例

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
class EventBus:
def __init__(self):
self.listeners = []

def subscribe(self, listener):
self.listeners.append(listener)

def publish(self, event):
for listener in self.listeners:
listener(event)

class Database:
def connect(self):
print("Connecting to the database")

class UserService:
def __init__(self, event_bus):
self.event_bus = event_bus
self.event_bus.subscribe(self.handle_event)

def handle_event(self, event):
if event == "GET_USER":
self.get_user(1)

def get_user(self, user_id):
db = Database()
db.connect()
print(f"Getting user {user_id}")

event_bus = EventBus()
user_service = UserService(event_bus)
event_bus.publish("GET_USER")

在这个示例中,UserService 通过事件总线 EventBus 进行通信,而不是直接依赖其他模块。这种架构提高了系统的模块化和扩展性。

Python中的函数参数有哪些类型与规则?

在Python中,函数的参数有多种类型和一套设定的规则需要遵守,这使得函数定义和调用非常灵活。以下是Python详细的参数规则和类型解释:

1. 位置参数(Positional Arguments)

位置参数是最常见的参数类型,按顺序传递给函数。

1
2
3
4
def greet(name, age):
print(f"Hello, my name is {name} and I am {age} years old.")

greet("Alice", 30)

2. 关键字参数(Keyword Arguments)

关键字参数允许在函数调用时通过参数名指定参数值,使得参数传递更具可读性,并且不必按顺序传递。

1
greet(age=30, name="Alice")

3. 默认参数(Default Arguments)

默认参数在函数定义时指定默认值,如果在函数调用时未提供该参数,则使用默认值。

1
2
3
4
5
def greet(name, age=25):
print(f"Hello, my name is {name} and I am {age} years old.")

greet("Alice") # 使用默认值25
greet("Bob", 30) # 覆盖默认值

4. 可变位置参数(Variable Positional Arguments)

使用 *args 语法,允许函数接受任意数量的位置参数。 *args 是一个元组。

1
2
3
4
5
def greet(*names):
for name in names:
print(f"Hello, {name}!")

greet("Alice", "Bob", "Charlie")

5. 可变关键字参数(Variable Keyword Arguments)

使用 **kwargs 语法,允许函数接受任意数量的关键字参数。 **kwargs 是一个字典。

1
2
3
4
5
def greet(**kwargs):
for key, value in kwargs.items():
print(f"{key} is {value}")

greet(name="Alice", age=30, location="Wonderland")

参数顺序规则

在定义函数时,参数应按照以下顺序排列:

  1. 位置参数
  2. 关键字参数
  3. 默认参数
  4. 可变位置参数 *args
  5. 可变关键字参数 **kwargs

示例

1
2
3
4
5
def example(a, b=2, *args, **kwargs):
print(a, b, args, kwargs)

example(1) # 输出: 1 2 () {}
example(1, 3, 4, 5, x=10, y=20) # 输出: 1 3 (4, 5) {'x': 10, 'y': 20}

什么是Python中的魔术方法?

在Python类中,以双下划线(__)开头和结尾的方法通常被称为“魔术方法”或“特殊方法”。这些方法定义了类的特殊行为,使类可以与Python的内置操作和函数紧密集成。以下是一些常见且常用的魔术方法:

1. 对象初始化和表示

  • __init__(self, ...):初始化对象时调用的构造方法。

    1
    2
    3
    class MyClass:
    def __init__(self, value):
    self.value = value
  • __repr__(self):返回对象的官方字符串表示,通常可以用来重新创建该对象。

    1
    2
    3
    class MyClass:
    def __repr__(self):
    return f"MyClass({self.value!r)"
  • __str__(self):返回对象的非正式字符串表示,适合用户友好输出。

    1
    2
    3
    class MyClass:
    def __str__(self):
    return f"Value is {self.value}"

2. 运算符重载

  • __add__(self, other):定义加法运算符 + 的行为。

    1
    2
    3
    4
    5
    6
    class MyClass:
    def __init__(self, value):
    self.value = value

    def __add__(self, other):
    return MyClass(self.value + other.value)
  • __sub__(self, other):定义减法运算符 - 的行为。

    1
    2
    3
    class MyClass:
    def __sub__(self, other):
    return MyClass(self.value - other.value)
  • __mul__(self, other):定义乘法运算符 * 的行为。

    1
    2
    3
    class MyClass:
    def __mul__(self, other):
    return MyClass(self.value * other.value)
  • __truediv__(self, other):定义真除法运算符 / 的行为。

    1
    2
    3
    class MyClass:
    def __truediv__(self, other):
    return MyClass(self.value / other.value)

3. 比较运算符

  • __eq__(self, other):定义等于运算符 == 的行为。

    1
    2
    3
    class MyClass:
    def __eq__(self, other):
    return self.value == other.value
  • __lt__(self, other):定义小于运算符 < 的行为。

    1
    2
    3
    class MyClass:
    def __lt__(self, other):
    return self.value < other.value
  • __gt__(self, other):定义大于运算符 > 的行为。

    1
    2
    3
    class MyClass:
    def __gt__(self, other):
    return self.value > other.value

4. 容器类型协议

  • __len__(self):定义 len() 函数的行为。

    1
    2
    3
    class MyClass:
    def __len__(self):
    return len(self.value)
  • __getitem__(self, key):定义获取元素的行为,如 self[key]

    1
    2
    3
    class MyClass:
    def __getitem__(self, key):
    return self.value[key]
  • __setitem__(self, key, value):定义设置元素的行为,如 self[key] = value

    1
    2
    3
    class MyClass:
    def __setitem__(self, key, value):
    self.value[key] = value
  • __delitem__(self, key):定义删除元素的行为,如 del self[key]

    1
    2
    3
    class MyClass:
    def __delitem__(self, key):
    del self.value[key]

5. 迭代器协议

  • __iter__(self):定义返回迭代器的行为。

    1
    2
    3
    class MyClass:
    def __iter__(self):
    return iter(self.value)
  • __next__(self):定义迭代器的下一个元素。

    1
    2
    3
    class MyClass:
    def __next__(self):
    return next(self.value)

6. 可调用对象

  • __call__(self, ...):使对象可以像函数一样被调用。

    1
    2
    3
    class MyClass:
    def __call__(self, *args, **kwargs):
    print("Called with", args, kwargs)

这些魔术方法使得类在使用时更加灵活和自然,能够与Python内置的操作和函数无缝衔接。

介绍一下Python中常用的标准库以及功能

Python 提供了丰富的标准库,这些库为我们提供了常用的工具和功能,涵盖了从操作系统交互、文件处理、数据序列化、网络通信到多线程编程等方方面面。这些标准库大大简化了我们的工作,使得开发高效、稳定、易于维护的应用程序变得更加容易。在实际项目中,熟练掌握和合理运用这些标准库,可以显著提高我们的开发效率和代码质量。

1. os

  • 功能os 模块提供了一种与操作系统进行交互的便捷方式。我们可以使用它来处理文件和目录、管理环境变量、执行操作系统命令等。
  • 常用功能:
    • os.path: 用于路径操作(如路径拼接、文件名提取)。
    • os.makedirs(): 创建多层目录。
    • os.getenv(): 获取环境变量。
    • os.system(): 执行系统命令。

2. sys

  • 功能sys 模块提供了与 Python 解释器相关的函数和变量,允许我们与解释器进行交互。
  • 常用功能:
    • sys.argv: 命令行参数列表。
    • sys.exit(): 终止程序运行。
    • sys.path: 模块搜索路径列表,可以动态修改。
    • sys.stdout / sys.stderr: 输出流和错误流的重定向。

3. math

  • 功能math 模块提供了基本的数学函数和常量,如三角函数、对数、指数、平方根、常数(如π)等。
  • 常用功能:
    • math.sqrt(): 计算平方根。
    • math.sin(), math.cos(), math.tan(): 三角函数。
    • math.log(): 计算对数(自然对数和其他基数对数)。
    • math.factorial(): 计算阶乘。

4. datetime

  • 功能datetime 模块用于处理日期和时间,支持日期的算术运算、格式化、解析等操作。
  • 常用功能:
    • datetime.date: 表示日期(年、月、日)。
    • datetime.time: 表示时间(时、分、秒、毫秒)。
    • datetime.datetime: 表示日期和时间的组合。
    • datetime.timedelta: 表示两个日期或时间的差。
    • datetime.strftime(): 格式化日期和时间为字符串。
    • datetime.strptime(): 从字符串解析日期和时间。

5. time

  • 功能time 模块提供了与时间相关的函数,如暂停、获取当前时间等。
  • 常用功能:
    • time.time(): 返回当前时间的时间戳(自1970-01-01以来的秒数)。
    • time.sleep(): 让程序暂停指定的时间(秒)。
    • time.localtime(): 将时间戳转换为本地时间的结构体。
    • time.strftime(): 格式化时间为字符串。

6. random

  • 功能random 模块用于生成伪随机数,并提供了随机选择、打乱顺序等功能。
  • 常用功能:
    • random.random(): 返回0到1之间的随机浮点数。
    • random.randint(a, b): 返回a到b之间的随机整数。
    • random.choice(): 从序列中随机选择一个元素。
    • random.shuffle(): 随机打乱序列顺序。
    • random.sample(): 从序列中随机取样。

7. re

  • 功能re 模块提供了正则表达式的支持,允许你在字符串中进行复杂的模式匹配、查找和替换。
  • 常用功能:
    • re.match(): 从字符串的起始位置进行匹配。
    • re.search(): 在字符串中查找模式的首次出现。
    • re.findall(): 查找字符串中所有符合模式的部分。
    • re.sub(): 替换字符串中符合模式的部分。

8. json

  • 功能json 模块提供了将 Python 对象转换为 JSON 格式,以及将 JSON 数据解析为 Python 对象的功能。
  • 常用功能:
    • json.dump(): 将 Python 对象序列化为 JSON 格式,并写入文件。
    • json.dumps(): 将 Python 对象序列化为 JSON 格式的字符串。
    • json.load(): 从文件中读取 JSON 数据并解析为 Python 对象。
    • json.loads(): 将 JSON 字符串解析为 Python 对象。

9. subprocess

  • 功能subprocess 模块允许我们生成子进程,并与其交互,代替旧的 os.system() 方法。
  • 常用功能:
    • subprocess.run(): 运行命令并等待其完成。
    • subprocess.Popen(): 启动一个子进程,并可以通过 stdin, stdout, stderr 与其交互。
    • subprocess.call(): 执行命令并返回状态码。

10. collections

  • 功能collections 模块提供了几个有用的容器数据类型,如 Counter, deque, defaultdict, namedtuple 等。
  • 常用功能:
    • Counter: 用于计数的字典,可以统计元素出现的次数。
    • deque: 双端队列,支持在两端高效地添加和删除元素。
    • defaultdict: 带有默认值的字典。
    • namedtuple: 定义命名元组,可以像对象一样访问元素。

11. itertools

  • 功能itertools 模块提供了用于操作迭代器的函数,用于高效地处理循环和组合生成器等任务。
  • 常用功能:
    • itertools.chain(): 将多个迭代器连接在一起。
    • itertools.cycle(): 无限循环一个迭代器。
    • itertools.permutations(): 生成序列的所有排列。
    • itertools.combinations(): 生成序列的所有组合。
    • itertools.product(): 生成笛卡尔积。

12. functools

  • 功能functools 模块提供了处理和操作函数的工具,支持部分函数应用、缓存、比较等功能。
  • 常用功能:
    • functools.partial(): 创建一个部分应用的函数。
    • functools.lru_cache(): 通过缓存来优化函数性能。
    • functools.reduce(): 累积地将函数应用于序列的元素。

13. threading

  • 功能threading 模块支持多线程编程,允许我们在 Python 中创建和管理线程。
  • 常用功能:
    • threading.Thread(): 创建并启动一个新线程。
    • threading.Lock(): 实现线程间的互斥锁。
    • threading.Event(): 用于线程间通信的同步原语。
    • threading.Timer(): 延迟执行的线程。

14. multiprocessing

  • 功能multiprocessing 模块提供了支持并行处理的功能,通过在多个进程中分配任务来提高计算效率。
  • 常用功能:
    • multiprocessing.Process(): 创建并启动一个新进程。
    • multiprocessing.Pool(): 创建一个进程池,用于并行处理多个任务。
    • multiprocessing.Queue(): 用于进程间通信的队列。
    • multiprocessing.Manager(): 管理共享状态的服务。

15. shutil

  • 功能shutil 模块提供了高级的文件操作功能,如复制、移动、删除文件和目录。
  • 常用功能:
    • shutil.copy(): 复制文件。
    • shutil.move(): 移动文件或目录。
    • shutil.rmtree(): 删除目录及其所有内容。
    • shutil.make_archive(): 创建压缩文件(zip、tar 等)。

16. glob

  • 功能glob 模块用于匹配文件路径名模式,如查找符合特定模式的文件。
  • 常用功能:
    • glob.glob(): 返回符合特定模式的文件路径列表。
    • glob.iglob(): 返回一个迭代器,生成符合模式的文件路径。

17. csv

  • 功能csv 模块提供了读写 CSV 文件的功能,支持多种格式的 CSV 文件操作。
  • 常用功能:
    • csv.reader(): 读取 CSV 文件内容,返回一个可迭代的 reader 对象。
    • csv.writer(): 写入 CSV 文件内容。
    • csv.DictReader(): 以字典的形式读取 CSV 文件。
    • csv.DictWriter(): 以字典的形式写入 CSV 文件。

18. hashlib

  • 功能hashlib 模块提供了用于生成哈希值和摘要的算法,如 SHA-1、SHA-256、MD5 等。
  • 常用功能:
    • hashlib.sha256(): 生成 SHA-256 哈希值。
    • hashlib.md5(): 生成 MD5 哈希值。
    • hashlib.blake2b(): 生成 Blake2b 哈希值。
    • hashlib.sha512(): 生成 SHA-512 哈希值。

19. http

  • 功能http 模块提供了处理 HTTP 请求和响应的功能,包含服务器和客户端相关的工具。
  • 常用功能:
    • http.client: 用于发起 HTTP 请求。
    • http.server: 用于创建简单的 HTTP 服务器。
    • http.cookies: 用于处理 HTTP Cookies。
    • http.HTTPStatus: 枚举 HTTP 状态码。

20. socket

  • 功能socket 模块提供了低级别的网络通信接口,支持 TCP、UDP、IP 等网络协议的编程。
  • 常用功能:
    • socket.socket(): 创建一个套接字对象。
    • socket.bind(): 绑定套接字到地址。
    • socket.listen(): 监听连接。
    • socket.accept(): 接受连接请求。
    • socket.connect(): 连接到远程套接字。

python中海象运算符的介绍

介绍

Python 3.8 引入了赋值表达式(也称为海象运算符),它允许在表达式中进行赋值操作。赋值表达式的基本语法是 :=,它将右侧的值赋给左侧的变量,并返回该值。

语法

1
<variable> := <expression>

示例

基本用法

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

循环中

1
2
while (line := file.readline()) != '':
process(line)

函数参数

1
2
3
4
5
6
def send_email(address, /, *, subject, message, sender):
"""发送电子邮件"""
# 使用海象运算符来获取用户名和域名
user, domain = address.split('@')
# 发送电子邮件
send_email(user, domain, subject, message, sender)

赋值表达式可以简化代码,特别是在需要计算和赋值的情况下。