python问题笔记

01 python的GIL是什么?

GIL 是 Global Interpreter Lock(全局解释器锁)的缩写,是 Python CPython 解释器(Python 的主流实现)中的一个技术术语。GIL 是一个互斥锁,用于在任何时刻只允许一个线程执行 Python 字节码。这意味着在多线程的环境中,即使在多核心的机器上,只有一个线程在给定的时间内被执行。
想象你有一个咖啡店,而这家咖啡店只有一个咖啡机。GIL 就像这个咖啡店的规矩:一次只允许一个员工使用咖啡机。

  1. 如果有很多顾客来买咖啡,即使你雇佣了多个员工(这里的员工可以想象为线程),由于这个“一次只允许一个员工使用咖啡机”的规矩,其他的员工都必须等待,直到咖啡机空闲下来。这就是为什么即使你有很多线程,但在 CPU 密集型任务上,它们还是一个接一个地执行,而不是同时执行。

  2. 但是,如果员工在为客户做咖啡的时候,需要等待咖啡豆磨成咖啡粉(可以看作 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 中,有多种方法可以实现列表去重。以下是几种常见的方法:

  1. 使用集合(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)。

  1. 列表 (list)

    • 列表是一个有序的元素集合。
    • 列表是可变的,这意味着你可以在创建后修改、添加或删除列表中的元素。
    • 它使用方括号 [] 进行定义。
    • 例如:fruits = ['apple', 'banana', 'cherry']
  2. 元组 (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_Apoint_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:是一个可选的条件语句,用于筛选出满足条件的元素。

示例

  1. 基本的列表推导式

    # 从一个列表中创建一个新的列表
    original_list = [1, 2, 3, 4, 5]
    new_list = [x * 2 for x in original_list]
    # 结果:[2, 4, 6, 8, 10]
    
  2. 带条件的列表推导式

    # 只选择大于2的元素
    original_list = [1, 2, 3, 4, 5]
    filtered_list = [x for x in original_list if x > 2]
    # 结果:[3, 4, 5]
    
  3. 嵌套循环的列表推导式

    # 结合两个列表的所有元素
    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中断言方法举例

以下是几个使用断言进行条件检查的示例:

  1. 检查列表是否为空:
def process_data(data):
    assert len(data) > 0, "数据不能为空"
    # 处理数据的代码

my_data = []  # 空列表
process_data(my_data)  # 触发断言异常,抛出 AssertionError
  1. 检查函数参数是否满足要求:
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 表中选择了唯一的姓名,并返回了对应的 idscorecity 列的值。在结果中,重复的姓名被消除,每个姓名只出现一次。这样,就得到了去重后的结果集。

14 python2和python3区别?列举5个

Unicode:不仅包含了 ASCII 中的所有字符,还包括了世界上大部分的文字,例如中文、阿拉伯文、希伯来文等。
Python 2 和 Python 3 之间存在多个差异。以下列出了5个主要的区别:

  1. 打印:

    • Python 2: print 是一个语句。
      print "Hello, World!"
      
    • Python 3: print() 是一个函数。
      print("Hello, World!")
      
  2. 整数除法:

    • Python 2: / 进行整数除法时,返回的是向下取整后的整数。
      result = 3/2  # 结果为 1
      
    • Python 3: / 返回浮点数结果,而 // 用于向下取整的整数除法。
      result = 3/2  # 结果为 1.5
      result_floor_div = 3//2  # 结果为 1
      
  3. Unicode支持:

    • Python 2: 字符串默认是 ASCII 编码,要使用 Unicode 字符串需要添加 u 前缀。
      unicode_string = u"你好"
      
    • Python 3: 字符串默认是 Unicode,而不需要特殊的前缀。
  4. xrange 和 range:

    • Python 2: range() 返回一个列表,而 xrange() 返回一个生成器来节省内存。
    • Python 3: range() 的行为像 Python 2 中的 xrange(),返回一个不真正创建整个列表的迭代器。
  5. 错误处理:

    • Python 2: 异常使用 not, 来区分。
      try:
          # some code
      except ValueError, e:
          pass
      
    • Python 3: 使用 as 关键字。
      try:
          # some code
      except ValueError as e:
          pass
      

关于第四点的解释

在 Python 2 中,range()xrange() 都是用来生成一系列的数字,但它们的方式和内部实现是不同的。

Python 2

  1. range():
    在 Python 2 中,range() 实际上会生成一个包含指定范围内的整数的列表。这意味着对于大的范围,它会使用更多的内存。

    例如:

    numbers = range(10)  # 结果为 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    
  2. xrange():
    xrange() 则返回一个迭代器,这意味着它不会立即生成所有的数字,而是每次需要下一个数字时才生成。这对于大的范围非常有用,因为它不会消耗大量的内存。

    例如:

    numbers = xrange(10)  # 这不会立即生成一个列表,而是返回一个迭代器
    

Python 3

在 Python 3 中,为了简化和提高效率,xrange() 被移除,而 range() 的行为更像 Python 2 中的 xrange()

  1. range():
    在 Python 3 中,range() 返回一个不真正创建整个列表的迭代器。

    例如:

    numbers = range(10)  # 这不会立即生成一个列表,而是返回一个迭代器
    

总的来说,Python 3 中的 range() 被设计得更为高效,特别是在处理大范围时,因为它只在需要时生成数字,而不是立即生成整个列表。

15 列出python中可变数据类型和不可变数据类型,举例说明,并简述原理

Python 中的数据类型可以分为可变和不可变两种。

不可变数据类型:

不可变数据类型的对象一旦创建,其内容就不能被修改。如果尝试更改它的值,将会返回一个新的对象。

常见的不可变数据类型包括:

  1. 整数 (int)

    x = 5
    id_before = id(x)
    x = x + 1
    id_after = id(x)
    # id_before 和 id_after 将是不同的,因为一个新的整数对象被创建
    
  2. 浮点数 (float)

    y = 3.14
    # 同整数,浮点数也是不可变的
    
  3. 字符串 (str)

    s = "hello"
    id_before 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值