01 python的GIL是什么?
GIL 是 Global Interpreter Lock(全局解释器锁)的缩写,是 Python CPython 解释器(Python 的主流实现)中的一个技术术语。GIL 是一个互斥锁,用于在任何时刻只允许一个线程执行 Python 字节码。这意味着在多线程的环境中,即使在多核心的机器上,只有一个线程在给定的时间内被执行。
想象你有一个咖啡店,而这家咖啡店只有一个咖啡机。GIL 就像这个咖啡店的规矩:一次只允许一个员工使用咖啡机。
-
如果有很多顾客来买咖啡,即使你雇佣了多个员工(这里的员工可以想象为线程),由于这个“一次只允许一个员工使用咖啡机”的规矩,其他的员工都必须等待,直到咖啡机空闲下来。这就是为什么即使你有很多线程,但在 CPU 密集型任务上,它们还是一个接一个地执行,而不是同时执行。
-
但是,如果员工在为客户做咖啡的时候,需要等待咖啡豆磨成咖啡粉(可以看作 I/O 操作,如读取文件或网络请求),那么这个员工可以让出咖啡机,允许其他员工来使用。这就是为什么在 I/O 密集型任务上,多线程仍然是有效的,因为线程在等待的时候可以释放 GIL,让其他线程运行。
所以,GIL 就像一个规定,确保一次只有一个线程执行任务,而其他线程必须等待。这对于保护数据是有帮助的,但在多核 CPU 上,它限制了多线程程序的并发性能。
希望这个比喻能让你更好地理解 GIL 的概念。
GIL(Global Interpreter Lock)是一个与Python语言紧密相关的概念,尤其是在CPython解释器中。GIL是一种机制,用于确保任何时候只有一个线程在执行Python字节码。这意味着即使在多线程环境下,Python代码在执行时仍然是单线程的。这主要是因为CPython的内存管理并不是线程安全的。
如何在项目中使用GIL
1. 了解GIL的限制
- 在编写Python程序时,特别是涉及到多线程的时候,要认识到由于GIL的存在,多线程并不会带来效率的大幅提升,尤其是在CPU密集型任务中。
2. 避免长时间持有GIL
- 在实现多线程程序时,应尽量避免一个线程长时间持有GIL,以防止其他线程饿死。
3. 使用多进程代替多线程
- 对于CPU密集型任务,可以使用多进程(例如
multiprocessing
模块)代替多线程。因为每个Python进程都有自己的Python解释器和内存空间,所以它们不会受到GIL的限制。
5. 使用其他的Python解释器
- 考虑使用不受GIL限制的Python解释器,如Jython或IronPython。
例子
假设你有一个CPU密集型的任务,比如计算大量数据的统计信息。
-
使用多线程(受GIL限制):
import threading def compute_statistics(data): # 计算统计信息 pass thread1 = threading.Thread(target=compute_statistics, args=(data_slice1,)) thread2 = threading.Thread(target=compute_statistics, args=(data_slice2,)) thread1.start() thread2.start() thread1.join() thread2.join()
-
使用多进程(避免GIL限制):
from multiprocessing import Process def compute_statistics(data): # 计算统计信息 pass process1 = Process(target=compute_statistics, args=(data_slice1,)) process2 = Process(target=compute_statistics, args=(data_slice2,)) process1.start() process2.start() process1.join() process2.join()
02 python列表去重
在 Python 中,有多种方法可以实现列表去重。以下是几种常见的方法:
- 使用集合(Set):将列表转换为集合,集合会自动去除重复元素,然后再将它转换回列表。
my_list = [1, 2, 2, 3, 4, 4, 5]
unique_list = list(set(my_list))
print(unique_list)
输出:
[1, 2, 3, 4, 5]
03 fun(args,**kwargs)中的args,**kwargs什么意思?
以下是一个简单的例子来说明它们的用法:
def example_function(*args, **kwargs):
print("args: ", args)
print("kwargs: ", kwargs)
# 调用函数
example_function(1, 2, 3, a=4, b=5, c=6)
输出:
args: (1, 2, 3)
kwargs: {'a': 4, 'b': 5, 'c': 6}
从上面的例子可以看出:
args
捕获了所有位置参数(那些没有被命名的参数),并将它们存储为一个元组。kwargs
捕获了所有的关键字参数(那些被命名的参数),并将它们存储为一个字典。
这两种语法允许函数接受任意数量和类型的参数,使得函数更加灵活。
04 元组和列表
让我们先讨论 Python 中的元组(tuple)和列表(list)。
-
列表 (list):
- 列表是一个有序的元素集合。
- 列表是可变的,这意味着你可以在创建后修改、添加或删除列表中的元素。
- 它使用方括号
[]
进行定义。 - 例如:
fruits = ['apple', 'banana', 'cherry']
-
元组 (tuple):
- 元组也是一个有序的元素集合,但与列表不同,元组是不可变的。这意味着一旦你创建了元组,就不能再修改、添加或删除其中的元素。
- 它使用圆括号
()
进行定义。 - 例如:
colors = ('red', 'green', 'blue')
以下是一个使用元组的示例,这个示例中我们使用元组来表示一个二维的点 (x, y):
def compute_distance(point1, point2):
"""计算两个点之间的欧几里得距离"""
x1, y1 = point1
x2, y2 = point2
return ((x2 - x1)**2 + (y2 - y1)**2)**0.5
# 定义两个点,每个点都是一个元组
point_A = (1, 2)
point_B = (4, 6)
# 计算这两个点之间的距离
distance = compute_distance(point_A, point_B)
print(f"The distance between point A and point B is {
distance:.2f}")
在上述代码中:
- 我们使用元组
point_A
和point_B
来分别表示两个二维点。 - 我们定义了一个函数
compute_distance()
来计算这两个点之间的欧几里得距离。 - 由于元组是不可变的,我们可以确保在函数中不会意外地修改这两个点的坐标。
这是一个典型的使用元组的场景,因为二维点的 x 和 y 坐标在表示一个特定的点时是固定的,不应该被修改。
如果你的数据集是固定的并且不会发生变化(例如,一周中的天数、地球上的大洲等),那么使用元组是合适的。如果数据集需要动态地更改,那么使用列表更为恰当。
05 python2和python3的range(100)的区别
在 Python 2 和 Python 3 中,range(100)
的行为有所不同。
在 Python 2 中,range(100)
返回一个列表,包含从 0 到 99 的整数值:
>>> range(100)
[0, 1, 2, 3, ..., 98, 99]
在 Python 3 中,range(100)
返回一个可迭代对象(range 对象),它表示从 0 到 99 的整数范围:
>>> range(100)
range(0, 100)
这样做的改变是出于性能优化的考虑。在 Python 2 中,range()
函数会立即生成一个完整的列表,占用大量内存,尤其是对于较大的范围。而在 Python 3 中,range()
函数返回的是一个惰性序列,只有在需要时才会生成下一个元素,节省了内存空间。
如果需要在 Python 3 中获得与 Python 2 相同的行为(返回一个列表),可以使用 list()
函数将 range()
转换为列表:
>>> list(range(100))
[0, 1, 2, 3, ..., 98, 99]
06 一句话解释什么样的语言能够用装饰器?
下面是一个示例,说明了装饰器的作用和使用:
# 定义一个装饰器函数
def uppercase_decorator(func):
def wrapper():
result = func() # 调用原始函数,获取结果
return result.upper() # 将结果转为大写并返回
return wrapper
# 定义一个普通函数
def say_hello():
return "Hello, World!"
# 使用装饰器来装饰函数
decorated_func = uppercase_decorator(say_hello)
# 调用被装饰后的函数
print(decorated_func()) # 输出: HELLO, WORLD!
这个例子展示了Python中装饰器的特性:函数作为参数传递给另一个函数(高阶函数)以及返回一个新的函数(闭包)。这使得我们能够在不修改原始函数代码的情况下,通过装饰器来扩展其功能。
07 简述面向对象中__new__和__init__区别
在面向对象的 Python 编程中,__new__
和 __init__
是两个经常被提到的特殊方法,它们都与对象的创建和初始化有关,但它们的职责和执行时机是不同的。
举个例子,考虑一个简单的 Person
类:
class Person:
def __new__(cls, *args, **kwargs):
print("Creating an instance...")
instance = super(Person, cls).__new__(cls)
return instance
def __init__(self, name, age):
print("Initializing the instance...")
self.name = name
self.age = age
p = Person("Alice", 30)
输出:
Creating an instance...
Initializing the instance...
从这个例子中,我们可以看到:
- 当我们实例化
Person
类时,首先调用了__new__
方法来创建实例。 - 创建实例后,
__init__
方法被调用,初始化实例的属性。
大多数时候,我们只需要定义 __init__
方法来初始化对象的属性,但在某些特定的场景下(如单例模式、自定义不可变对象等),我们可能需要重写 __new__
方法。
08 列表[1,2,3,4,5],请使用map()函数输出[1,4,9,16,25],并使用列表推导式提取出大于10的数,最终输出[16,25]
使用 map()
函数将列表中每个元素平方并返回一个新的列表:
numbers = [1, 2, 3, 4, 5]
squared_numbers = list(map(lambda x: x ** 2, numbers))
print(squared_numbers)
输出:
[1, 4, 9, 16, 25]
使用列表推导式从 squared_numbers
列表中提取大于10的数:
filtered_numbers = [x for x in squared_numbers if x > 10]
print(filtered_numbers)
输出:
[16, 25]
map()
以下是一个使用 map()
函数的例子:
def square(x):
return x ** 2
numbers = [1, 2, 3, 4, 5]
squared_numbers = list(map(square, numbers))
print(squared_numbers) # 输出: [1, 4, 9, 16, 25]
列表推导式
列表推导式(List Comprehension)是Python中一种简洁且高效的方法,用于从一个或多个迭代器中快速创建列表。其基本结构如下:
[expression for item in iterable if condition]
这里的各个部分意味着:
expression
:新列表的元素,可以是对item
的计算结果或其任何形式的表达式。item
:是iterable
中的对象。iterable
:是一个可以迭代的对象,如列表、元组、集合等。condition
:是一个可选的条件语句,用于筛选出满足条件的元素。
示例
-
基本的列表推导式:
# 从一个列表中创建一个新的列表 original_list = [1, 2, 3, 4, 5] new_list = [x * 2 for x in original_list] # 结果:[2, 4, 6, 8, 10]
-
带条件的列表推导式:
# 只选择大于2的元素 original_list = [1, 2, 3, 4, 5] filtered_list = [x for x in original_list if x > 2] # 结果:[3, 4, 5]
-
嵌套循环的列表推导式:
# 结合两个列表的所有元素 list1 = [1, 2, 3] list2 = ['a', 'b', 'c'] combined_list = [(x, y) for x in list1 for y in list2] # 结果:[(1, 'a'), (1, 'b'), (1, 'c'), (2, 'a'), (2, 'b'), (2, 'c'), (3, 'a'), (3, 'b'), (3, 'c')]
列表推导式是一种编写更简洁且易于理解的代码的有效方式,尤其是当你需要从现有的数据结构中创建新列表时。
09 python中生成随机整数、随机小数、0–1之间小数方法
随机整数:random.randint(a,b),生成区间内的整数
随机小数:习惯用numpy库,利用np.random.randn(5)生成5个随机小数
0-1随机小数:random.random(),括号中不传参
10 避免转义给字符串加哪个字母表示原始字符串?
为了避免在字符串中使用反斜杠进行转义,可以在字符串前加上字母 r
,表示原始字符串(Raw String)。
以下是一个示例,说明如何使用原始字符串:
path = r'C:\Users\Username\Desktop\file.txt'
print(path)
输出:
C:\Users\Username\Desktop\file.txt
11
中国
,用正则匹配出标签里面的内容(“中国”),其中class的类名是不确定的
可以使用正则表达式来匹配出 <div>
标签中的内容。以下是一个示例代码:
import re
html = '<div class="nam">中国</div>'
pattern = r'<div\s+class="[^"]*">(.*?)<\/div>'
match = re.search(pattern, html)
if match:
content = match.group(1)
print(content)
输出:
中国
在上述示例中,我们使用了正则表达式模式 <div\s+class="[^"]*">(.*?)<\/div>
来匹配 <div>
标签及其内容。这里的 \s+
表示匹配多个空白字符,[^"]*
表示匹配任意数量的非双引号字符,(.*?)
表示匹配任意字符(非贪婪模式),并将其捕获为分组。
使用 re.search()
函数搜索整个字符串,并返回第一个匹配项。如果找到匹配项,则通过 match.group(1)
提取出分组中的内容(即标签内的文本)。
12 python中断言方法举例
以下是几个使用断言进行条件检查的示例:
- 检查列表是否为空:
def process_data(data):
assert len(data) > 0, "数据不能为空"
# 处理数据的代码
my_data = [] # 空列表
process_data(my_data) # 触发断言异常,抛出 AssertionError
- 检查函数参数是否满足要求:
def divide(a, b):
assert b != 0, "除数不能为零"
return a / b
result = divide(10, 0) # 触发断言异常,抛出 AssertionError
当断言条件不满足时,Python 会抛出 AssertionError 异常。这意味着程序遇到了一个不可接受的情况,并通过抛出异常来表示这个问题。
在示例中,当断言 assert len(data) > 0
的条件不满足(即列表为空)时,会引发 AssertionError 异常。如果没有提供自定义错误消息,则默认的 AssertionError 异常消息为 “AssertionError”。
为了提供更有用的错误信息,可以在断言语句后面添加一个字符串,作为 AssertionError 异常的错误消息。这样,当断言失败时,会打印出该错误消息,以帮助定位问题。
需要注意的是,在正式发布的生产代码中,断言往往会被禁用或移除,以避免性能损失。因此,断言主要用于开发和测试阶段,帮助开发者及时发现和解决问题。
13 数据表student有id,name,score,city字段,其中name中的名字可有重复,需要消除重复行,请写sql语句
假设有以下 student
表:
+----+--------+-------+---------+
| id | name | score | city |
+----+--------+-------+---------+
| 1 | Alice | 85 | London |
| 2 | Bob | 90 | Paris |
| 3 | Alice | 95 | New York|
| 4 | Carol | 88 | London |
| 5 | Bob | 92 | Tokyo |
+----+--------+-------+---------+
为了消除重复行并保留唯一的姓名,可以使用以下 SQL 语句:
DISTINCT 取消重复行;消除重复行;
SELECT DISTINCT name, id, score, city
FROM student;
执行以上 SQL 语句后,将得到以下结果:
+--------+----+-------+---------+
| name | id | score | city |
+--------+----+-------+---------+
| Alice | 1 | 85 | London |
| Bob | 2 | 90 | Paris |
| Alice | 3 | 95 | New York|
| Carol | 4 | 88 | London |
+--------+----+-------+---------+
通过使用 DISTINCT
关键字,我们从 student
表中选择了唯一的姓名,并返回了对应的 id
、score
和 city
列的值。在结果中,重复的姓名被消除,每个姓名只出现一次。这样,就得到了去重后的结果集。
14 python2和python3区别?列举5个
Unicode:不仅包含了 ASCII 中的所有字符,还包括了世界上大部分的文字,例如中文、阿拉伯文、希伯来文等。
Python 2 和 Python 3 之间存在多个差异。以下列出了5个主要的区别:
-
打印:
- Python 2:
print
是一个语句。print "Hello, World!"
- Python 3:
print()
是一个函数。print("Hello, World!")
- Python 2:
-
整数除法:
- Python 2:
/
进行整数除法时,返回的是向下取整后的整数。result = 3/2 # 结果为 1
- Python 3:
/
返回浮点数结果,而//
用于向下取整的整数除法。result = 3/2 # 结果为 1.5 result_floor_div = 3//2 # 结果为 1
- Python 2:
-
Unicode支持:
- Python 2: 字符串默认是 ASCII 编码,要使用 Unicode 字符串需要添加
u
前缀。unicode_string = u"你好"
- Python 3: 字符串默认是 Unicode,而不需要特殊的前缀。
- Python 2: 字符串默认是 ASCII 编码,要使用 Unicode 字符串需要添加
-
xrange 和 range:
- Python 2:
range()
返回一个列表,而xrange()
返回一个生成器来节省内存。 - Python 3:
range()
的行为像 Python 2 中的xrange()
,返回一个不真正创建整个列表的迭代器。
- Python 2:
-
错误处理:
- Python 2: 异常使用
not
和,
来区分。try: # some code except ValueError, e: pass
- Python 3: 使用
as
关键字。try: # some code except ValueError as e: pass
- Python 2: 异常使用
关于第四点的解释
在 Python 2 中,range()
和 xrange()
都是用来生成一系列的数字,但它们的方式和内部实现是不同的。
Python 2
-
range():
在 Python 2 中,range()
实际上会生成一个包含指定范围内的整数的列表。这意味着对于大的范围,它会使用更多的内存。例如:
numbers = range(10) # 结果为 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
-
xrange():
xrange()
则返回一个迭代器,这意味着它不会立即生成所有的数字,而是每次需要下一个数字时才生成。这对于大的范围非常有用,因为它不会消耗大量的内存。例如:
numbers = xrange(10) # 这不会立即生成一个列表,而是返回一个迭代器
Python 3
在 Python 3 中,为了简化和提高效率,xrange()
被移除,而 range()
的行为更像 Python 2 中的 xrange()
。
-
range():
在 Python 3 中,range()
返回一个不真正创建整个列表的迭代器。例如:
numbers = range(10) # 这不会立即生成一个列表,而是返回一个迭代器
总的来说,Python 3 中的 range()
被设计得更为高效,特别是在处理大范围时,因为它只在需要时生成数字,而不是立即生成整个列表。
15 列出python中可变数据类型和不可变数据类型,举例说明,并简述原理
Python 中的数据类型可以分为可变和不可变两种。
不可变数据类型:
不可变数据类型的对象一旦创建,其内容就不能被修改。如果尝试更改它的值,将会返回一个新的对象。
常见的不可变数据类型包括:
-
整数 (int)
x = 5 id_before = id(x) x = x + 1 id_after = id(x) # id_before 和 id_after 将是不同的,因为一个新的整数对象被创建
-
浮点数 (float)
y = 3.14 # 同整数,浮点数也是不可变的
-
字符串 (str)
s = "hello" id_before