python面试题大全(三)

设计模式

66. 对设计模式的理解,简述你了解的设计模式?

设计模式是在软件设计和开发中经过验证的、可重复使用的解决方案的指导原则。它们提供了一套经验丰富的解决方案,帮助解决常见的设计问题,并促进代码的可读性、可维护性和可扩展性。

以下是一些常见的设计模式:

  1. 创建型模式(Creational Patterns):这些模式关注对象的创建机制,包括简化对象创建、隐藏对象创建的细节、提供灵活性和可配置性等。常见的创建型模式包括工厂模式(Factory Pattern)、抽象工厂模式(Abstract Factory Pattern)、建造者模式(Builder Pattern)、原型模式(Prototype Pattern)和单例模式(Singleton Pattern)。

  2. 结构型模式(Structural Patterns):这些模式关注对象之间的组合和关系,以形成更大的结构。它们涉及类和对象的组合,以实现更大的功能。常见的结构型模式包括适配器模式(Adapter Pattern)、桥接模式(Bridge Pattern)、组合模式(Composite Pattern)、装饰器模式(Decorator Pattern)、外观模式(Facade Pattern)、享元模式(Flyweight Pattern)和代理模式(Proxy Pattern)。

  3. 行为型模式(Behavioral Patterns):这些模式关注对象之间的通信和交互,以及责任的分配和任务的执行。它们涉及到算法、职责和对象之间的交互。常见的行为型模式包括模板方法模式(Template Method Pattern)、策略模式(Strategy Pattern)、观察者模式(Observer Pattern)、迭代器模式(Iterator Pattern)、责任链模式(Chain of Responsibility Pattern)、命令模式(Command Pattern)、备忘录模式(Memento Pattern)、状态模式(State Pattern)、访问者模式(Visitor Pattern)和中介者模式(Mediator Pattern)。

  4. 并发模式(Concurrency Patterns):这些模式关注多线程和并发编程中的问题和解决方案。它们涉及到线程同步、协调和通信等方面。常见的并发模式包括锁模式(Locking Pattern)、并发容器模式(Concurrent Container Pattern)、传输模式(Messaging Pattern)等。

设计模式不是固定的解决方案,而是根据特定的问题和需求进行选择和应用。它们提供了一种共享的设计语言和思维模式,使得开发人员可以更好地理解和沟通设计意图,并以一种可维护和可扩展的方式构建软件系统。

67. 请手写一个单例模式

class SingletonClass:
    _instance = None

    def __new__(cls, value):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            cls._instance.value = value
        return cls._instance

# 使用
instance1 = SingletonClass("Instance 1")
instance2 = SingletonClass("Instance 2")

print(instance1.value)  # Output: Instance 1
print(instance2.value)  # Output: Instance 1 (same as instance1)
print(instance1 is instance2)  # Output: True

68. 单例模式的应用场景有那些?

单例模式的应用场景包括以下情况:

  1. 资源共享:当多个对象需要共享同一个资源时,例如数据库连接池、线程池、日志记录器等,可以使用单例模式确保只有一个实例被创建和共享,避免资源的重复创建和浪费。

  2. 全局配置:当需要在整个应用程序中共享一些全局配置信息时,例如应用程序的设置、系统参数等,可以使用单例模式来管理和访问这些配置信息,确保全局一致性和方便的访问。

  3. 缓存管理:当需要管理全局缓存或缓存池时,例如数据缓存、图片缓存等,可以使用单例模式来管理缓存对象,提供统一的访问接口和缓存策略。

  4. 日志记录:当需要记录应用程序的日志信息时,可以使用单例模式来创建日志记录器对象,确保只有一个记录器实例,并提供统一的接口进行日志记录。

  5. 线程池管理:当需要管理线程池来处理并发任务时,可以使用单例模式来创建和管理线程池对象,确保线程池的唯一性和可控性。

需要注意的是,单例模式在设计和使用时需要慎重考虑,因为它引入了全局状态和共享资源,可能导致代码的复杂性和耦合度增加。因此,在选择使用单例模式时,需要确保它是合适的解决方案,并仔细考虑其对系统的影响和潜在的扩展性问题。

69. 对装饰器的理解,并写出一个计时器记录方法执行性能的装饰器?

装饰器是一种特殊的函数,它可以接受一个函数作为参数,并返回一个新的函数作为结果。装饰器用于在不修改原始函数代码的情况下,增加额外的功能或行为。

装饰器通常用于以下情况:

  1. 添加额外的功能或行为,如日志记录、性能监测、缓存等。
  2. 修改函数的输入、输出或行为,如参数验证、数据转换等。
  3. 重用和组合现有的函数逻辑,以实现代码复用和模块化。

下面是一个计时器装饰器的示例,用于记录方法执行的性能:

import time

def performance_timer(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        execution_time = end_time - start_time
        print(f"方法 {func.__name__} 执行时间:{execution_time} 秒")
        return result
    return wrapper

在上述代码中,定义了一个装饰器函数 performance_timer,它接受一个函数 func 作为参数。装饰器内部定义了一个内部函数 wrapper,用于包裹原始函数 func

wrapper 函数内部,首先记录了函数执行的开始时间 start_time,然后调用原始函数 func,并将其参数传递给它。接着,记录函数执行的结束时间 end_time,计算执行时间 execution_time,并打印出来。最后,返回原始函数 func 的执行结果。

要使用这个装饰器,只需在目标函数上方使用 @performance_timer 注解即可,例如:

@performance_timer
def my_function():
    # 函数逻辑
    pass

my_function()  # 执行带有性能计时的函数

在执行带有性能计时的函数时,装饰器会自动记录函数的执行时间并进行打印。

70. 解释一下什么是闭包?

闭包(Closure)是在编程语言中一种特殊的函数。它是由一个函数及其相关的引用环境组合而成的实体。闭包包含了函数定义时所在的环境中的变量,即使在函数定义之后,它仍然可以访问和操作这些变量。

闭包通常由以下两个特点组成:

  1. 函数嵌套:闭包是由一个函数内部定义的函数所组成。这个内部函数可以访问外部函数的变量和参数。

  2. 变量引用:闭包中的内部函数会引用外部函数的变量,并保留对这些变量的引用,即使外部函数已经执行完毕。这使得闭包具有"记忆"的能力,可以在后续调用中使用之前的状态。

闭包的一个重要应用是创建和返回函数。通过使用闭包,我们可以在运行时动态地创建一个函数,并将其作为返回值。闭包可以捕获和保持函数定义时所在的环境中的变量状态,使得返回的函数可以继续访问和操作这些变量。

闭包在许多编程语言中都有支持和应用。在函数式编程语言中,闭包是一种常见的编程概念,它允许我们编写高阶函数和实现函数柯里化等功能。在其他编程语言中,如Python、JavaScript等,闭包也被广泛使用于回调函数、事件处理等场景中。

总结来说,闭包是由函数及其相关的引用环境组成的实体,它可以访问和操作外部函数的变量,并在函数执行完毕后仍然保持对这些变量的引用。闭包提供了一种方式来创建和返回函数,并且可以在后续调用中保持状态和上下文。

71. 生成器,迭代器的区别?

生成器(Generator)和迭代器(Iterator)是在 Python 中用于处理可迭代对象的概念,它们有一些共同点,但也存在一些区别。

**迭代器(Iterator)**是一个实现了迭代协议的对象。它必须包含两个方法:__iter__()__next__()__iter__() 方法返回迭代器对象自身,而 __next__() 方法返回迭代器中的下一个元素。当迭代器没有元素可供返回时,它会引发 StopIteration 异常。

迭代器的特点包括:

  1. 迭代器可以遍历一个容器(如列表、元组、集合、字典等)中的元素,一个接一个地返回每个元素,而不需要一次性将所有元素存储在内存中。

  2. 迭代器可以使用 for 循环来遍历,也可以使用 next() 函数逐个获取下一个元素。

  3. 迭代器提供了一种惰性计算的方式,只在需要时才计算并返回元素,从而节省了内存和计算资源。

**生成器(Generator)**是一种特殊的迭代器。它是使用函数来定义的,使用 yield 关键字来生成一个值,并在下次迭代时从上次离开的地方继续执行。生成器函数在每次迭代时返回一个值,而不是一次性返回所有值。

生成器的特点包括:

  1. 生成器使用函数定义,通过 yield 语句生成一个值,并在下次迭代时从上次离开的地方继续执行。

  2. 生成器函数可以通过 yield 关键字来暂停执行并产生值,然后再次从暂停的地方继续执行。

  3. 生成器可以使用 for 循环来遍历,也可以通过调用 next() 函数逐个获取下一个值。

  4. 生成器提供了一种简洁、高效的方式来生成序列,特别适合处理大量数据或无限序列。

总结来说,迭代器是一种实现了迭代协议的对象,可以逐个返回元素。生成器是一种特殊的迭代器,使用函数定义,通过 yield 语句逐个生成值。生成器提供了一种惰性计算和高效处理序列的方式,而迭代器则是处理和遍历可迭代对象的通用方式。

72. X是什么类型?

X= (i for i in range(10))

X是 generator类型

73. 请用代码实现将1-N 的整数列表以3为单位分组

def cutter(n):
    grouped_list = [list(range(i, min(i + 3, n + 1))) for i in range(1, n + 1, 3)]
    return grouped_list


print(cutter(10))  # [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]

74. Python中yield的用法?

在Python中,yield 是一个关键字,用于定义生成器函数。生成器函数是一种特殊的函数,它可以暂停执行并返回一个值,然后在下次迭代时从上次离开的地方继续执行。

yield 的用法有两种情况:

  1. 生成器函数中的单个 yield:在生成器函数中,使用 yield 语句可以将一个值生成为生成器的下一个值,并暂停函数的执行。当生成器的下一个值被请求时,函数将从 yield 语句之后的位置继续执行,直到遇到下一个 yield 或函数结束。这样可以逐个产生值,而不是一次性生成整个序列。

    示例:

    def my_generator():
        yield 1
        yield 2
        yield 3
    
    gen = my_generator()
    print(next(gen))  # 输出:1
    print(next(gen))  # 输出:2
    print(next(gen))  # 输出:3
    
  2. yield 表达式的赋值:yield 可以作为表达式使用,并将产生的值赋给一个变量。这样可以实现双向通信,即生成器函数可以从外部接收值,并根据接收到的值来控制生成器的行为。

    示例:

    def counter():
        i = 0
        while True:
            received = yield i
            if received == "stop":
                break
            i += 1
    
    gen = counter()
    print(next(gen))       # 输出:0
    print(gen.send("go"))  # 输出:1
    print(gen.send("go"))  # 输出:2
    print(gen.send("stop"))  # StopIteration 异常
    

在上述示例中,生成器函数 my_generator() 使用 yield 逐个生成值。通过调用 next() 函数来获取生成器的下一个值。

在另一个示例中,生成器函数 counter() 接收外部的值,并根据接收到的值来控制生成器的行为。通过调用 send() 方法将值发送给生成器,并使用 yield 表达式将生成的值返回。

面向对象

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

在Python中,对象分为可变对象(Mutable Object)和不可变对象(Immutable Object)两种类型。这指的是对象在创建后是否可以被修改。

**可变对象(Mutable Object)**是指在创建后可以修改其内容的对象。当对可变对象进行操作时,其内部状态可以改变,但对象的身份(identity)不会发生变化。

Python 中的可变对象包括列表(list)、字典(dictionary)、集合(set)等。例如,对列表进行添加、删除、修改等操作都会直接影响列表的内容。

示例:

my_list = [1, 2, 3]
my_list.append(4)  # 修改了列表的内容
print(my_list)    # 输出:[1, 2, 3, 4]

**不可变对象(Immutable Object)**是指在创建后不可以修改其内容的对象。当对不可变对象进行操作时,必须创建一个新的对象来存储修改后的值,原始对象的身份保持不变。

Python 中的不可变对象包括整数(integer)、浮点数(float)、布尔值(boolean)、字符串(string)、元组(tuple)等。例如,对字符串进行拼接、切片等操作时,会创建一个新的字符串对象。

示例:

my_string = "Hello"
new_string = my_string + " World"  # 创建了一个新的字符串对象
print(new_string)  # 输出:Hello World

需要注意的是,不可变对象并不意味着对象的值不可变,而是指对象在创建后无法被修改,对其进行操作会创建新的对象。而可变对象可以直接修改其内容,不会创建新的对象。

理解对象的可变性对于编写正确的代码非常重要,特别是在涉及函数参数传递、对象赋值、引用传递等情况下。

76. Python的魔法方法

在Python中,魔法方法(Magic methods),也称为特殊方法(Special methods)或双下划线方法(Dunder methods),是以双下划线 (__) 开头和结尾的特殊命名方法。它们用于定义类的行为,可以被Python解释器自动调用,以实现类的特定功能。

以下是一些常用的Python魔法方法及其对应的功能:

  • __init__(self, ...): 构造方法,用于初始化对象。
  • __str__(self): 返回对象的字符串表示。
  • __repr__(self): 返回对象的字符串表示,通常用于调试目的。
  • __len__(self): 返回对象的长度。
  • __getitem__(self, key): 获取指定键的值。
  • __setitem__(self, key, value): 设置指定键的值。
  • __delitem__(self, key): 删除指定键的值。
  • __iter__(self): 返回一个迭代器对象,用于支持迭代操作。
  • __next__(self): 返回迭代器的下一个值。
  • __contains__(self, item): 检查对象是否包含指定元素。
  • __call__(self, ...): 将对象作为函数调用。
  • __eq__(self, other): 定义对象的相等比较操作。
  • __lt__(self, other): 定义对象的小于比较操作。
  • __gt__(self, other): 定义对象的大于比较操作。
  • __add__(self, other): 定义对象的加法操作。
  • __sub__(self, other): 定义对象的减法操作。
  • __mul__(self, other): 定义对象的乘法操作。
  • __div__(self, other): 定义对象的除法操作。
  • __enter__(self): 与 with 语句结合使用,在进入代码块前执行一些操作。
  • __exit__(self, exc_type, exc_val, exc_tb): 与 with 语句结合使用,在退出代码块时执行一些操作。

以上只是一些常见的魔法方法示例,实际上Python还有很多其他的魔法方法,用于定义类的特定行为和操作。魔法方法的使用可以让我们自定义类的行为,使其更符合我们的需求,并与Python内置的语言特性进行交互。

77. 面向对象中怎么实现只读属性?

在面向对象编程中,可以通过以下方法实现只读属性:

  1. 使用属性装饰器(Property Decorator):在类的成员方法上使用 @property 装饰器将其转换为只读属性。这样,外部代码可以读取属性的值,但无法直接修改属性的值。

    示例:

    class MyClass:
        def __init__(self, value):
            self._value = value
    
        @property
        def value(self):
            return self._value
    
    obj = MyClass(10)
    print(obj.value)  # 输出:10
    obj.value = 20    # 抛出 AttributeError: can't set attribute
    

    在上述示例中,value 方法被装饰为只读属性。外部代码可以通过 obj.value 读取属性的值,但无法通过 obj.value = 20 直接修改属性的值。

  2. 使用 property() 函数:可以使用 property() 函数手动创建只读属性,并指定获取属性值的方法。

    示例:

    class MyClass:
        def __init__(self, value):
            self._value = value
    
        def get_value(self):
            return self._value
    
        value = property(get_value)
    
    obj = MyClass(10)
    print(obj.value)  # 输出:10
    obj.value = 20    # 抛出 AttributeError: can't set attribute
    

    在上述示例中,property() 函数用于创建只读属性 value,并将其与 get_value 方法关联。外部代码可以通过 obj.value 读取属性的值,但无法通过 obj.value = 20 直接修改属性的值。

通过以上两种方法,可以实现只读属性,限制外部对属性的修改,从而确保属性的只读性。

78. 谈谈你对面向对象的理解?

面向对象编程(Object-Oriented Programming,OOP)是一种编程范式,它将程序中的数据和对数据的操作封装到对象中,通过对象之间的交互来完成任务。面向对象编程主要基于以下三个核心概念:封装、继承和多态。

1. 封装(Encapsulation): 封装是指将数据和对数据的操作封装到一个对象中,通过定义类来创建对象。类是对象的蓝图,包含了对象的属性(数据)和方法(操作)。封装可以隐藏对象的内部实现细节,使对象对外部代码提供统一的接口,从而提高代码的可维护性和重用性。

2. 继承(Inheritance): 继承是指一个类可以从另一个类继承属性和方法。通过继承,子类可以获取父类的属性和方法,并可以在此基础上进行扩展或修改。继承支持代码的重用和层次化组织,可以建立类之间的关系,如父类和子类的关系,基类和派生类的关系。

3. 多态(Polymorphism): 多态是指同一操作可以在不同的对象上具有不同的行为。多态性允许使用统一的接口来处理不同类型的对象,提高代码的灵活性和可扩展性。多态性可以通过函数重载、运算符重载和方法重写等方式实现。

面向对象编程的优点包括:

  • 模块化和可维护性: 面向对象编程将程序划分为多个对象,每个对象负责特定的功能,使得代码更加模块化和可维护。
  • 代码重用: 继承和多态性提供了代码的重用机制,可以通过扩展或修改现有类来创建新的类,减少冗余代码的编写。
  • 灵活性和扩展性: 多态性和封装性使得程序更加灵活,可以通过替换对象的实现来改变程序的行为,同时也方便扩展和修改代码。
  • 可靠性和安全性: 面向对象编程通过封装和隐藏实现细节,提供了更高的安全性和可靠性,防止外部代码直接访问和修改对象的内部状态。

总之,面向对象编程通过封装、继承和多态这些核心概念,提供了一种结构化、模块化和可扩展的编程方法,使得代码更加灵活、可维护和可重用。它是现代软件开发中广泛应用的一种编程范式。

正则表达式

79. a = “abbbccc”,用正则匹配为abccc,不管有多少b,就出现一次?

import re
a = "abbbccc"

print(re.sub(r'b+', 'b', a))  # abccc

80. Python字符串查找和替换?

在Python中,你可以使用字符串的内置方法和正则表达式模块 re 来进行字符串的查找和替换操作。

字符串的查找操作:

  1. find(substring[, start[, end]]): 在字符串中查找子串 substring,返回第一个匹配的子串的起始索引,如果未找到则返回 -1。可选参数 startend 指定查找的起始和结束位置。

    s = "Hello, World!"
    index = s.find("World")
    print(index)  # 输出:7
    
  2. index(substring[, start[, end]]): 在字符串中查找子串 substring,返回第一个匹配的子串的起始索引,如果未找到则抛出 ValueError 异常。可选参数 startend 指定查找的起始和结束位置。

    s = "Hello, World!"
    index = s.index("World")
    print(index)  # 输出:7
    
  3. count(substring[, start[, end]]): 统计子串 substring 在字符串中出现的次数,可选参数 startend 指定统计的起始和结束位置。

    s = "Hello, World!"
    count = s.count("o")
    print(count)  # 输出:2
    

字符串的替换操作:

  1. replace(old, new[, count]): 将字符串中的 old 子串替换为 new 子串,可选参数 count 指定替换的次数。

    s = "Hello, World!"
    new_string = s.replace("World", "Python")
    print(new_string)  # 输出:Hello, Python!
    

使用正则表达式进行字符串的查找和替换:

如果需要更复杂的模式匹配和替换操作,可以使用正则表达式模块 re

import re

s = "Hello, World!"
pattern = r"o\w+"  # 匹配以字母 "o" 开头的单词
matches = re.findall(pattern, s)
print(matches)  # 输出:['orl']

new_string = re.sub(pattern, "Python", s)
print(new_string)  # 输出:HellPythong, WPythond!

在上述示例中,re.findall() 函数使用正则表达式 pattern 在字符串 s 中查找所有匹配的子串,并返回一个列表。re.sub() 函数使用正则表达式 pattern 将匹配到的子串替换为指定的字符串。

这些方法提供了在字符串中查找和替换的常用功能,你可以根据具体需求选择合适的方法进行操作。

81. python re模块的常用方法

Python的re模块提供了一组用于正则表达式匹配和操作的方法。以下是re模块中常用的方法:

  1. re.match(pattern, string, flags=0): 从字符串的开头开始匹配正则表达式 pattern,如果匹配成功,则返回一个匹配对象;否则返回None。

  2. re.search(pattern, string, flags=0): 在字符串中搜索匹配正则表达式 pattern 的第一个位置,如果匹配成功,则返回一个匹配对象;否则返回None。

  3. re.findall(pattern, string, flags=0): 在字符串中查找匹配正则表达式 pattern 的所有非重叠的子串,并返回一个包含所有匹配子串的列表。

  4. re.finditer(pattern, string, flags=0): 在字符串中查找匹配正则表达式 pattern 的所有非重叠的子串,并返回一个迭代器,每个迭代项都是一个匹配对象。

  5. re.sub(pattern, repl, string, count=0, flags=0): 使用正则表达式 pattern 在字符串中查找匹配的子串,并将其替换为指定的字符串 repl。可选参数 count 指定替换的次数。

  6. re.split(pattern, string, maxsplit=0, flags=0): 使用正则表达式 pattern 对字符串进行分割,并返回一个包含分割后子串的列表。可选参数 maxsplit 指定最大分割次数。

  7. re.compile(pattern, flags=0): 将正则表达式 pattern 编译为一个正则表达式对象,可以重复使用该对象进行匹配操作,提高效率。

这些是re模块中最常用的方法,可以满足大部分正则表达式的需求。此外,还有一些其他方法和常用的正则表达式语法,如字符类、量词、分组、边界匹配等,可以根据具体需求进行学习和使用。

82. 用Python匹配HTML tag的时候,<.> 和 <.?> 有什么区别

在正则表达式中,. 是一个特殊字符,表示匹配除换行符以外的任意字符。当你想匹配实际的字符.时,需要使用转义字符 \ 来取消.的特殊含义。

对于匹配HTML标签的情况,<.><.?> 的区别如下:

  • <.>: 这个正则表达式会匹配任意一个字符,包括 <>、空格、字母、数字等等。它会匹配包括HTML标签在内的任何字符。它并不会对标签的结构或语义进行验证,只是简单地匹配任意字符。例如,<.> 可以匹配

    等标签。

  • <.?>: 这个正则表达式使用了量词 ?,它表示匹配前面的字符零次或一次。因此,<.?> 会匹配一个字符或空字符串。这个表达式用于匹配可能为空的HTML标签,即标签可能没有任何内容,例如


    等。

需要注意的是,正则表达式无法完全正确地解析和验证HTML或其他复杂的标记语言。HTML标签的语法和结构非常复杂,包含嵌套、属性等各种元素。对于HTML的解析和处理,更推荐使用专门的HTML解析库,如Beautiful Soup,它可以更有效地处理HTML文档,并提供更多的功能和灵活性。

83. 正则表达式贪婪与非贪婪模式的区别?

正则表达式的贪婪(greedy)和非贪婪(non-greedy)模式涉及到量词的匹配方式。量词用于指定模式中前面的字符或子表达式的出现次数。

在贪婪模式下,量词会尽可能多地匹配字符,而在非贪婪模式下,量词会尽可能少地匹配字符。

以下是贪婪模式和非贪婪模式的示例:

import re

s = "abbbccc"

# 贪婪模式
result_greedy = re.findall(r"ab+", s)
print(result_greedy)  # 输出: ['abbb']

# 非贪婪模式
result_non_greedy = re.findall(r"ab+?", s)
print(result_non_greedy)  # 输出: ['ab']

在上述示例中,正则表达式 ab+ 匹配以 “a” 开头,后面跟着一个或多个 “b” 的字符串。在贪婪模式下,使用 + 量词,它会尽可能多地匹配 “b”,因此结果是 ['abbb']。而在非贪婪模式下,使用 +? 量词,它会尽可能少地匹配 “b”,因此结果是 ['ab']

贪婪模式是正则表达式的默认行为,它会尽量匹配更多的字符。如果想要在量词后面加上 ?,可以将其转换为非贪婪模式。

需要注意的是,在某些情况下,贪婪模式和非贪婪模式的结果可能会有所不同,特别是在使用复杂的正则表达式时。因此,在编写正则表达式时,根据具体需求选择相应的模式以确保获得预期的结果。

84. 写出开头匹配字母和下划线,末尾是数字的正则表达式?

要匹配以字母和下划线开头,末尾是数字的字符串,可以使用以下正则表达式:

import re

pattern = r'^[a-zA-Z_]\w*\d$'

# 测试字符串
strings = ['abc123', '_test456', 'Abc_', '123', 'abc_', 'test123_']

for string in strings:
    if re.match(pattern, string):
        print(f"Matched: {string}")
    else:
        print(f"Not matched: {string}")

"""
Matched: abc123
Matched: _test456
Not matched: Abc_
Not matched: 123
Not matched: abc_
Not matched: test123_
"""

在上面的示例中,正则表达式 ^[a-zA-Z_]\w*\d$ 匹配以下模式:

  • ^: 字符串的开头
  • [a-zA-Z_]: 以字母(大小写均可)或下划线开头
  • \w*: 匹配零个或多个字母、数字或下划线
  • \d: 匹配一个数字
  • $: 字符串的结尾

因此,该正则表达式可以匹配类似于 “abc123”、“test456" 这样的字符串,但不匹配 "Abc”、“123”、“abc_” 或 “test123_” 这样不符合要求的字符串。

注意,如果要匹配多行文本中每一行的开头和结尾,可以使用 re.MULTILINE 标志。例如:re.match(pattern, string, re.MULTILINE)

85. 怎么过滤评论中的表情?

要过滤评论中的表情符号,可以使用正则表达式来匹配和移除这些符号。表情符号通常是一些特定的Unicode字符或字符序列。

以下是一个示例,展示如何使用正则表达式过滤评论中的表情符号:

import re

def remove_emojis(text):
    # 匹配表情符号的正则表达式
    emoji_pattern = re.compile("["
                           u"\U0001F600-\U0001F64F"  # emoticons
                           u"\U0001F300-\U0001F5FF"  # symbols & pictographs
                           u"\U0001F680-\U0001F6FF"  # transport & map symbols
                           u"\U0001F1E0-\U0001F1FF"  # flags (iOS)
                           u"\U00002702-\U000027B0"
                           u"\U000024C2-\U0001F251"
                           "]+", flags=re.UNICODE)
    # 移除表情符号
    return emoji_pattern.sub(r'', text)

# 测试评论
comment = "I love this movie!  It's so amazing! ❤️"

# 移除表情符号
filtered_comment = remove_emojis(comment)

print(filtered_comment)  # 输出: I love this movie! It's so amazing!

在上面的示例中,remove_emojis 函数使用了一个正则表达式 emoji_pattern 来匹配并移除评论中的表情符号。该正则表达式利用了Unicode字符范围来匹配常见的表情符号。通过使用 re.compile 函数创建正则表达式对象,并使用 re.UNICODE 标志来支持Unicode匹配。

然后,使用 emoji_pattern.sub(r'', text) 将匹配到的表情符号替换为空字符串,实现了表情符号的过滤。

请注意,这只是一个简单的示例,用于演示如何过滤表情符号。具体的表情符号范围和需求可能因应用场景而异。你可以根据需要自定义正则表达式来匹配和过滤特定的表情符号。

86. 简述Python里面search和match的区别

在Python中,searchmatch 是正则表达式模块 re 提供的两个函数,用于在字符串中进行模式匹配。

区别如下:

  1. search 函数:re.search(pattern, string) 在给定的字符串中搜索第一个匹配给定模式的位置。它会从字符串的任意位置开始搜索,只要找到第一个匹配项,就会返回匹配对象。如果没有找到匹配项,则返回 None

    示例:

    import re
    
    pattern = r"apple"
    string = "I have an apple and a banana"
    match = re.search(pattern, string)
    
    if match:
        print("Match found at index", match.start())
    else:
        print("No match found")
    

    输出:

    Match found at index 9
    
  2. match 函数:re.match(pattern, string) 从字符串的开头开始尝试匹配给定模式。它只在字符串的开头进行匹配,如果模式与字符串开头不匹配,则返回 None。如果模式与字符串开头匹配,则返回匹配对象。

    示例:

    import re
    
    pattern = r"apple"
    string = "I have an apple and a banana"
    match = re.match(pattern, string)
    
    if match:
        print("Match found at index", match.start())
    else:
        print("No match found")
    

    输出:

    No match found
    

总结:

  • search 会在整个字符串中搜索第一个匹配项,而 match 只会在字符串开头进行匹配。
  • search 返回第一个匹配项的匹配对象,而 match 只有在模式与字符串开头匹配时才返回匹配对象。
  • 如果你只关心字符串是否与模式匹配,并且不需要了解匹配的具体位置,可以使用 match 函数。如果你想找到第一个匹配项的位置或获取匹配的详细信息,可以使用 search 函数。

需要注意的是,searchmatch 函数都只返回第一个匹配项。如果你需要找到所有的匹配项,可以使用 findall 函数。

系统编程

87. 谈谈你对多进程,多线程,以及协程的理解,项目是否用?

  • 进程:一个运行的程序(代码)就是一个进程,没有运行的代码叫程序,进程是系统资源分配的最小单 位,进程拥有自己独立的内存空间,所有进程间数据不共享,开销大。
  • 线程: cpu调度执行的最小单位,也叫执行路径,不能独立存在,依赖进程存在,一个进程至少有一个线程,叫主线程,而多个线程共享内存(数据共享,共享全局变量),从而极大地提高了程序的运行效率。
  • 协程: 是一种用户态的轻量级线程,协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。协程调度时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和 栈,直接操中栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。

多进程、多线程和协程是并发编程的三种常见方式,每种方式都有其特点和适用场景。

  1. 多进程:多进程是通过创建多个独立的进程来实现并发的编程方式。每个进程都有自己独立的内存空间和系统资源,它们之间相互独立运行。多进程可以实现真正的并行处理,适用于执行计算密集型任务,如图像处理、数据分析等。Python中的multiprocessing模块提供了创建和管理多进程的功能。

  2. 多线程:多线程是在同一个进程中创建多个线程来实现并发的编程方式。多个线程共享同一个进程的内存空间和系统资源,它们可以并发执行,但由于全局解释器锁(GIL)的存在,多线程在Python中不能实现真正的并行处理。多线程适用于I/O密集型任务,如网络请求、文件读写等。Python中的threading模块提供了创建和管理多线程的功能。

  3. 协程:协程是一种轻量级的并发编程方式,通过协作而非抢占式的方式实现任务的切换。协程可以在同一个线程内实现多个任务间的切换,通过避免线程切换的开销,提高了并发性能。协程适用于I/O密集型任务,如异步网络请求、高并发服务器等。Python中的asyncio模块提供了协程编程的支持。

至于在项目中是否使用多进程、多线程或协程,需要根据具体的需求和场景来决定:

  • 如果项目中有大量的计算密集型任务,并且希望实现真正的并行处理,可以考虑使用多进程。
  • 如果项目中主要是I/O密集型任务,例如网络请求、数据库访问等,可以考虑使用多线程或协程。如果需要使用Python自带的异步IO框架,可以选择协程(asyncio);如果不需要使用异步IO框架,且对并发性能要求不高,可以选择多线程。
  • 如果项目中需要高并发、高吞吐量的异步任务处理,可以选择协程(asyncio),结合异步IO框架进行开发。

综上所述,选择多进程、多线程还是协程,取决于项目的需求、任务类型、性能要求以及开发团队的经验和技术栈。

88. Python异步使用场景有那些?

Python中的异步编程(asynchronous programming)在以下几个场景中非常有用:

  1. 网络编程:异步编程在处理网络通信时非常常见。通过使用异步IO框架(如asyncio),可以实现高性能的异步网络请求、服务器和客户端。

  2. Web开发:异步编程在处理Web请求时能够提供更好的性能和可伸缩性。通过使用异步Web框架(如aiohttp)或异步数据库驱动程序,可以实现高并发的异步Web应用程序。

  3. 数据库访问:当需要与数据库进行交互时,异步编程可以提高性能。通过使用异步数据库驱动程序(如aiomysqlaiopg),可以在执行数据库查询时同时处理其他任务,提高系统的吞吐量。

  4. 并发任务处理:异步编程可以方便地处理并发任务,例如批量数据处理、并行计算等。通过将任务异步化,可以充分利用系统资源,提高任务执行效率。

  5. 高性能爬虫:异步编程在网络爬虫中非常有用。通过使用异步的HTTP请求和处理方式,可以实现高效的异步爬取和解析页面的功能。

  6. 实时数据处理:异步编程在实时数据处理和流式处理中非常有用。通过使用异步编程模型,可以实时地处理数据流,例如实时分析、实时推送等。

需要注意的是,异步编程适用于那些涉及到I/O密集型任务的场景,而对于计算密集型任务,由于Python的全局解释器锁(GIL)限制,异步编程的性能提升有限。因此,在选择是否使用异步编程时,需要根据具体的应用场景和任务类型进行权衡和评估。

89. 多线程共同操作同一个数据互斥锁同步?

import threading
import time


class MyThread(threading.Thread):
    def run(self):
        global num
        time.sleep(1)
        if mutex.acquire(1):
            num += 1
            msg = self.name + ' set num to ' + str(num)
            print(msg)
            mutex.release()


num = 0
mutex = threading.Lock()


def test():
    threads = []
    for _ in range(5):
        t = MyThread()
        threads.append(t)
        t.start()

    for t in threads:
        t.join()


if __name__ == "__main__":
    test()
    print(num)
    """
    Thread-1 set num to 1
    Thread-3 set num to 2
    Thread-5 set num to 3
    Thread-2 set num to 4
    Thread-4 set num to 5
    5
    """

90. 什么是多线程竞争?

多线程竞争(threading competition)是指多个线程同时访问和修改共享资源时可能发生的不确定性和冲突。

在多线程环境中,当多个线程同时访问和修改共享资源(如全局变量、共享数据结构、文件等)时,由于线程的执行是并发的,可能会出现以下问题:

  1. 竞争条件(Race Condition):多个线程对同一个共享资源进行读写操作,由于执行顺序不确定,可能导致结果的不一致性或错误。例如,两个线程同时对一个变量进行自增操作时,由于执行顺序不确定,可能导致结果不符合预期。

  2. 数据不一致:当多个线程同时对共享数据进行修改时,可能会导致数据的不一致性。例如,一个线程正在读取共享数据时,另一个线程修改了该数据,导致读取到的数据与期望不符。

  3. 死锁(Deadlock):多个线程因为竞争资源而相互等待,导致程序无法继续执行。当每个线程都持有一部分资源,并且等待其他线程释放它所需要的资源时,就可能发生死锁。

  4. 饥饿(Starvation):某个线程由于竞争资源的不公平分配,无法获得所需的资源而一直无法执行。

为避免多线程竞争带来的问题,需要采取合适的线程同步机制,例如互斥锁(Mutex)、信号量(Semaphore)、条件变量(Condition)等,来保证多个线程之间对共享资源的安全访问和操作。

同时,编写线程安全的代码也是避免多线程竞争问题的重要手段。线程安全的代码能够正确处理多个线程同时访问和修改共享资源的情况,确保程序的正确性和稳定性。

91. 请介绍一下Python的线程同步?

在 Python 中,线程同步是通过不同的同步机制来确保多个线程之间对共享资源的安全访问和操作。下面是 Python 中常用的线程同步机制:

  1. 互斥锁(Mutex):互斥锁是最基本的线程同步机制,用于保护共享资源的访问。在任意时刻,只允许一个线程持有互斥锁,其他线程必须等待锁的释放才能访问共享资源。Python 中的 threading.Lock() 类提供了互斥锁的实现。

  2. 信号量(Semaphore):信号量用于控制对共享资源的并发访问数量。它维护一个计数器,指示可同时访问资源的线程数量。当计数器为正时,线程可以访问资源;当计数器为零时,线程需要等待。Python 中的 threading.Semaphore() 类提供了信号量的实现。

  3. 条件变量(Condition):条件变量用于线程之间的通信和协作。它提供了一种机制,使得一个线程可以等待某个条件达成后再继续执行。条件变量通常与互斥锁一起使用,通过调用 wait() 等待条件,notify()notifyAll() 发送条件变化的通知。Python 中的 threading.Condition() 类提供了条件变量的实现。

  4. 事件(Event):事件是一种线程同步机制,用于线程之间的通信。它基于一个内部标志,当标志为真时,等待事件的线程可以继续执行;当标志为假时,线程需要等待。Python 中的 threading.Event() 类提供了事件的实现。

  5. 屏障(Barrier):屏障用于线程之间的同步,它要求一组线程在达到某个点之前都必须等待。当所有线程都到达屏障点时,屏障打开,所有线程可以继续执行。Python 中的 threading.Barrier() 类提供了屏障的实现。

这些线程同步机制可以根据具体的需求选择和使用。在编写多线程代码时,正确地使用线程同步机制可以避免竞争条件、数据不一致、死锁等多线程问题,并确保线程安全的访问和操作共享资源。

92. 何为守护线程?有什么使用场景?

在 Python 中,守护线程(daemon thread)是一种特殊类型的线程。与常规线程(也称为前台线程或用户线程)不同,守护线程的生命周期与主线程(或其他非守护线程)的生命周期无关,即当所有非守护线程结束时,守护线程会自动退出。

具体来说,当一个 Python 程序启动时,默认情况下会有一个主线程在运行。如果在主线程中创建了一个守护线程,当主线程结束时,无论守护线程是否完成,守护线程会被强制终止并立即退出。

守护线程的主要特点包括:

  1. 生命周期不受控制:守护线程的生命周期与主线程无关,它会在主线程结束时自动退出。因此,守护线程通常用于执行一些后台任务,不需要等待其完成。

  2. 不能持有关键资源:由于守护线程的突然终止,它们不能持有关键资源(如文件、数据库连接等)。因为在守护线程被强制终止时,可能无法正确释放这些资源。

  3. 不保证执行完整性:由于守护线程会在任何时候被终止,因此无法保证守护线程的任务执行完整性。如果需要确保任务的完整执行,应该使用常规线程。

守护线程在某些情况下非常有用,例如:

  • 后台任务:当需要在后台执行一些不需要等待的任务时,可以使用守护线程。例如,日志记录、定时任务等。

  • 辅助线程:当需要运行一些辅助性的线程,但不希望这些线程阻止程序的正常退出时,可以将它们设置为守护线程。

要将线程设置为守护线程,可以使用 setDaemon(True) 方法或在创建线程时设置 daemon=True。例如:

import threading
import time

def daemon_thread():
    while True:
        print("Daemon thread is running...")
        time.sleep(1)

# 创建守护线程
t = threading.Thread(target=daemon_thread)
t.setDaemon(True)

# 启动守护线程
t.start()

# 主线程继续执行
print("Main thread is done.")

在上面的示例中,daemon_thread 函数是一个无限循环的守护线程。当主线程结束后,守护线程会立即退出。

93. 多线程的join函数有啥用?

在多线程编程中,join() 函数是用于等待线程结束并回收资源的方法。当一个线程调用另一个线程的 join() 方法时,它会被阻塞,直到被调用的线程执行完成。

join() 函数主要有以下作用:

  1. 等待线程完成:通过调用 join() 方法,可以确保主线程(或其他线程)在子线程执行完成之前暂停等待。这对于需要等待子线程执行完毕后再执行后续操作的场景非常有用。

  2. 合并线程执行流程:通过在主线程中调用 join() 方法,可以将线程的执行流程合并到主线程中,确保线程的执行顺序和预期一致。

  3. 资源回收:在线程结束后,调用 join() 方法可以进行资源的回收,避免出现线程的僵尸状态(zombie thread),确保线程的正确退出。

下面是一个简单的示例,演示了如何使用 join() 方法等待线程的结束:

import threading

def worker():
    print("Worker thread started")
    # 模拟耗时操作
    for i in range(5):
        print("Working...")
        time.sleep(1)
    print("Worker thread finished")

# 创建线程
t = threading.Thread(target=worker)

# 启动线程
t.start()

# 主线程等待线程结束
t.join()

print("Main thread finished")

在上述示例中,worker() 函数是一个耗时的工作线程。主线程创建并启动了该线程,然后调用 join() 方法等待线程的结束。当工作线程执行完毕后,主线程才会继续执行后续操作。

需要注意的是,join() 方法可以带有一个可选的超时参数,用于设置最长等待时间。如果超时时间到达而线程仍未结束,join() 方法会返回,主线程可以继续执行其他操作。

总之,join() 方法是用于等待线程结束并回收资源的重要方法,确保线程的正确执行顺序和资源的正确回收。

94. 介绍下有互斥锁,可重入锁,死锁

当多个线程同时访问共享资源时,可能会导致数据不一致或竞争条件的问题。为了保证线程安全和数据一致性,可以使用不同的锁机制,包括互斥锁、可重入锁以及避免死锁。

  1. 互斥锁(Mutex Lock):
    互斥锁是最基本的线程同步机制之一。它提供了一种机制,确保在任何时刻只有一个线程可以进入被保护的临界区,从而避免竞争条件。当一个线程获得互斥锁后,其他线程必须等待锁的释放才能进入临界区。在 Python 中,可以使用 threading.Lock() 类来创建互斥锁。

  2. 可重入锁(Reentrant Lock):
    可重入锁是互斥锁的一种扩展,也称为递归锁。它允许同一个线程多次获得同一个锁,并且每次获得锁后必须相应地释放锁。可重入锁主要用于解决同一线程内部的嵌套锁定问题,确保线程在嵌套调用中能够正确地管理锁的获取和释放。在 Python 中,可以使用 threading.RLock() 类来创建可重入锁。

  3. 死锁(Deadlock):
    死锁是多线程编程中常见的问题,它发生在两个或多个线程相互等待对方持有的资源时。当多个线程都在等待某个资源被释放,而又无法继续执行时,就会发生死锁。死锁可能导致程序无法继续执行,成为严重的问题。避免死锁的常见方法包括避免循环等待、按照固定顺序获取锁、设置超时等待等。

例如,下面是一个简单的示例,演示了互斥锁的使用和死锁的情况:

import threading

# 创建互斥锁
lock_a = threading.Lock()
lock_b = threading.Lock()

def thread_a():
    lock_a.acquire()
    print("Thread A acquired lock A")
    # 模拟一段耗时操作
    threading.sleep(1)

    lock_b.acquire()
    print("Thread A acquired lock B")
    lock_b.release()

    lock_a.release()

def thread_b():
    lock_b.acquire()
    print("Thread B acquired lock B")
    # 模拟一段耗时操作
    threading.sleep(1)

    lock_a.acquire()
    print("Thread B acquired lock A")
    lock_a.release()

    lock_b.release()

# 创建并启动线程
t1 = threading.Thread(target=thread_a)
t2 = threading.Thread(target=thread_b)
t1.start()
t2.start()

# 等待线程执行完毕
t1.join()
t2.join()

print("Main thread finished")

在上述示例中,thread_athread_b 是两个线程函数,它们分别尝试获取 lock_alock_b。由于两个线程都在等待对方持有的资源,因此会发生死锁,导致程序无法继续执行。

为了避免死锁,可以通过调整锁的获取顺序或使用超时等待来解决。在实际开发中,正确地管理锁的获取和释放顺序、避免多个线程出现循环等待是避免死锁的重要策略。

95. 多线程交互访问数据,如果访问到了就不访问了?

如果多个线程需要交互地访问某个共享数据,并且一旦某个线程访问到数据后,其他线程就不再访问该数据,可以使用互斥锁(Mutex Lock)来实现。

互斥锁可以确保在任何时刻只有一个线程可以进入被保护的临界区。当一个线程成功获取到互斥锁后,其他线程必须等待锁的释放才能进入临界区。因此,可以利用互斥锁来控制多个线程对共享数据的访问。

下面是一个示例代码,演示了如何使用互斥锁来实现多线程交互访问数据,一旦某个线程访问到数据后,其他线程就不再访问该数据:

import threading

# 共享数据
shared_data = None

# 创建互斥锁
lock = threading.Lock()

def thread_func():
    global shared_data

    # 尝试获取互斥锁
    if lock.acquire():
        # 检查是否已经访问到数据
        if shared_data is None:
            # 模拟访问数据的操作
            shared_data = "Data"
            print("Thread {} accessed shared data: {}".format(threading.current_thread().name, shared_data))

        # 释放互斥锁
        lock.release()

# 创建并启动多个线程
threads = []
for i in range(5):
    t = threading.Thread(target=thread_func)
    threads.append(t)
    t.start()

# 等待所有线程执行完毕
for t in threads:
    t.join()

print("Main thread finished")

在上述示例中,shared_data 是一个共享的数据变量,多个线程通过互斥锁 lock 来控制对该变量的访问。每个线程在尝试获取互斥锁后,首先检查共享数据是否已经被访问到。如果数据尚未访问到(shared_dataNone),则该线程可以访问数据并更新它。一旦一个线程成功访问到数据,其他线程就不再访问该数据。

需要注意的是,在实际应用中,对共享数据的访问可能涉及更复杂的操作,可能需要更多的线程同步机制和数据结构来保证正确性和一致性。此外,还需要考虑线程安全和竞争条件等并发编程的相关问题。

96. 什么是线程安全,什么是互斥锁?

线程安全(Thread Safety)是指在多线程环境中,对共享数据的访问操作能够保证正确性和一致性的特性。具体来说,当多个线程同时访问共享数据时,线程安全的代码能够正确处理并发访问的情况,保证数据不会被破坏或产生不一致的结果。

线程安全的代码可以同时被多个线程调用而不会导致数据冲突、竞争条件或其他并发问题。它能够确保共享数据的访问和修改操作是正确的、可预测的,并且不会导致数据损坏或程序崩溃。

互斥锁(Mutex Lock)是一种常见的线程同步机制,用于保护共享资源的访问。它提供了一种互斥的机制,确保在任何时刻只有一个线程可以进入被保护的临界区。当一个线程成功获取到互斥锁后,其他线程必须等待锁的释放才能进入临界区。

互斥锁的作用是防止多个线程同时访问共享资源,从而避免并发访问导致的数据不一致或竞争条件的问题。通过在关键代码段前后加上互斥锁的获取和释放操作,可以确保在任何时刻只有一个线程能够访问共享资源,从而保证线程安全。

互斥锁的典型用法是在多线程环境中保护共享数据的读写操作。当某个线程需要访问共享数据时,首先尝试获取互斥锁,如果获取成功则执行操作,执行完毕后释放互斥锁。其他线程在获取互斥锁之前会被阻塞,直到互斥锁被释放。

需要注意的是,互斥锁只能保证线程安全的访问,但并不能解决所有并发问题。在并发编程中,还需要考虑其他线程同步机制、数据结构的选择和设计,以及避免竞争条件、死锁等问题,以确保程序的正确性和性能。

97. 说说下面几个概念:同步,异步,阻塞,非阻塞?

以下是对同步、异步、阻塞和非阻塞的简要解释:

  1. 同步(Synchronous):
    同步是指线程或进程之间的操作按照顺序依次执行,一个操作完成后才能进行下一个操作。在同步模式中,调用方需要等待被调用方完成任务后才能继续执行。同步通常用于简化编程模型和处理顺序依赖性。

  2. 异步(Asynchronous):
    异步是指线程或进程之间的操作相互独立,可以同时执行,不需要等待前一个操作完成。在异步模式中,调用方发起一个操作后,可以继续执行其他任务,而不必等待被调用方完成。异步通常用于提高系统的并发性和响应性,以及处理耗时操作。

  3. 阻塞(Blocking):
    阻塞是指线程或进程在执行操作时,如果不能继续执行,就会暂停等待,直到满足某个条件才能继续执行。在阻塞模式下,线程会一直等待,直到获取到需要的资源或事件发生。阻塞通常会导致线程挂起,无法执行其他任务。

  4. 非阻塞(Non-blocking):
    非阻塞是指线程或进程在执行操作时,如果不能继续执行,不会等待,而是立即返回一个状态或错误码,让调用方可以继续执行其他任务。在非阻塞模式下,线程会尽可能地继续执行其他任务,而不会等待资源或事件的发生。非阻塞通常需要通过轮询或回调等方式来获取操作完成的通知。

需要注意的是,同步和异步是描述操作之间的关系,而阻塞和非阻塞是描述线程或进程在执行操作时的状态。

在实际编程中,可以根据需求选择合适的同步或异步模式,以及阻塞或非阻塞方式,来满足系统的要求和性能需求。

98. 什么是僵尸进程和孤儿进程?怎么避免僵尸进程?

僵尸进程(Zombie Process)和孤儿进程(Orphan Process)是与进程状态相关的两个概念。

  1. 僵尸进程:
    当一个子进程结束运行,但其父进程还没有调用 wait() 或类似的系统调用来获取子进程的退出状态时,子进程会变成僵尸进程。僵尸进程在系统中保留了一些资源(如进程表项),但不再执行任何代码。

僵尸进程的存在通常是由于父进程没有及时处理子进程的退出状态。如果系统中存在大量僵尸进程,可能会耗尽系统资源。因此,及时清理僵尸进程是很重要的。

  1. 孤儿进程:
    孤儿进程是指父进程先于子进程退出,而子进程仍在运行的情况下产生的进程。孤儿进程的父进程 ID 变为 1,即 init 进程(在 Unix-like 系统中)。

孤儿进程通常由于父进程异常退出或者父进程没有等待子进程结束而产生。孤儿进程会被 init 进程接管,并由 init 进程负责清理资源和终止它们的执行。

避免僵尸进程的常用方法是父进程调用 wait() 系统调用或者相关的函数来获取子进程的退出状态。这样可以使得子进程的资源被及时清理。另外,还可以使用以下方法来避免僵尸进程的产生:

  1. 使用 waitpid() 或类似的系统调用,设置 WNOHANG 标志,非阻塞地检查子进程的退出状态。
  2. 在父进程中注册 SIGCHLD 信号处理函数,当子进程退出时,通过信号处理函数获取子进程的退出状态。
  3. 使用双向管道(Bidirectional Pipe)或信号量(Semaphore)等进程间通信机制,让父进程和子进程之间进行必要的同步和通信,以确保父进程能够及时处理子进程的退出状态。

通过合理的进程管理,及时处理子进程的退出状态,可以避免僵尸进程的产生,并保持系统的正常运行。

99. python中进程与线程的使用场景?

在 Python 中,进程(Process)和线程(Thread)都可以用于并发执行任务,但它们的使用场景略有不同。

  1. 进程的使用场景:
  • 并行计算:如果需要利用多核处理器的能力,并行执行计算密集型任务,可以使用多进程来实现。每个进程都有自己独立的内存空间,可以充分利用多核处理器的优势进行并行计算。
  • 外部命令调用:如果需要调用外部命令或执行独立的子任务,可以使用 subprocess 模块创建子进程并执行命令。每个子进程在独立的进程空间中执行,可以并行地执行多个命令。

进程之间的通信可以使用进程间通信(Inter-Process Communication,IPC)机制,如管道、共享内存、消息队列等。

  1. 线程的使用场景:
  • 并发 I/O 操作:如果需要同时处理多个 I/O 密集型任务(如网络请求、文件读写),可以使用多线程来实现。线程可以在同一个进程内共享相同的内存空间,通过并发执行,提高 I/O 操作的效率。
  • 异步编程:使用协程和异步框架(如 asyncio)可以实现非阻塞的异步编程模型,充分利用单线程处理大量并发任务。这种方式可以提高并发性和响应性,适用于高并发的网络服务器、Web 应用等场景。

线程之间的通信可以使用线程间通信(Inter-Thread Communication)机制,如锁(Lock)、条件变量(Condition)、队列(Queue)等。

需要注意的是,Python 中的全局解释器锁(Global Interpreter Lock,GIL)限制了同一进程中的多个线程只能在同一时间点执行一条 Python 字节码指令,因此多线程并不能充分利用多核处理器的能力。在 Python 中,多进程通常比多线程更适合用于 CPU 密集型任务的并行计算,而多线程则更适合用于 I/O 密集型任务的并发处理。

100。 线程是并发还是并行,进程是并发还是并行?

线程和进程的并发性和并行性是不同的:

  1. 线程:
    线程是操作系统调度的基本单位,它是在同一个进程内部并发执行的。多个线程可以在同一时间点执行不同的任务,但这些线程共享同一个进程的内存空间。在多核处理器上,多个线程可以通过时间片轮转的方式交替执行,从而实现并发。但由于全局解释器锁(GIL)的存在,Python 中的多线程无法实现真正的并行计算。

  2. 进程:
    进程是操作系统分配资源的基本单位,它是独立的执行环境。每个进程都有自己独立的内存空间,包括代码、数据和堆栈等。多个进程可以在同一时间点在不同的 CPU 核上并行执行,从而实现真正的并行计算。进程之间通过进程间通信(IPC)机制来实现数据的交换和共享。

综上所述:

  • 线程是并发的,因为多个线程可以在同一时间点执行不同的任务,共享进程的资源。
  • 线程不是并行的,因为在 Python 中,由于全局解释器锁(GIL)的限制,多个线程无法同时在多个 CPU 核上并行执行 Python 字节码指令。
  • 进程既可以是并发的,也可以是并行的。多个进程可以在同一时间点并发执行不同的任务,共享操作系统的资源。而在多核处理器上,多个进程可以同时在不同的 CPU 核上并行执行,实现真正的并行计算。

101. IO密集型和CPU密集型区别?

IO密集型(I/O-bound)和CPU密集型(CPU-bound)是描述计算任务特性的两个概念。它们区别如下:

  1. IO密集型:
    IO密集型任务的特点是主要的时间消耗在等待输入输出(I/O)操作上,而不是进行计算。这类任务通常涉及读取或写入文件、网络通信、数据库访问等操作,需要等待外部资源的响应。

IO密集型任务的特点包括:

  • 高度依赖外部资源的读取和写入操作。
  • 执行时间主要花费在等待IO操作完成的时间上。
  • 可以通过并发执行,如使用多线程或异步编程,以充分利用等待IO操作的时间。
  1. CPU密集型:
    CPU密集型任务的特点是主要的时间消耗在计算操作上,而不是等待IO操作完成。这类任务通常涉及大量的数值计算、图像处理、加密解密等操作,需要大量的CPU资源。

CPU密集型任务的特点包括:

  • 高度依赖CPU计算能力。
  • 执行时间主要花费在进行复杂的计算操作上。
  • 无法通过并发执行来加速,因为多个计算任务需要共享同一个CPU。

区分任务是IO密集型还是CPU密集型对于选择适当的并发策略和资源分配是很重要的。对于IO密集型任务,可以通过并发执行来提高效率,例如使用多线程或异步编程。而对于CPU密集型任务,使用并发执行并不能提高效率,因为多个任务会竞争同一个CPU资源,反而可能导致性能下降。

102. python asyncio的原理?

Python的asyncio是一个异步编程框架,它基于协程(coroutine)和事件循环(event loop)实现异步操作。下面是asyncio的工作原理简述:

  1. 协程(Coroutine):
    协程是asyncio的核心概念。协程是一种可以暂停和恢复执行的函数,通过async def定义。协程使用await关键字等待异步操作的完成,并在等待时释放控制权,让事件循环继续执行其他任务。

  2. 事件循环(Event Loop):
    事件循环是asyncio的运行引擎。它负责调度和执行协程,并在适当的时候切换执行上下文。事件循环维护一个任务队列,每个任务都是一个协程对象。事件循环会不断地从任务队列中选择一个任务执行,直到所有任务完成。

  3. 异步操作和等待(Async Operations and Await):
    异步操作是asyncio中的核心概念。异步操作可以是IO操作、网络请求、定时器等。协程使用await关键字等待异步操作的完成,当遇到await时,协程会暂时挂起并将控制权返回给事件循环,同时注册回调函数,等待异步操作完成。一旦异步操作完成,事件循环会通知相应的协程继续执行。

  4. 回调函数(Callbacks):
    asyncio使用回调函数来处理异步操作的完成事件。当异步操作完成时,事件循环会调用预先注册的回调函数,通知相关的协程继续执行。回调函数可以是普通函数、协程函数或任务。

  5. 并发性(Concurrency):
    asyncio通过事件循环的机制实现并发性。事件循环可以同时处理多个协程,通过在协程之间进行切换,使得多个任务可以并发地执行。这种并发性是基于协程的非阻塞特性实现的,避免了线程切换的开销。

总结起来,asyncio通过协程、事件循环和异步操作等机制,实现了基于协程的异步编程模型。通过合理地编写协程,使用await等待异步操作的完成,并在事件循环中调度和执行协程,可以实现高效的异步IO操作和并发处理。

你可能感兴趣的:(面试八股文,python,开发语言)