Python - 面试题 (面试)

什么是进程、线程、协程?以及它们的优缺点?

  • 进程是操作系统资源分配的基本单位,线程是 CPU 调度的基本单位,协程是用户态的轻量级线程。
  • 进程之间相互独立,线程之间共享进程资源,协程之间共享线程资源。
  • 进程切换代价最大,线程次之,协程最小。
  • 进程之间切换需要切换地址空间,线程之间切换需要切换上下文,协程之间切换只需要保存上下文。
  • 进程之间通信需要 IPC,线程之间通信可以通过共享内存等方式,协程之间通信可以通过 yield 等方式。
  • 进程可以利用多核 CPU,线程和协程只能利用单核 CPU。
  • 因此,进程适合 CPU 密集型任务,线程适合 IO 密集型任务,协程适合高并发任务。

爬虫框架 (Scrapy) 的运行机制

  • 工作原理图

  • Scrapy 运行机制

    1. 引擎从调度器中取出一个链接 (URL) 用于接下来的抓取。
    2. 引擎把 URL 封装成一个请求 (Request) 传给下载器 (Downloader)。
    3. 下载器把资源下载下来,并封装成应答包 (Response)。
    4. 爬虫解析 Response。
    5. 解析出实体(Item),则交给实体管道进行进一步的处理。
    6. 解析出的是链接(URL),则把 URL 交给调度器等待抓取。

爬虫框架 (Scrapy-Redis) 的运行机制

Scrapy-Redis 是基于 Scrapy 框架的一个分布式爬虫框架,它的运行机制如下:

  1. 爬虫程序启动,将爬取请求放入 Redis 队列中。
  2. 爬虫程序从 Redis 队列中获取请求,并将请求交给 Scrapy 引擎。
  3. 引擎将请求交给调度器,调度器根据一定的策略将请求放入请求队列中。
  4. 引擎从请求队列中获取请求,并将请求交给下载器。
  5. 下载器将请求对应的网页下载下来,并将下载结果返回给引擎。
  6. 引擎将下载结果交给爬虫程序进行解析。
  7. 爬虫程序将解析结果进行处理,如存储到数据库中。
  8. 重复步骤 2-7,直到 Redis 队列中没有请求。

在这个过程中,Scrapy-Redis 使用了 Redis 作为分布式队列,将请求和下载结果存储在 Redis 中,实现了分布式爬虫的功能。同时,Scrapy-Redis 还提供了一些去重、过滤等功能,方便用户进行爬虫开发。

爬虫框架 (Scrapy-Redis) 的优缺点

  • 优点
    1. 支持分布式爬取,可以将爬虫任务分配到多个节点上执行,提高了爬取效率;
    2. 支持断点续爬,可以在爬虫中途停止后,从上次停止的位置继续爬取,避免了重复爬取;
    3. 支持优先级调度,可以根据不同的优先级来调度爬虫任务,提高了爬取效率;
    4. 支持多种数据存储方式,可以将爬取结果存储到 Redis、MySQL、MongoDB 等多种数据源中。
  • 缺点
    1. 学习成本较高,需要掌握 Scrapy 框架的基础知识以及 Redis 的使用;
    2. 需要搭建 Redis 服务器,增加了部署难度;
    3. Redis 是内存数据库,如果 Redis 宕机,数据将会丢失。
    4. Redis 是单线程的,当 Redis 的负载达到瓶颈时,性能会下降。
    5. Redis 的存储空间有限,如果爬取的数据量很大,Redis 可能会存储不下。
  • 总结
    • 对于小规模的爬虫任务,使用 Scrapy-Redis 可能会增加开发和部署的复杂度,不一定能带来明显的效率提升。
    • 如果你的爬虫任务需要支持分布式爬取、断点续爬、优先级调度等功能,那么 Scrapy-Redis 是一个不错的选择。但如果你的爬虫任务规模较小,或者不需要这些高级功能,那么使用 Scrapy 框架可能更为简单和方便。

Feapder 和 Scrapy 有什么区别?

  1. 架构和设计思路
    Scrapy 是一个功能完整的 Web 爬虫框架,它采用了基于 Twisted 的异步网络框架,具有高效的并发性和稳定性,支持自定义中间件、管道和扩展等功能。而 Feapder 采用了分布式架构,基于 Celery 和 Redis 进行任务调度和分发,支持多进程、多线程、异步 IO 等方式,具有更高的可扩展性和可配置性。
  2. 支持的平台和扩展性
    Scrapy 可以运行在 Windows、Linux 和 Mac OS X 等平台上,并且具有丰富的插件和扩展,如 Scrapy-Redis、Scrapy-Splash 等,可用于处理 JavaScript 动态渲染的页面。Feapder 则更适合于大规模分布式爬取任务,支持海量数据的处理和分发。
  3. 学习和使用难度
    相对而言,Scrapy 的学习和使用难度较高,需要掌握异步编程和 Twisted 网络框架的相关知识,同时也需要了解 HTML、CSS 和 XPath 等 Web 开发相关技术。而 Feapder 则更加易于上手,用户只需要定义好爬取规则和数据处理逻辑即可,无需过多关注底层的技术实现细节。

爬虫爬取数据的过程中,遇到的反爬手段及解决方案

  1. IP 封锁
    使用代理 IP,通过轮换 IP 的方式来避免被封锁。可以使用第三方代理 IP 服务商,也可以自己搭建代理池。
  2. 验证码
    使用 OCR 技术识别验证码,或者手动输入验证码。如果验证码是动态的,可以使用 Selenium 模拟人工操作。
  3. 请求头检测
    模拟浏览器行为,设置请求头,如 User-Agent、Referer 等,使请求看起来像是来自浏览器而非爬虫。
  4. 动态页面
    使用 Selenium 模拟人工操作,获取动态页面的数据。
  5. 数据加密
    分析加密算法,模拟加密过程,获取加密后的数据。
  6. 反爬策略更新
    定期更新爬虫代码,适应网站反爬策略的更新。同时,遵守网站的爬虫规则,不要频繁请求、不要过度抓取、不要对网站造成过大的负担。

如果对方网站可以反爬取,封 ip 怎么办?

  • 如果对方网站可以反爬取,封 ip,可以使用代理 IP,轮流使用多个 IP 进行爬取,避免被封 IP。
  • 可以使用第三方代理 IP 服务商,或者自己搭建代理池。
  • 在爬虫代码中,可以使用 requests 库的 proxies 参数来设置代理 IP。具体使用方法如下:
    1
    2
    3
    4
    5
    6
    7
    8
    import requests

    proxies = {
    'http': 'http://ip1:port1',
    'https': 'https://ip2:port2',
    }

    response = requests.get(url, proxies=proxies)
  • 其中,ip1:port1ip2:port2 分别是代理 IP 的地址和端口号。可以使用多个代理 IP,每次请求时随机选择一个代理 IP。如果使用第三方代理 IP 服务商,可以通过 API 获取代理 IP。如果自己搭建代理池,可以使用开源的代理池项目,如 proxypool

Python 垃圾回收机制?

  1. 引用计数
    Python 内部使用引用计数,来保持追踪内存中的对象,Python 内部记录了对象有多少个引用,即引用计数,当对象被创建时就创建了一个引用计数,当对象不再需要时,这个对象的引用计数为 0 时,它被作为垃圾回收。
  2. 循环垃圾回收器
    当两个对象相互引用时,确保释放循环引用对象 (a 引用 b, b 引用 a, 导致其引用计数永远不为 0) 。
  3. 内存池机制
    Python 提供了对内存的垃圾收集机制,但是它将不用的内存放到内存池而不是返回给操作系统。
    Python 中所有小于 256 个字节的对象都使用 pymalloc 实现的分配器,而大的对象则使用系统的 malloc。另外 Python 对象,如整数,浮点数和 List,都有其独立的私有内存池,对象间不共享他们的内存池。也就是说如果你分配又释放了大量的整数,用于缓存这些整数的内存就不能再分配给浮点数。
  4. 分代回收
    Python 同时采用了分代 (generation) 回收的策略。这一策略的基本假设是,存活时间越久的对象,越不可能在后面的程序中变成垃圾。我们的程序往往会产生大量的对象,许多对象很快产生和消失,但也有一些对象长期被使用。出于信任和效率,对于这样一些 “长寿” 对象,我们相信它们的用处,所以减少在垃圾回收中扫描它们的频率。

Post 和 Get 的区别是什么?

  1. GET 请求的数据会附在 URL 之后,以?分割 URL 和传输数据,参数之间以 & 相连,如:http://www.baidu.com/index.php?class=1&name=2;而 POST 则是将提交的数据放在 HTTP 请求的正文中。
  2. GET 请求提交的数据大小有限制(因为浏览器对 URL 的长度有限制),而 POST 请求提交的数据没有大小限制。
  3. GET 请求只能进行 url 编码,而 POST 支持多种编码方式。
  4. GET 请求的数据会被浏览器缓存,而 POST 请求不会被缓存,除非手动设置。
  5. GET 请求只能进行数据查询,而 POST 请求支持更多的数据操作。
  6. GET 请求比 POST 请求更不安全,因为参数直接暴露在 URL 上,所以不能用来传递敏感信息。
  7. GET 请求的执行效率比 POST 请求高,因为 GET 请求只需要从服务器上获取数据,而 POST 请求需要向服务器提交数据,然后等待服务器响应。
  1. cookie 数据存放在客户的浏览器上, session 数据放在服务器上
  2. cookie 不是很安全,别人可以分析存放在本地的 cookie 并进行 cookie 欺骗,如果主要考虑到安全应当使用 session
  3. session 会在一定时间内保存在服务器上。 当访问增多,会比较占用你服务器的性能,如果主要考虑到减轻服务器性能方面,应当使用 cookie
  4. 单个 cookie 在客户端的限制是 3K,就是说一个站点在客户端存放的 cookie 不能超过 3K
  5. 所以:将登陆信息等重要信息存放为 session ;其他信息如果需要保留,可以放在 cookie 中
  6. session 的运行依赖 session id,而 session id 是存在 cookie 中的,也就是说,如果浏览器禁用了 cookie ,同时 session 也会失效(但是可以通过其它方式实现,比如在 url 中传递 session_id)

Python 的数据类型以及彼此间的区别?

Python 提供的基本数据类型主要有:布尔类型、整型、浮点型、字符串、列表、 元组、集合、字典等等

数据 类型 值 / 表示方法 区别
整型 int
浮点型 float
布尔型 bool true/false
NoneType None
字符串 str
列表 list [] 可修改
元组 tuple () 不可修改
集合 set {} 不可修改;集合中的元素不能重复
字典 dict { key : value } 可修改

Python 多线程和 GIL

  • 什么是 GIL?

    GIL 是 Python 解释器中的一个锁,它确保在任何时候只有一个线程在执行 Python 字节码。这意味着在 Python 中,多线程不能利用多个 CPU 核心。

  • 为什么需要 GIL?

    GIL 是为了保护 Python 解释器免受线程冲突的影响。如果没有 GIL,多个线程可能会同时访问 Python 对象,从而导致数据损坏和其他问题。

  • GIL 的缺点

    由于 GIL 的存在,Python 中的多线程不能利用多个 CPU 核心。这意味着在 Python 中,多线程不能加速 CPU 密集型任务。

  • 如何避免 GIL?

    要避免 GIL,可以使用多进程而不是多线程。在 Python 中,多进程可以利用多个 CPU 核心,因为每个进程都有自己的 Python 解释器和 GIL。

Python 中并发和并行有啥区别?

  • 在 Python 中,并发和并行是两个不同的概念。
  • 并发是指同时处理多个任务,但是在同一时刻只能处理一个任务,因为只有一个 CPU。
  • 而并行是指同时处理多个任务,并且在同一时刻可以处理多个任务,因为有多个 CPU。
  • 在 Python 中,可以使用多线程实现并发,也可以使用多进程实现并行。
  • 多线程是在同一个进程中创建多个线程,共享进程的内存空间,因此线程之间的通信比较容易。
  • 多进程是在操作系统中创建多个进程,每个进程有自己独立的内存空间,因此进程之间的通信比较困难。
  • 如果需要在 Python 中实现并行,可以使用 multiprocessing 模块。如果需要在 Python 中实现并发,可以使用 threading 模块。

乐观锁和悲观锁

  • 在 Python 中,乐观锁和悲观锁是两种不同的并发控制机制。
  • 悲观锁:悲观锁假定在并发访问时会发生冲突,因此在访问共享资源之前会先锁定它,以确保其他线程无法访问该资源。
  • 乐观锁:乐观锁则假定在并发访问时不会发生冲突,因此不会锁定共享资源,而是在更新时检查是否有其他线程已经更新了该资源。如果有,则重新尝试更新,直到成功为止。
  • 在 Python 中,可以使用线程锁或进程锁来实现悲观锁,也可以使用版本控制来实现乐观锁。

深拷贝和浅拷贝的区别

  • 在 Python 中,深拷贝和浅拷贝是两种不同的对象复制方式。
  • 浅拷贝:浅拷贝只复制对象的引用,而不复制对象本身。因此,如果原始对象中的某个属性被修改,那么浅拷贝的对象中的相应属性也会被修改。
  • 深拷贝:深拷贝则会复制对象本身,包括对象中的所有属性。因此,如果原始对象中的某个属性被修改,深拷贝的对象中的相应属性不会被修改。
  • 在 Python 中,可以使用 copy 模块中的 copy() 函数来实现浅拷贝,使用 copy 模块中的 deepcopy() 函数来实现深拷贝。
  • 如,假设有一个列表 a 和一个字典 b,可以使用以下代码进行浅拷贝和深拷贝:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    import copy

    a = [1, 2, 3]
    b = {'name': 'Alice', 'age': 25}

    # 浅拷贝
    a_copy = copy.copy(a)
    b_copy = copy.copy(b)

    # 深拷贝
    a_deepcopy = copy.deepcopy(a)
    b_deepcopy = copy.deepcopy(b)

  • 需要注意的是,如果对象中包含循环引用,使用深拷贝可能会导致无限递归,从而导致程序崩溃。因此,在使用深拷贝时,需要确保对象中不存在循环引用。

什么是闭包?

  • 定义:

    • 闭包是指函数可以访问其它函数内部变量的函数。
    • 在 Python 中,闭包是通过嵌套函数实现的。嵌套函数可以访问外部函数的变量,即使外部函数已经返回。这是因为嵌套函数在定义时会捕获外部函数的变量,并在嵌套函数中保留对这些变量的引用。这些变量和嵌套函数一起形成了闭包。
  • 条件:

    • 外部函数中定义一个内部函数;
    • 内部函数中使用了外部函数的变量;
    • 外部函数将内部函数作为返回值返回。
  • 示例:

    1
    2
    3
    4
    5
    6
    7
    8
    def outer_function(x):
    def inner_function(y):
    return x + y
    return inner_function

    closure = outer_function(10)
    print(closure(5)) # Output: 15

    • 在这个例子中,outer_function 返回 inner_function,并将 x 作为参数传递给 inner_functionclosure 变量现在引用 inner_function,并且可以像普通函数一样调用。当 closure 被调用时,它将使用 x 的值和传递给它的参数 y 的值计算结果。由于 x 是在 outer_function 中定义的,inner_function 可以访问它,即使 outer_function 已经返回。这就是一个闭包的例子。
  • 作用:提高代码的复用度

什么是装饰器?

  • 简介:

    • 装饰器是 Python 中的一种高级语法,它允许程序员在不修改原始代码的情况下,动态地修改或扩展函数、方法或类的行为。
    • 装饰器本质上是一个函数,它接受一个函数作为参数,并返回一个新的函数,该函数可以包装原始函数并添加额外的功能。装饰器通常用于实现横切关注点(cross-cutting concerns),例如日志记录、性能分析、缓存、验证等。
  • 示例:它可以在函数调用前后打印日志

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    def log(func):
    def wrapper(*args, **kwargs):
    print('调用函数:', func.__name__)
    result = func(*args, **kwargs)
    print('函数结果:', result)
    return result

    return wrapper


    @log
    def add(x, y):
    return x + y


    print(add(2, 3))

    """
    输出:
    调用函数: add
    函数结果: 5
    5
    """

在上面的示例中,我们定义了一个名为 log 的装饰器函数,它接受一个函数作为参数,并返回一个新的函数 wrapperwrapper 函数包装了原始函数 func,并在函数调用前后打印日志。然后,我们使用 @log 语法将 add 函数装饰为 log(add),这意味着 add 函数现在被 log 函数包装,并且在调用 add 函数时会自动调用 log 函数。

什么是可迭代对象?

  • 在 Python 中,可迭代对象是指可以使用 for 循环进行遍历的对象。
  • 常见的可迭代对象包括 列表、元组、字符串、字典、集合 等。
  • 可迭代对象可以使用 for 循环进行遍历,也可以使用内置函数 iter() 将其转换为迭代器对象。
  • 可以使用 isinstance() 函数来判断一个对象是否为可迭代对象,例如:
    1
    2
    3
    4
    from collections.abc import Iterable

    lst = [1, 2, 3]
    print(isinstance(lst, Iterable)) # True

什么是迭代器?

  • 简介:

    • Python 中的迭代器是一种可以遍历容器内元素的对象,它可以实现对容器内元素的访问,而不需要暴露容器的内部细节。
    • 迭代器可以被用于遍历序列(列表、元组、字符串)和非序列(字典、文件等)的数据类型。
    • 在 Python 中,迭代器是通过实现 __iter____next__ 方法来实现的。其中,__iter__ 方法返回迭代器对象本身,__next__ 方法返回容器中的下一个元素。当容器中没有元素时,__next__ 方法会抛出 StopIteration 异常。
  • 示例:

    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
    class MyIterator:
    def __init__(self, start, end):
    self.current = start
    self.end = end

    def __iter__(self):
    return self

    def __next__(self):
    if self.current >= self.end:
    raise StopIteration
    else:
    self.current += 1
    return self.current - 1


    my_iterator = MyIterator(0, 5)
    for i in my_iterator:
    print(i)

    """
    输出:
    0
    1
    2
    3
    4
    """

    在上面的示例中,我们定义了一个 MyIterator 类,它实现了 __iter____next__ 方法。__iter__ 方法返回迭代器对象本身,__next__ 方法返回容器中的下一个元素。在 for 循环中,我们创建了一个 MyIterator 对象,并使用它来遍历容器中的元素。

  • 总结:

    • 就是可以使用 for-in 进行遍历,并且可以使用 next 依次获取元素的对象
    • 生成器就是一种特殊的迭代器
    • 判断是否是迭代器
      1
      2
      3
      4
      from collections.abc import Iterator

      l = (i for i in range(10))
      print(isinstance(l, Iterator)) # True

什么是生成器?

  • 简介:

    • Python 生成器是一种特殊的迭代器,它的元素是按需计算的,而不是预先计算的。这使得生成器非常适合处理大量数据或无限数据流,因为它们可以逐个生成元素,而不必一次性将它们全部计算出来。
    • 生成器可以使用函数来定义,这些函数使用 yield 语句来产生元素。当函数执行到 yield 语句时,它会暂停执行并返回一个值给调用者。调用者可以使用 next() 函数来恢复函数的执行,并继续执行到下一个 yield 语句。
    • 生成器还可以用于实现协程,这是一种轻量级的并发编程模型。在协程中,多个函数可以同时执行,但是它们之间的切换是由程序员控制的,而不是由操作系统控制的。这使得协程非常适合处理高并发的网络应用程序,因为它们可以在单个线程中处理多个连接,而不必创建多个线程或进程。
  • 示例:生成斐波那契数列的前 n 个元素

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    def fibonacci(n):
    a, b = 0, 1
    for i in range(n):
    yield a
    a, b = b, a + b


    # 使用生成器生成斐波那契数列的前10个元素
    for num in fibonacci(10):
    print(num)

    """
    输出:
    0
    1
    1
    2
    3
    5
    8
    13
    21
    34
    """

    这个示例中,fibonacci() 函数定义了一个生成器,它使用 yield 语句来产生斐波那契数列的元素。在 for 循环中,我们使用这个生成器来生成斐波那契数列的前 10 个元素,并将它们打印出来。

  • 特性:

    • 可以使用 next 获取数据,一次一个,结束时会报错
    • 只能遍历一遍
    • 可以转换为列表
    • 可以使用 for-in 遍历

什么是单例模式?有哪些实现方法?有哪些使用场景?

  • 单利模式

    在 Python 中,单例模式是一种设计模式,它确保类只有一个实例,并提供全局访问点。

  • 实现方式

    1. 饿汉式单例模式

      • 饿汉式单例模式是指在程序启动时就创建单例对象,以后每次调用都返回该对象。实现起来比较简单,只需要在模块加载时创建单例对象即可。
        1
        2
        3
        4
        5
        6
        7
        class Singleton:
        __instance = None

        def __new__(cls):
        if cls.__instance is None:
        cls.__instance = super().__new__(cls)
        return cls.__instance
      • 在这个例子中,我们定义了一个 Singleton 类,其中 __instance 是一个类变量,用于保存单例对象。在 __new__ 方法中,我们判断 __instance 是否为 None,如果是,则调用父类的 __new__ 方法创建一个新的对象,并将其赋值给 __instance;否则,直接返回 __instance
    2. 懒汉式单例模式

      • 懒汉式单例模式是指在需要时才创建实例,而不是在程序启动时就创建实例。这种方式可以节省系统资源,但需要考虑线程安全问题。
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        class Singleton:
        __instance = None

        @staticmethod
        def get_instance():
        if Singleton.__instance is None:
        Singleton()
        return Singleton.__instance

        def __init__(self):
        if Singleton.__instance is not None:
        raise Exception("This class is a singleton!")
        else:
        Singleton.__instance = self
      • 在这个实现中,我们使用一个静态变量 __instance 来保存实例。在 get_instance 方法中,如果 __ instance 为空,则创建一个新实例并将其赋值给 __instance。否则,返回现有实例。
      • 注意,我们使用了一个私有的构造函数来防止在类外部创建实例。如果尝试创建第二个实例,则会引发异常。
      • 这个实现是线程安全的,因为 Python 的全局解释器锁(GIL)会确保在任何时候只有一个线程可以执行 Python 代码。如果您需要在多线程环境中使用此实现,请确保您的代码不会释放 GIL,否则可能会导致竞态条件。
    3. 基于模块式单例模式

      • 模块在程序中只会被导入一次,因此可以利用这个特性来实现单例模式。
        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
        # singleton.py
        class Singleton:
        __instance = None

        def __new__(cls):
        if not cls.__instance:
        cls.__instance = super().__new__(cls)
        return cls.__instance

        def __init__(self):
        self.name = 'Singleton'

        @staticmethod
        def getInstance():
        if not Singleton.__instance:
        Singleton.__instance = Singleton()
        return Singleton.__instance


        # main.py
        from singleton import Singleton

        s1 = Singleton.getInstance()
        s2 = Singleton.getInstance()

        print(s1 is s2) # True

      • 在上面的示例代码中,Singleton 类实现了单例模式,getInstance() 方法用于获取单例对象。在 main.py 中,通过导入 singleton 模块,使用 Singleton.getInstance() 方法获取单例对象。运行结果为 True,说明 s1s2 是同一个对象,即单例模式实现成功。
    4. 基于装饰器的单例模式

      • 使用装饰器来实现单例模式的好处是,它可以保证在任何时候都只有一个实例存在,并且不需要修改原来的类定义。
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        def singleton(cls):
        instances = {}

        def getinstance(*args, **kwargs):
        if cls not in instances:
        instances[cls] = cls(*args, **kwargs)
        return instances[cls]

        return getinstance


        @singleton
        class MyClass:
        pass
      • 在这个例子中,singleton 是一个装饰器函数,它接受一个类作为参数,并返回一个新的函数 getinstancegetinstance 函数会检查 instances 字典中是否已经有了该类的实例,如果没有,则创建一个新的实例并将其存储在 instances 字典中,然后返回该实例。如果已经有了该类的实例,则直接返回该实例。
    5. 基于元类的单例模式

      • 元类是用于创建类对象的类,类对象创建实例对象时一定要调用 __call__ 方法,因此在调用 __call__ 方法时,可以保证始终只创建一个实例对象。
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        class Singleton(type):
        _instances = {}

        def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
        cls._instances[cls] = super().__call__(*args, **kwargs)
        return cls._instances[cls]


        class MyClass(metaclass=Singleton):
        pass
      • 在这个示例代码中,我们定义了一个名为 Singleton 的元类,它维护了一个 _instances 字典,用于存储每个类的唯一实例。在调用 __call__ 方法时,如果该类的实例不存在,则创建一个新实例并将其存储在 _instances 字典中。如果该类的实例已经存在,则直接返回该实例。这样,我们就可以保证每个类只有一个实例对象了。
      • 要使用这个元类,只需要将它作为一个关键字参数传递给类定义即可,如下所示:
        1
        2
        class MyClass(metaclass=Singleton):
        pass
      • 这样,我们就创建了一个名为 MyClass 的类,并使用 Singleton 元类来确保它是一个单例类。
  • 使用场景

    • 当您需要确保只有一个对象可以访问某些共享资源时,例如数据库连接或文件系统。
    • 当您需要确保只有一个对象可以控制某些系统级别的操作时,例如日志记录或配置管理。
    • 当您需要确保只有一个对象可以代表某个概念时,例如应用程序中的用户或设置。

Python 如何提高并发?

  • 使用多线程或多进程
    Python 中的 threadingmultiprocessing 模块可以用于创建多个线程或进程,从而提高并发性能。但是,需要注意的是,在使用多线程或多进程时,需要考虑线程 / 进程之间的同步和通信问题,以避免出现竞争条件和死锁等问题。
  • 使用协程
    Python 中的 asyncio 模块可以用于创建协程,从而实现异步编程。协程是一种轻量级的线程,可以在单个线程中实现并发执行。使用协程可以避免线程 / 进程之间的切换开销,从而提高并发性能。
  • 使用异步 IO
    Python 中的 asyncio 模块还可以用于实现异步 IO 操作,从而提高 IO 密集型应用的并发性能。异步 IO 可以避免线程 / 进程之间的切换开销,从而提高并发性能。
  • 使用线程池或进程池
    Python 中的 concurrent.futures 模块可以用于创建线程池或进程池,从而提高并发性能。线程池或进程池可以避免频繁创建和销毁线程 / 进程的开销,从而提高并发性能。

HTTP 的三次握手和四次挥手

  • HTTP 的三次握手和四次挥手是指在 HTTP 协议中,客户端和服务器之间建立连接和断开连接的过程。
  • 三次握手是指客户端和服务器之间建立连接时,需要进行三次握手,以确保连接的可靠性。
  • 四次挥手是指客户端和服务器之间断开连接时,需要进行四次挥手,以确保数据的完整性。

AJAX 的优缺点

  • 优点:

    • 无需刷新整个页面,只需要更新部分页面,减少了不必要的数据传输,提高了用户体验。
    • 可以异步请求数据,不会阻塞页面,提高了页面的响应速度。
    • 可以根据用户的操作动态地更新页面,增强了页面的交互性。
    • 可以通过后台请求数据,保护了前端代码的安全性。
  • 缺点:

    • 对搜索引擎的支持不够友好,因为搜索引擎爬虫不会执行 JavaScript 代码,无法获取通过 AJAX 请求获取的数据。
    • 对浏览器的前进后退按钮支持不够友好,因为 AJAX 请求不会改变浏览器的 URL,无法通过浏览器的前进后退按钮返回到之前的页面。
    • 对代码的可维护性不够友好,因为 AJAX 请求的代码通常是分散在多个文件中的,不利于代码的维护和管理。
    • 对服务器的压力较大,因为 AJAX 请求的次数较多,会增加服务器的负担。

MySQL 分区

  • RANGE 分区:按照给定的连续区间进行分区,每个分区包含给定的范围内的所有值。
  • LIST 分区:按照给定的离散值进行分区,每个分区包含给定的值。
  • HASH 分区:根据用户指定的表达式的值进行分区,每个分区包含具有相同表达式值的所有行。
  • KEY 分区:类似于 HASH 分区,但是使用哈希函数计算分区键值,而不是使用用户指定的表达式。
  • 在 MySQL 5.5 及更高版本中,还支持了子分区,可以在每个分区内再进行分区。

什么是 MySQL 事务?

  • MySQL 事务是指一组 SQL 语句,这些语句作为一个单独的工作单元执行,要么全部执行,要么全部不执行。如果其中任何一个语句失败,则整个事务都将回滚,即撤消对数据库的所有更改。
  • 在 MySQL 中,可以使用以下语句来控制事务:
    • START TRANSACTION:开始一个事务。
    • COMMIT:提交事务,使更改永久保存在数据库中。
    • ROLLBACK:回滚事务,撤消对数据库的所有更改。
  • 以下是一个简单的 MySQL 事务示例:
    1
    2
    3
    4
    START TRANSACTION;
    UPDATE accounts SET balance = balance - 100 WHERE id = 1;
    UPDATE accounts SET balance = balance + 100 WHERE id = 2;
    COMMIT;
  • 在这个例子中,我们首先开始一个事务,然后执行两个更新语句来转移 100 个单位的货币从账户 1 到账户 2。如果两个更新语句都成功执行,则使用 COMMIT 语句提交事务。如果任何一个更新语句失败,则使用 ROLLBACK 语句回滚事务,撤消对数据库的所有更改。

数据库事务的 ACID 四大特性

  • 简介:
    ACID 是指数据库事务应该具有的四个特性:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)。
  • 原子性
    指一个事务中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
  • 一致性
    指事务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。在一个事务中,数据从一个状态转变为另一个状态,如果事务的执行过程中出现了错误,那么系统会回滚到事务开始前的状态,这样就可以保证数据的一致性。
  • 隔离性
    指多个事务并发访问数据库时,每个事务都应该感觉不到其他事务的存在。也就是说,当多个事务并发执行时,执行每个事务的结果与将这些事务依次串行执行的结果相同。隔离性分为四个级别,由低到高依次为:读未提交(Read Uncommitted)、读已提交(Read Committed)、可重复读(Repeatable Read)和串行化(Serializable)。
  • 持久性
    指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其执行结果有任何影响。持久性通过将事务所做的修改持久化到非易失性存储器中(通常是硬盘)来实现。

MySQL 常用的聚合函数

函数 说明
SUM 计算某个字段的总和
AVG 计算某个字段的平均值
MAX 计算某个字段的最大值
MIN 计算某个字段的最小值
COUNT 计算行数,可以用来统计某个字段的数量
1
2
3
4
5
6
7
8
9
SELECT COUNT(*) FROM table_name;

SELECT SUM(column_name) FROM table_name;

SELECT AVG(column_name) FROM table_name;

SELECT MAX(column_name) FROM table_name;

SELECT MIN(column_name) FROM table_name;

MongoDB 中聚合的方法使用 aggregate()

表达式 描述
$sum 计算总和
$avg 计算平均值
$min 获取最小值
$max 获取最大值
$push 在结果文档中插入值到一个数组中
$addToSet 在结果文档中插入值到一个数组中,但不创建副本
$first 根据资源文档的排序获取第一个文档数据
$last 根据资源文档的排序获取最后一个文档数据
$group 将集合中的文档分组,可用于统计结果
$sort 将文档排序后输出
$limit 限制聚合管道返回的文档数
$skip 跳过指定数量的文档,并返回余下的文档
$unwind 将数组类型的字段进行拆分
$lookup 用于在同一数据库中的不同集合之间执行类似于 SQL 的左外部连接
  • MongoDB 的聚合管道将 MongoDB 文档在一个管道处理完毕后将结果传递给下一个管道处理。管道操作是可以重复的。
  • 表达式:处理输入文档并输出。表达式是无状态的,只能用于计算当前聚合管道的文档,不能处理其它的文档。
  • 聚合框架中常用的几个操作:
    • $project:修改输入文档的结构。可以用来重命名、增加或删除域,也可以用于创建计算结果以及嵌套文档。
    • $match:用于过滤数据,只输出符合条件的文档。
    • $match 使用 MongoDB 的标准查询操作。
    • $limit:用来限制 MongoDB 聚合管道返回的文档数。
    • $skip:在聚合管道中跳过指定数量的文档,并返回余下的文档。
    • $unwind:将文档中的某一个数组类型字段拆分成多条,每条包含数组中的一个值。
    • $group:将集合中的文档分组,可用于统计结果。
    • $sort:将输入文档排序后输出。
    • $geoNear:输出接近某一地理位置的有序文档

Redis 的数据类型有哪些?

类型 简介 特性 场景
String (字符串) 二进制安全 可以包含任何数据,比如 jpg 图片或者序列化的对象,一个键最大能存储 512M ...
Hash (字典) 键值对集合,即编程语言中的 Map 类型 适合存储对象,并且可以像数据库中 update 一个属性一样只修改某一项属性值 (Memcached 中需要取出整个字符串反序列化成对象修改完再序列化存回去) 存储、读取、修改用户属性
List (列表) 链表 (双向链表) 增删快,提供了操作某一段元素的 API 1, 最新消息排行等功能 (比如朋友圈的时间线) 2, 消息队列
Set (集合) 哈希表实现,元素不重复 1, 添加、删除,查找的复杂度都是 O (1) 2, 为集合提供了求交集、并集、差集等操作 1, 共同好友 2, 利用唯一性,统计访问网站的所有独立 ip 3, 好用推荐时,根据 tag 求交集,大于某个阈值就可以推荐
Sorted Set (有序集合) 将 Set 中的元素增加一个权重参数 score, 元素按 score 有序排列 数据插入集合时,已经进行天然排序 1, 排行榜 2, 带权重的消息队列

MongoDB 和 MySQL 有什么区别?

  1. 数据存储方式:
    MySQL 是关系型数据库系统,使用表来存储数据,表中的每行数据都有一个固定的结构和字段类型。而 MongoDB 是一种面向文档的数据库系统,它将数据存储在文档中,每个文档可以有不同的结构,没有固定的字段类型。
  2. 数据查询语言:
    MySQL 使用结构化查询语言(SQL)进行数据查询,而 MongoDB 使用基于文档的查询语言进行数据查询。
  3. 数据扩展性:
    MySQL 在处理大量数据时需要对表进行分区(分片)以支持水平扩展,而 MongoDB 天生支持水平扩展,通过分片来实现数据的负载均衡和横向扩展。
  4. 数据处理能力:
    MySQL 在处理事务时非常强大,可以保证数据的一致性和完整性。MongoDB 的事务处理相对较弱,但可以通过使用复制集(Replica Set)和分片(Sharding)来保证数据的可靠性和高可用性。
  5. 应用场景:
    MySQL 适用于需要进行大量数据处理和复杂的事务处理的应用程序,如电子商务网站、金融系统等。而 MongoDB 适用于需要处理大量非结构化或半结构化数据的应用程序,如社交网络、实时数据分析等。

爬虫滑块验证码解决方法

  • 简介:
    滑块验证码是一种常见的反爬虫手段,其主要原理是在用户登录或者注册时,需要用户拖动滑块到指定位置,从而验证用户的真实性。

  • 解决方案:

    1. 使用机器学习算法进行识别
      这种方法需要大量的数据集进行训练,而且需要不断地更新模型,以应对验证码的变化。
    2. 使用第三方验证码识别服务
      这种方法需要付费,而且不一定能够保证识别的准确性。
    3. 使用 Selenium 模拟用户行为
      这种方法需要模拟用户的鼠标移动、点击等操作,从而通过滑块验证码的验证。
    4. 使用 OCR 技术进行识别
      这种方法需要对验证码进行预处理,然后使用 OCR 技术进行识别,但是准确率不高。
  • 总结:
    如果验证码比较简单,可以使用 OCR 技术进行识别,如果验证码比较复杂,可以使用机器学习算法进行识别。如果需要高准确率的识别,可以考虑使用第三方验证码识别服务。

机器学习算法有哪些?

  1. 线性回归(Linear Regression)

    • 线性回归是一种用于建立两种或两种以上变量之间关系的方法。它可以用于预测结果,例如预测房价或股票价格。线性回归的目标是找到一条直线,使得所有数据点到该直线的距离之和最小。
    • 在 Python 中,可以使用 scikit-learn 库中的 LinearRegression 类来实现线性回归。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      from sklearn.linear_model import LinearRegression

      # 创建一个线性回归模型
      model = LinearRegression()

      # 训练模型
      model.fit(X_train, y_train)

      # 预测结果
      y_pred = model.predict(X_test)

    • 在这个例子中,X_trainy_train 是训练数据,X_test 是测试数据。model.fit(X_train, y_train) 用于训练模型,model.predict(X_test) 用于预测结果。
  2. 逻辑回归(Logistic Regression)

    • 逻辑回归是一种用于分类问题的机器学习算法。它可以用于二元分类和多元分类问题。
    • 逻辑回归的目标是找到一个函数,该函数可以将输入数据映射到一个离散的输出类别。
    • 在 Python 中,可以使用 scikit-learn 库中的 LogisticRegression 类来实现逻辑回归。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      from sklearn.linear_model import LogisticRegression
      from sklearn.model_selection import train_test_split
      from sklearn.datasets import load_iris

      # 加载数据集
      iris = load_iris()

      # 将数据集分为训练集和测试集
      X_train, X_test, y_train, y_test = train_test_split(iris.data, iris.target, test_size=0.2, random_state=42)

      # 创建逻辑回归模型
      model = LogisticRegression()

      # 训练模型
      model.fit(X_train, y_train)

      # 测试模型
      score = model.score(X_test, y_test)

      print("模型得分:", score)

    • 在这个例子中,我们使用了鸢尾花数据集来训练和测试逻辑回归模型。我们将数据集分为训练集和测试集,然后使用 LogisticRegression 类来创建模型。我们使用 fit() 方法来训练模型,并使用 score() 方法来测试模型的准确性。
    • 这只是逻辑回归算法的一个简单例子。在实际应用中,可能需要对数据进行预处理、特征选择和调整模型参数等操作,以获得更好的结果。
  3. 决策树(Decision Tree)

    • Python 中的决策树算法是一种基于树结构的监督学习算法,用于分类和回归分析。
    • 在决策树中,每个内部节点表示一个特征或属性,每个分支代表一个决策规则,每个叶节点代表一个输出结果。决策树算法的目标是创建一个模型,可以通过将输入数据沿树的分支传递来预测输出变量的值。
    • 在 Python 中,可以使用 scikit-learn 中的 DecisionTreeClassifier 类来训练和测试决策树模型。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      from sklearn.tree import DecisionTreeClassifier
      from sklearn.datasets import load_iris

      iris = load_iris()
      X = iris.data[:, 2:]
      y = iris.target

      tree_clf = DecisionTreeClassifier(max_depth=2)
      tree_clf.fit(X, y)

    • 在这个示例中,我们使用 Iris 数据集来训练一个决策树分类器。我们只使用了数据集中的两个特征,并将树的最大深度限制为 2。您可以根据需要调整这些参数以获得更好的性能。
  4. 随机森林(Random Forest)

    • 随机森林是一种集成学习方法,它通过在数据集上构建多个决策树来提高预测准确性和泛化能力。
    • 每个决策树都是基于随机选择的特征子集和随机选择的数据子集进行训练的。最终的预测结果是所有决策树的预测结果的平均值(回归问题)或投票结果(分类问题)。
    • 在 Python 中,可以使用 scikit-learn 库来实现随机森林算法。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      from sklearn.ensemble import RandomForestClassifier

      # 创建一个随机森林分类器
      rfc = RandomForestClassifier(n_estimators=100, max_depth=2, random_state=0)

      # 训练模型
      rfc.fit(X_train, y_train)

      # 预测
      y_pred = rfc.predict(X_test)

    • 在这个例子中,我们使用了 RandomForestClassifier 类来创建一个随机森林分类器。n_estimators 参数指定了要构建的决策树的数量,max_depth 参数指定了每个决策树的最大深度。random_state 参数用于控制随机性,以便在多次运行代码时得到相同的结果。
    • 我们使用 fit() 方法来训练模型,并使用 predict() 方法来进行预测。
    • 需要注意的是,随机森林算法的一个重要参数是 n_estimators,它指定了要构建的决策树的数量。通常情况下,n_estimators 越大,模型的预测准确性越高,但是训练时间也会越长。
  5. 支持向量机(Support Vector Machine)

    • 支持向量机(Support Vector Machine,SVM)是一种二分类模型,它的基本模型是定义在特征空间上的间隔最大的线性分类器,间隔最大使它有别于感知机;支持向量机还包括核技巧,这使它成为实质上的非线性分类器,它的学习策略便是间隔最大化,最终可转化为一个凸二次规划问题的求解。
    • SVM 的主要优点是:能够处理高维数据集;能够处理非线性特征的相互作用;无需依赖整个数据;可以提高泛化能力。如果你想了解更多关于 SVM 的知识,可以参考《统计学习方法》这本书,里面有详细的介绍和推导过程。
    • 如果你想在 Python 中使用 SVM 算法,可以使用 sklearn 库中的 SVM 模块。
      1
      2
      3
      4
      5
      6
      7
      from sklearn import svm

      X = [[0, 0], [1, 1]]
      y = [0, 1]
      clf = svm.SVC()
      clf.fit(X, y)

    • 这个例子中,我们使用 SVM 模块中的 SVC 类来创建一个分类器,然后使用 fit() 方法来训练模型。在这个例子中,我们使用了一个简单的二维数据集,其中包含两个类别。我们将这个数据集存储在 Xy 中,然后使用 fit() 方法来训练模型。
  6. K 近邻(K-Nearest Neighbors)

    • K 近邻算法是一种基本的分类和回归方法,它的基本思想是:如果一个样本在特征空间中的 k 个最相似(即特征空间中最邻近)的样本中的大多数属于某一个类别,则该样本也属于这个类别。
    • 在 Python 中,可以使用 scikit-learn 库中的 KNeighborsClassifier 类来实现 K 近邻算法。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      from sklearn.neighbors import KNeighborsClassifier

      # 创建K近邻分类器对象
      knn = KNeighborsClassifier(n_neighbors=3)

      # 训练模型
      knn.fit(X_train, y_train)

      # 预测新样本的类别
      y_pred = knn.predict(X_test)

    • 其中,n_neighbors 参数指定了 K 近邻算法中的 k 值,X_trainy_train 分别是训练集的特征和标签,X_test 是测试集的特征,y_pred 是预测的标签。
    • 需要注意的是,K 近邻算法对于特征空间的维度非常敏感,因此在使用 K 近邻算法时需要对特征进行适当的处理和选择。此外,K 近邻算法还需要大量的存储空间来存储训练集,因此在处理大规模数据时需要考虑存储空间的问题。
  7. 贝叶斯分类器(Naive Bayes)

    • 贝叶斯分类器是一种基于贝叶斯定理的分类器算法。它是一种概率分类器,即它基于样本的统计数据来预测新样本的类别。
    • 贝叶斯分类器的基本思想是利用贝叶斯定理来计算后验概率,即给定一个样本属于某个类别的概率,然后选择具有最高概率的类别作为预测结果。
    • 贝叶斯分类器有三种类型:朴素贝叶斯分类器、贝叶斯网络和贝叶斯推理。其中,朴素贝叶斯分类器是最常用的一种类型,它假设所有特征之间相互独立,从而简化了计算过程。
    • 在 Python 中,可以使用 scikit-learn 库中的 GaussianNB 类来实现朴素贝叶斯分类器算法。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      from sklearn.naive_bayes import GaussianNB

      # 创建一个朴素贝叶斯分类器对象
      clf = GaussianNB()

      # 训练分类器
      clf.fit(X_train, y_train)

      # 预测测试集
      y_pred = clf.predict(X_test)

    • 其中,X_trainy_train 是训练集的特征和标签,X_test 是测试集的特征。
  8. 神经网络(Neural Networks)

    • 神经网络是一种机器学习算法,它模拟了人类大脑的神经元之间的相互作用。神经网络通常用于分类和回归问题。在 Python 中,可以使用许多库来实现神经网络,例如 TensorFlowKeras
    • 这些库提供了许多不同类型的神经网络,例如卷积神经网络和循环神经网络。您可以根据您的数据和问题选择适当的神经网络类型。如果您是初学者,我建议您从 Keras 开始,因为它比 TensorFlow 更易于使用。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      from keras.models import Sequential
      from keras.layers import Dense

      # create model
      model = Sequential()
      model.add(Dense(12, input_dim=8, activation='relu'))
      model.add(Dense(8, activation='relu'))
      model.add(Dense(1, activation='sigmoid'))

      # Compile model
      model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])

    • 在这个例子中,我们创建了一个具有三个层的神经网络,其中第一层有 12 个神经元,第二层有 8 个神经元,最后一层有 1 个神经元。我们使用 ReLU 激活函数来激活每个神经元,并使用 Sigmoid 激活函数来输出二进制分类结果。我们使用二进制交叉熵作为损失函数,Adam 作为优化器,并使用准确度作为评估指标。
  9. 聚类(Clustering)

    • 聚类算法是一种无监督学习算法,它将数据集中的数据分成不同的组,使得组内的数据点彼此相似,而组间的数据点不相似。
    • 常见的聚类算法有 K-Means、层次聚类、DBSCAN 等。
    • 其中 K-Means 是最常用的聚类算法之一,它将数据集分成 K 个簇,每个簇的中心点是该簇中所有数据点的平均值。
    • 层次聚类是一种自下而上的聚类方法,它将数据点逐步合并成越来越大的簇,直到所有数据点都在同一个簇中。
    • DBSCAN 是一种基于密度的聚类算法,它将数据点分为核心点、边界点和噪声点,核心点之间的数据点属于同一个簇,边界点属于与其距离在一定范围内的核心点所在的簇,噪声点不属于任何簇。
    • 在选择聚类算法时,需要根据数据集的特点和应用场景进行选择。
  10. 主成分分析(Principal Component Analysis)

    • 主成分分析(PCA)是一种常用的数据降维算法,它可以将高维数据降到低维,同时保留数据的主要特征。
    • PCA 的基本思想是将原始数据投影到一个新的坐标系中,使得投影后的数据方差最大。这个新的坐标系就是数据的主成分方向。
    • 在 Python 中,我们可以使用 scikit-learn 库中的 PCA 类来实现主成分分析。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      from sklearn.decomposition import PCA
      import numpy as np

      # 创建一个1000x10的随机矩阵
      X = np.random.rand(1000, 10)

      # 创建PCA对象,将数据降到2维
      pca = PCA(n_components=2)

      # 对数据进行降维
      X_new = pca.fit_transform(X)

      # 输出降维后的数据
      print(X_new.shape)

    • 在这个例子中,我们首先创建了一个 1000x10 的随机矩阵 X,然后创建了一个 PCA 对象 pca,并将数据降到了 2 维。最后,我们使用 fit_transform 方法对数据进行降维,并输出降维后的数据形状。

线程上下文开销大是指什么?

  • 线程上下文开销大是指线程切换时需要保存和恢复的上下文信息较多,这些上下文信息包括程序计数器、寄存器、栈指针等。
  • 线程上下文开销大会导致线程切换的时间变长,从而影响程序的性能。在 Python 中,由于 GIL 的存在,线程切换时需要获取和释放 GIL,这也会增加线程上下文开销。
  • 因此,在 Python 中使用多线程时,应该尽量减少线程的切换次数,可以使用协程等方式来避免线程切换的开销。

用 SHA1 加密后的内容有几位?

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
45
46
47
48
from hashlib import md5
from hashlib import sha1
from hashlib import sha224
from hashlib import sha256
from hashlib import sha384
from hashlib import sha512

# MD5
md = md5('123456'.encode('utf8'))
# e10adc3949ba59abbe56e057f20f883e
print(md.hexdigest())
# 32
print(len(md.hexdigest()))

# SHA1
s1 = sha1('123456'.encode('utf8'))
# 7c4a8d09ca3762af61e59520943dc26494f8941b
print(s1.hexdigest())
# 40
print(len(s1.hexdigest()))

# SHA224
s224 = sha224('123456'.encode('utf8'))
# f8cdb04495ded47615258f9dc6a3f4707fd2405434fefc3cbf4ef4e6
print(s224.hexdigest())
# 56
print(len(s224.hexdigest()))

# SHA256
s256 = sha256('123456'.encode('utf8'))
# 8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92
print(s256.hexdigest())
# 64
print(len(s256.hexdigest()))

# SHA384
s384 = sha384('123456'.encode('utf8'))
# 0a989ebc4a77b56a6e2bb7b19d995d185ce44090c13e2984b7ecc6d446d4b61ea9991b76a4c2f04b1b4d244841449454
print(s384.hexdigest())
# 96
print(len(s384.hexdigest()))

# SHA512
s512 = sha512('123456'.encode('utf8'))
# ba3253876aed6bc22d4a6ff53d8406c6ad864195ed144ab5c87621b6c233b548baeae6956df346ec8c17f5ea10f35ee3cbc514797ed7ddd3145464e2a0bab413
print(s512.hexdigest())
# 128
print(len(s512.hexdigest()))

Python 的主要特性有哪些?

  1. 简单易学:Python 的语法非常简单,易于学习和理解。
  2. 开放源代码:Python 是一种开放源代码的编程语言,任何人都可以免费使用和修改它。
  3. 跨平台:Python 可以在多个操作系统上运行,包括 Windows、Linux 和 Mac OS 等。
  4. 面向对象:Python 是一种面向对象的编程语言,它支持面向对象的编程范式。
  5. 动态类型:Python 是一种动态类型的编程语言,它不需要在编写代码时指定变量的类型。
  6. 强大的标准库:Python 有一个强大的标准库,其中包含了许多有用的模块和函数,可以帮助开发人员快速开发应用程序。
  7. 可扩展性:Python 可以通过编写 C 或 C++ 扩展来扩展其功能,也可以通过使用其他语言编写的模块来扩展其功能。
  8. 高级特性:Python 支持许多高级特性,如生成器、装饰器、迭代器等,这些特性可以帮助开发人员编写更加高效和优雅的代码。

列表 (list) 和元组 (tulple) 有什么区别?

  1. 列表是可变的,元组是不可变的。这意味着,一旦创建了一个元组,就不能修改它,而列表可以随时修改。
  2. 列表使用方括号 [] 来定义,元组使用圆括号 () 来定义。
  3. 列表通常用于存储同类型的数据项,而元组通常用于存储异构数据项(即不同类型的数据项)。
  4. 列表支持许多操作,如添加、删除、插入和排序。元组只支持少数操作,如索引和切片。
  5. 列表和元组在性能方面也有所不同。由于元组是不可变的,因此它们比列表更快。
  6. 如果您需要存储一些不需要修改的数据,那么使用元组可能更好。如果您需要存储一些需要修改的数据,那么使用列表可能更好。

什么是 Python?为什么它会如此流行?

  • Python 是一种高级编程语言,它是一种解释型语言,这意味着代码可以直接运行,而不需要编译。
  • Python 是一种通用语言,它可以用于各种用途,例如 Web 开发,数据分析,人工智能等等。
  • Python 的流行原因有很多,其中一些原因包括它的易学性,易读性和广泛的社区支持。此外,Python 还有许多强大的库和框架,可以帮助开发人员更快地构建应用程序。

为什么 Python 执行速度慢,我们如何改进它?

  • Python 是一种解释型语言,相比于编译型语言,它的执行速度确实会慢一些。
  • 但是,Python 有很多优秀的第三方库,如 NumPy、Pandas、TensorFlow 等,这些库都是用 C 或 C++ 编写的,因此它们的执行速度非常快。如果你的代码需要处理大量的数据,可以考虑使用这些库来提高代码的执行速度。
  • 此外,Python 还有一些优化技巧可以提高代码的执行速度,例如使用列表推导式代替 for 循环、使用生成器代替列表、使用局部变量代替全局变量等。你可以在 Python 官方文档中了解更多关于优化 Python 代码的技巧。
  • 如果你的代码需要更高的执行速度,你可以考虑使用 Cython 将 Python 代码转换为 C 代码,这样可以大大提高代码的执行速度。Cython 是一种 Python 的扩展语言,它可以将 Python 代码转换为 C 代码,并且可以轻松地与 Python 代码进行交互。
  • 最后,如果你的代码需要更高的执行速度,你可以考虑使用 C 或 C++ 编写代码,并将其与 Python 代码进行交互。你可以使用 Python 的 ctypes 模块或 Cython 的 cdef 语句来实现这一点。但是,这种方法需要更多的编程技能和经验,因此只有在必要时才应该使用。

Python 有哪些应用?

  • Python 是一种高级编程语言,它可以用于各种应用程序,包括 Web 开发、数据分析、人工智能、机器学习、科学计算、自动化测试等等。
  • Web 开发:
    Python 有许多流行的 Web 框架,如 Django、Flask、Pyramid 等,可以用于构建高性能、可扩展的 Web 应用程序。
  • 数据分析:
    Python 有许多强大的数据分析库,如 Pandas、NumPy、SciPy 等,可以用于处理和分析大量数据。
  • 人工智能和机器学习:
    Python 有许多流行的机器学习库,如 TensorFlow、PyTorch、Scikit-learn 等,可以用于构建各种人工智能和机器学习应用程序。
  • 科学计算:
    Python 有许多科学计算库,如 Matplotlib、SymPy、SciPy 等,可以用于解决各种科学计算问题。
  • 自动化测试:
    Python 有许多自动化测试框架,如 Selenium、Pytest 等,可以用于自动化测试各种应用程序。
  • 游戏开发:
    Pygame 是一个流行的 Python 库,用于游戏开发。

Python 的局限性?

  1. 速度:
    Python 是解释性语言,因此比编译性语言慢。这意味着 Python 在处理大量数据时可能会变得很慢。为了解决这个问题,可以使用 Cython 或 PyPy 等工具来加速 Python 代码。
  2. 内存使用:
    Python 使用的内存比其他语言多。这是因为 Python 使用垃圾回收机制来管理内存,这需要更多的内存。
  3. 并发性:
    Python 的并发性不如其他语言。这是因为 Python 的全局解释器锁(GIL)限制了同一时间只能有一个线程执行 Python 字节码。这意味着 Python 不能充分利用多核处理器。
  4. 移动开发:
    Python 在移动开发方面的支持不如其他语言。虽然有一些移动开发框架,如 Kivy 和 PyQt,但它们的性能和功能有限。
  5. 可靠性:
    Python 的动态类型系统可能导致一些错误在运行时才能被发现。这使得 Python 在某些情况下不够可靠。

Python 代码是如何执行的?

  1. 词法分析:将代码分解成单词或符号,这些单词或符号被称为 “标记”。
  2. 语法分析:将标记转换为语法树,语法树表示代码的结构。
  3. 编译:将语法树转换为字节码,字节码是一种中间形式,可以在不同的平台上运行。
  4. 解释:将字节码转换为机器码,机器码是计算机可以直接执行的指令。
  • Python 的解释器是一种解释器,它将代码逐行解释并执行。当解释器遇到函数调用时,它会将控制权转移到函数的定义,并在函数返回时将控制权返回到调用点。这种行为被称为 “调用栈”。

如何在 Python 中管理内存?

  • 在 Python 中,内存管理是由解释器自动处理的。Python 使用引用计数来跟踪内存中的对象。当对象的引用计数为零时,它们将被垃圾回收。这意味着您不需要手动分配或释放内存。但是,如果您正在处理大型数据集或长时间运行的程序,可能需要考虑使用一些内存管理技巧来优化性能。
  • 以下是一些 Python 中的内存管理技巧:
    • 使用生成器和迭代器而不是列表和循环。这可以减少内存使用量并提高性能。
    • 使用切片而不是完整的列表。切片只返回一个视图,而不是整个列表。
    • 使用 del 语句删除不再需要的对象。这将减少内存使用量并释放不再需要的对象。
    • 使用内置的 gc 模块手动控制垃圾回收。但是,这通常不是必要的,因为 Python 的自动垃圾回收机制通常很好地工作。
    • 使用 numpy 和 pandas 等库来处理大型数据集。这些库使用高效的算法和数据结构来减少内存使用量并提高性能。

解释 Python 的内置数据结构?

  • Python 有四种内置数据结构:列表、元组、字典和集合。
  • 列表(List)是 Python 中最常用的数据结构之一,它是一个有序的集合,可以随时添加、删除和修改其中的元素。列表用方括号 [] 表示,其中的元素用逗号分隔。
    1
    my_list = [1, 2, 3, 'hello', 'world']
  • 元组(Tuple)与列表类似,也是一个有序的集合,但是元组是不可变的,即一旦创建就不能修改。元组用圆括号 () 表示,其中的元素用逗号分隔。
    1
    my_tuple = (1, 2, 3, 'hello', 'world')
  • 字典(Dictionary)是 Python 中另一个非常有用的数据结构,它是一个无序的键值对集合,可以通过键来访问其中的值。字典用花括号 {} 表示,其中的键值对用冒号 : 分隔,不同的键值对用逗号分隔。
    1
    my_dict = {'name': 'Alice', 'age': 25, 'city': 'New York'}
  • 集合(Set)是一个无序的不重复元素集合,可以进行集合运算,如并集、交集、差集等。集合用花括号 {} 表示,其中的元素用逗号分隔。
    1
    my_set = {1, 2, 3, 4, 5}

解释 //、%、* * 运算符?

  • // 运算符表示整数除法,即结果向下取整到最接近的整数。例如,5 // 2 的结果为 2,而 -5 // 2 的结果为 - 3。
  • % 运算符表示模数运算,即返回除法的余数。例如,5%2 的结果为 1,而 -5%2 的结果为 1。
  • ** 运算符表示幂运算,即返回一个数的另一个数次幂。例如,2 ** 3 的结果为 8,而 -3 ** 2 的结果为 - 9。

Python 中的单引号和双引号有什么区别?

  • 在 Python 中,单引号和双引号都可以用来表示字符串。它们之间的区别在于,如果字符串本身包含单引号,则可以使用双引号来表示字符串,反之亦然。
  • 单引号和双引号在 Python 中没有本质的区别,只是用于表示字符串的两种不同方式。

Python 中 append,insert 和 extend 的区别?

  • 在 Python 中,列表是一种非常常见的数据类型。列表有许多内置的方法,其中包括 append,insert 和 extend。这些方法都可以用来向列表中添加元素,但它们的行为略有不同。
  • append 方法将一个元素添加到列表的末尾。
  • insert 方法将一个元素插入到列表的指定位置。
  • extend 方法将一个可迭代对象中的所有元素添加到列表的末尾。
  • 下面是一个简单的示例,演示了这些方法的用法:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    my_list = [1, 2, 3]

    # append方法
    my_list.append(4)
    print(my_list) # 输出 [1, 2, 3, 4]

    # insert方法
    my_list.insert(0, 0)
    print(my_list) # 输出 [0, 1, 2, 3, 4]

    # extend方法
    my_list.extend([5, 6, 7])
    print(my_list) # 输出 [0, 1, 2, 3, 4, 5, 6, 7]

break、continue、pass 是什么?

  • 在 Python 中,break、continue 和 pass 是控制流语句。它们用于控制程序的执行方式。
  • break 语句用于终止循环语句,并跳出整个循环。
  • continue 语句用于跳过当前循环中的剩余语句,并继续下一次循环。
  • pass 语句用于占位,表示没有任何操作。
  • 以下是示例代码:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    # break语句
    for i in range(10):
    if i == 5:
    break
    print(i)

    # continue语句
    for i in range(10):
    if i == 5:
    continue
    print(i)


    # pass语句
    def my_function():
    pass

区分 Python 中的 remove、del 和 pop?

  • 在 Python 中,remove,del 和 pop 都是用于删除列表中的元素的方法。但是它们之间有一些区别。
  • remove() 方法用于删除列表中的指定元素。如果列表中有多个相同的元素,则只删除第一个匹配项。如果要删除所有匹配项,则需要使用循环或列表推导式。如果要删除的元素不在列表中,则会引发 ValueError 异常。
  • del 语句用于删除列表中的指定元素或整个列表。如果使用 del 语句删除整个列表,则列表将不再存在。如果要删除的元素不在列表中,则会引发 IndexError 异常。
  • pop() 方法用于删除列表中指定位置的元素,并返回该元素的值。如果未指定索引,则默认删除最后一个元素。如果要删除的元素不在列表中,则会引发 IndexError 异常。
  • 下面是一个示例,演示如何使用这些方法:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    # 使用remove()方法删除列表中的元素
    my_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    my_list.remove(5)
    print(my_list) # [1, 2, 3, 4, 6, 7, 8, 9, 10]

    # 使用del语句删除列表中的元素
    del my_list[0]
    print(my_list) # [2, 3, 4, 6, 7, 8, 9, 10]

    # 使用pop()方法删除列表中的元素
    popped_element = my_list.pop()
    print(popped_element) # 10
    print(my_list) # [2, 3, 4, 6, 7, 8, 9]

举例说明 Python 中的 range 函数?

  • Python 中的 range 函数可以用来生成一个整数序列,常用于 for 循环中。range 函数有三种使用方式:
    1. range(stop) 生成一个从 0 开始到 stop-1 的整数序列,步长为 1。
    2. range(start, stop) 生成一个从 start 开始到 stop-1 的整数序列,步长为 1。
    3. range(start, stop, step) 生成一个从 start 开始到 stop-1 的整数序列,步长为 step。
  • 例如,range(5) 生成的序列为 [0, 1, 2, 3, 4]range(1, 5) 生成的序列为 [1, 2, 3, 4]range(1, 10, 2) 生成的序列为 [1, 3, 5, 7, 9]

== 和 is 的区别是?

  • == 用于比较两个对象的值是否相等,而 is 用于比较两个对象的身份是否相等。
  • 也就是说,== 用于比较值,而 is 用于比较对象的标识符。当我们使用 == 时,我们比较的是两个对象的值,而当我们使用 is 时,我们比较的是两个对象的标识符。
  • 因此,如果两个对象的值相等,则它们可能具有不同的标识符,因此 is 将返回 False。但是,如果两个对象的标识符相同,则它们的值也必须相同,因此 == 将返回 True。
  • 请注意,对于小整数和字符串等不可变对象,Python 会在内部缓存它们的实例,因此它们的标识符相同。因此,对于这些对象,is 将返回 True,而 == 将返回 True 或 False,具体取决于它们的值是否相等。

如何更改列表的数据类型?

  • 使用 map 函数

    1
    2
    3
    4
    5
    # 将字符串列表转换为整数列表
    string_list = ['1', '2', '3', '4', '5']
    int_list = list(map(int, string_list))
    print(int_list)

    请注意,map 函数返回一个 map 对象,而不是列表。因此,我们需要使用 list() 函数将其转换为列表。
    如果您想将列表中的元素转换为其他数据类型,只需将 int() 函数替换为所需的函数即可。

  • 使用列表推导式

    1
    2
    3
    4
    5
    # 将整数列表转换为字符串列表
    int_list = [1, 2, 3, 4, 5]
    str_list = [str(i) for i in int_list]
    print(str_list)

!= 和 is not 运算符的区别?

  • !=is not 都是用来比较两个值是否不相等的运算符,但是它们的实现方式不同。
  • != 是一个比较运算符,用于比较两个对象的值是否相等。例如,a != b 表示如果 a 的值不等于 b 的值,则返回 True,否则返回 False。
  • is not 是一个身份运算符,用于比较两个对象的身份是否不同。例如,a is not b 表示如果 a 和 b 不是同一个对象,则返回 True,否则返回 False。
  • 在大多数情况下,!=is not 可以互换使用,但是在比较两个对象时,最好使用 is not,因为它比 != 更快,而且更准确。这是因为 is not 比较的是对象的身份,而 != 比较的是对象的值,而对象的身份比对象的值更容易比较。

Python 是否有 main 函数?

  • 在 Python 中,没有像 C++ 或 Java 中的 main 函数。
  • 相反,Python 文件的顶部通常包含一些全局定义,然后是一些函数定义。如果要在 Python 文件中编写可执行代码,可以使用以下代码:
    1
    2
    3
    if __name__ == "__main__":
    # 执行代码

  • 这个 if 语句检查当前文件是否被用作模块导入,如果不是,则执行代码块。这是 Python 中的一种常见惯例,可以确保模块导入时不会执行不必要的代码。

什么是 lambda 函数?

  • Lambda 函数是一种匿名函数,可以在一行内定义。
  • 它们通常用于需要一个函数,但只需要使用一次的情况。Lambda 函数可以接受任意数量的参数,但只能有一个表达式。这个表达式被求值并返回。
  • Lambda 函数可以像普通函数一样调用,但是它们通常作为参数传递给高阶函数,如 map、filter、reduce 等。
    1
    2
    3
    add = lambda x, y: x + y
    print(add(2, 3)) # 输出 5

iterables 和 iterators 之间的区别?

  • iterables 是指可以被迭代的对象,而 iterators 是用于迭代 iterables 的对象。
  • iterables 是可以使用 for 循环进行迭代的对象,而 iterators 是使用 next () 函数进行迭代的对象。iterables 可以被多次迭代,而 iterators 只能被迭代一次。
  • iterables 可以是列表、元组、字典、字符串等,而 iterators 可以使用 iter() 函数从 iterables 中创建。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # iterable
    my_list = [1, 2, 3]
    for i in my_list:
    print(i)

    # iterator
    my_iter = iter(my_list)
    print(next(my_iter))
    print(next(my_iter))
    print(next(my_iter))

解释 Python 中的 Map 函数?

  • map() 函数是 Python 内置的高阶函数之一,它接收一个函数和一个可迭代对象作为参数,返回一个新的可迭代对象,其中每个元素都是将原可迭代对象中的元素应用于函数后的结果。
  • 例如,以下代码将列表中的每个元素加倍:
    1
    2
    3
    4
    numbers = [1, 2, 3, 4, 5]
    doubled = list(map(lambda x: x * 2, numbers))
    print(doubled) # Output: [2, 4, 6, 8, 10]

  • 在这个例子中,map() 函数将 lambda 函数应用于 numbers 列表中的每个元素,将其加倍,并返回一个新的可迭代对象 doubled,其中包含加倍后的结果。

解释 Python 中的 Filter 函数?

  • 在 Python 中,filter() 函数是一个内置函数,它可以用于过滤序列中的元素。它接受两个参数:
    • 一个函数,用于定义过滤规则
    • 一个序列,需要过滤的序列
  • filter() 函数返回一个迭代器,其中包含序列中所有符合过滤规则的元素。
  • 以下是一个使用 filter() 函数的示例:
    1
    2
    3
    4
    numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    even_numbers = list(filter(lambda x: x % 2 == 0, numbers))
    print(even_numbers)

解释 Python 中 reduce 函数?

  • reduce() 函数是 Python 内置的一个高阶函数,它接收一个函数和一个序列作为参数,然后依次将函数作用在序列的每个元素上,得到一个最终的结果。
  • 工作原理:首先将序列的前两个元素传入函数,得到一个结果。然后将这个结果和序列的下一个元素传入函数,得到另一个结果。依此类推,直到将整个序列都处理完毕,得到最终的结果。
  • 例如,下面的代码使用 reduce() 函数计算了一个序列的和:
    1
    2
    3
    4
    5
    6
    from functools import reduce

    numbers = [1, 2, 3, 4, 5]
    result = reduce(lambda x, y: x + y, numbers)
    print(result) # 输出 15

  • 如果序列中只有一个元素,那么 reduce() 函数将直接返回这个元素,而不会调用传入的函数。如果序列为空,那么 reduce() 函数将会抛出一个 TypeError 异常。

解释 Python 中的 pickling 和 unpickling?

  • 在 Python 中,pickling 和 unpickling 是将 Python 对象转换为字节流的过程,以便它们可以在不同的 Python 解释器之间传输或保存到磁盘。
  • pickling 是将 Python 对象转换为字节流的过程,而 unpickling 是将字节流转换回 Python 对象的过程。
  • 这对于在不同的 Python 程序之间共享数据或将数据保存到磁盘以供以后使用非常有用。您可以使用 Python 的 pickle 模块来执行 pickling 和 unpickling 操作。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    import pickle

    # 将对象转储到文件中
    with open('data.pickle', 'wb') as f:
    pickle.dump(my_object, f)

    # 从文件中加载对象
    with open('data.pickle', 'rb') as f:
    my_object = pickle.load(f)

    在这里,'data.pickle' 是要保存数据的文件名,my_object 是要保存的 Python 对象。pickle.dump() 将对象转储到文件中,pickle.load() 将其加载回来。注意,文件必须以二进制模式打开(即 'wb''rb')。

解释 *args**kwargs

  • *args**kwargs 是 Python 中的可变参数。

  • *args 用于传递任意数量的非关键字参数,它们将作为元组传递给函数。

  • **kwargs 用于传递任意数量的关键字参数,它们将作为字典传递给函数。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    def example_function(*args, **kwargs):
    print('*args:')
    for arg in args:
    print(arg)
    print('**kwargs:')
    for key, value in kwargs.items():
    print(f"{key}: {value}")


    example_function(1, 2, 3, 4, 5, foo='bar', baz='qux')


    """
    输出:
    *args:
    1
    2
    3
    4
    5
    **kwargs:
    foo: bar
    baz: qux
    """

    在这个例子中,example_function 接受任意数量的非关键字参数和关键字参数。非关键字参数将作为元组传递给函数,而关键字参数将作为字典传递给函数。函数体中的第一个循环遍历元组中的每个参数并打印它们,第二个循环遍历字典中的每个键值对并打印它们。

解释 re 模块的 split ()、sub ()、subn () 方法?

  • re 模块是 Python 中的正则表达式模块,提供了很多方法来处理字符串。其中,split()sub()subn() 方法都是用来替换字符串的。

  • split() 方法:根据正则表达式分隔符,将字符串分割成一个列表。例如:

    1
    2
    3
    4
    5
    6
    import re

    text = "apple, banana, cherry"
    result = re.split(",\s", text)
    print(result) # ['apple', 'banana', 'cherry']

  • sub() 方法:使用指定的替换字符串替换所有匹配的字符串。例如:

    1
    2
    3
    4
    5
    6
    import re

    text = "apple, banana, cherry"
    result = re.sub(",\s", "; ", text)
    print(result) # 'apple; banana; cherry'

  • subn() 方法:与 sub() 方法类似,但返回一个元组,其中第一个元素是替换后的字符串,第二个元素是替换的次数。例如:

    1
    2
    3
    4
    5
    6
    import re

    text = "apple, banana, cherry"
    result = re.subn(",\s", "; ", text)
    print(result) # ('apple; banana; cherry', 2)

如何使用索引来反转 Python 中的字符串?

  • 要反转 Python 中的字符串,可以使用切片操作符。切片操作符允许您从序列中提取子序列。要反转字符串,您可以使用以下代码:
    1
    2
    3
    4
    my_string = "hello world"
    reversed_string = my_string[::-1]
    print(reversed_string)

  • 这将输出 “dlrow olleh”,即原始字符串的反转版本。在这里,[::-1] 表示从字符串的末尾开始,每次向后移动一个步骤,直到到达字符串的开头。

类和对象有什么区别?

  • 类和对象是面向对象编程的两个重要概念。
  • 类是一个模板,它定义了一组属性和方法,而对象是类的实例。类是一个抽象的概念,它描述了对象的共同特征,而对象则是具体的实体,它具有类所描述的属性和方法。
  • 在 Python 中,我们可以使用 class 关键字来定义一个类。例如,下面是一个简单的类定义:
    1
    2
    3
    4
    5
    6
    7
    8
    class Person:
    def __init__(self, name, age):
    self.name = name
    self.age = age

    def say_hello(self):
    print("Hello, my name is", self.name, "and I am", self.age, "years old.")

    在这个例子中,我们定义了一个名为 Person 的类,它有两个属性(name 和 age)和一个方法(say_hello)。

  • 我们可以使用这个类来创建一个 Person 对象,如下所示:
    1
    2
    3
    person = Person("Alice", 25)
    person.say_hello()

    这将输出以下内容:Hello, my name is Alice and I am 25 years old.

  • 因此,类和对象之间的区别在于,类是一个模板,它描述了对象的共同特征,而对象是类的实例,它具有类所描述的属性和方法。

你对 Python 类中的 self 有什么了解?

  • 在 Python 中,self 是一个指向类实例的引用。它是类方法的第一个参数,用于访问类的属性和方法。
  • 当你调用一个类的方法时,Python 会自动将该实例作为 self 参数传递给方法。这样,你就可以在方法中访问该实例的属性和方法。
  • 例如,如果你有一个名为 "Person" 的类,它有一个名为 "name" 的属性和一个名为 "say_hello" 的方法,你可以这样访问它们:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class Person:
    def __init__(self, name):
    self.name = name

    def say_hello(self):
    print("Hello, my name is", self.name)


    person = Person("Alice")
    person.say_hello() # 输出 "Hello, my name is Alice"

  • 在这个例子中,"self" 参数是在 "init"方法中使用的,它用于设置"name"属性。在"say_hello"方法中,"self"参数用于访问"name" 属性并打印出问候语。

__init__在 Python 中有什么用?

  • __init__ 是 Python 中的一个特殊方法,用于在创建对象时进行初始化操作。
  • 它是一个构造函数,当创建一个类的实例时,__init__ 方法会自动调用。在 __init__ 方法中,您可以设置对象的属性和执行其他必要的初始化操作。
  • 例如,如果您有一个名为 Person 的类,您可以在 __init__ 方法中设置 name 和 age 属性,如下所示:
    1
    2
    3
    4
    5
    class Person:
    def __init__(self, name, age):
    self.name = name
    self.age = age

  • 在这个例子中,当您创建一个 Person 对象时,您需要提供一个 name 和 age 参数。这些参数将被传递给 __init__ 方法,并用于设置 name 和 age 属性。例如:
    1
    2
    3
    4
    person = Person("Alice", 30)
    print(person.name) # 输出 "Alice"
    print(person.age) # 输出 30

  • 因此,__init__ 方法是一个非常有用的方法,它允许您在创建对象时执行任何必要的初始化操作,并设置对象的属性。

解释一下 Python 中的继承?

  • 在 Python 中,继承是一种创建新类的方式,新类可以继承一个或多个现有类的属性和方法。继承的语法如下:
    1
    2
    3
    class ChildClass(ParentClass):
    # ChildClass definition

  • 这里,ChildClass 是新类的名称,ParentClass 是要继承的现有类的名称。子类可以访问父类的属性和方法,也可以重写它们或添加新的属性和方法。例如,如果我们有一个 Person 类,它有一个 name 属性和一个 say_hello 方法:
    1
    2
    3
    4
    5
    6
    7
    class Person:
    def __init__(self, name):
    self.name = name

    def say_hello(self):
    print(f"Hello, my name is {self.name}")

  • 我们可以创建一个 Student 类,它继承 Person 类的属性和方法,并添加一个新的 grade 属性:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    class Student(Person):
    def __init__(self, name, grade):
    super().__init__(name)
    self.grade = grade

    def say_hello(self):
    super().say_hello()
    print(f"I am in grade {self.grade}")

  • 在这个例子中,Student 类继承了 Person 类的 name 属性和 say_hello 方法,并添加了一个新的 grade 属性。它还重写了 say_hello 方法,以便在打印 Person 类的问候语之后,还会打印学生的年级。

Python 中 OOPS 是什么?

  • 在 Python 中,OOPS 代表面向对象编程。
  • 它是一种编程范例,其中数据和相关操作被组织在一个对象中。对象是类的实例,类是一个模板或蓝图,用于创建对象。在 Python 中,您可以使用类来创建自己的对象,并使用这些对象来调用类中定义的方法。以下是一个简单的 Python 类示例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class Person:
    def __init__(self, name, age):
    self.name = name
    self.age = age

    def say_hello(self):
    print("Hello, my name is", self.name, "and I am", self.age, "years old.")


    person = Person("Alice", 25)
    person.say_hello()

  • 在这个例子中,我们定义了一个名为 Person 的类,它有两个属性:name 和 age。我们还定义了一个名为 say_hello 的方法,它将打印出一个问候语,其中包含对象的名称和年龄。最后,我们创建了一个名为 person 的对象,并调用了它的 say_hello 方法。

什么是抽象?

  • 在 Python 中,抽象是指将代码中的通用概念提取出来,以便在不同的上下文中重复使用。这可以通过使用抽象类和接口来实现。
  • 抽象类是一个不能被实例化的类,它的主要目的是为其子类提供一个通用的接口。接口是一个只包含方法签名的类,它定义了一个类应该实现的方法。
  • 在 Python 中,抽象类可以通过继承 abc.ABC 类来创建。接口可以通过定义一个只包含方法签名的类来创建。
  • 在 Python 中,接口通常使用 abc.ABCMeta 元类来创建。以下是一个抽象类和接口的示例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    import abc


    class AbstractClass(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def do_something(self):
    pass


    class Interface(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def do_something(self):
    pass

  • 在这个例子中,AbstractClass 和 Interface 都定义了一个 do_something 方法,但是它们没有提供具体的实现。这使得它们成为抽象类和接口。任何继承 AbstractClass 或实现 Interface 的类都必须提供 do_something 方法的具体实现。这使得代码更加模块化和可重用。

什么是封装?

  • 封装是面向对象编程中的一种重要概念,它指的是将数据和方法包装在一起,形成一个类。
  • 封装的目的是保护数据,防止外部直接访问和修改,只能通过类提供的接口进行访问和修改。
  • 在 Python 中,封装是通过属性和方法来实现的。属性是类中的变量,方法是类中的函数。通过将属性和方法设置为私有的,可以实现封装。私有属性和方法只能在类内部访问,外部无法访问。
  • 在 Python 中,将属性和方法设置为私有的方法是在属性或方法名前加上两个下划线 "__"。例如:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    class Person:
    def __init__(self, name, age):
    self.__name = name
    self.__age = age

    def get_name(self):
    return self.__name

    def get_age(self):
    return self.__age

    def set_name(self, name):
    self.__name = name

    def set_age(self, age):
    self.__age = age

  • 在这个例子中,Person 类有两个私有属性 __name__age,以及四个公有方法 get_name、get_age、set_name 和 set_age。通过这些方法,可以访问和修改私有属性。例如:
    1
    2
    3
    4
    5
    person = Person("Alice", 25)
    print(person.get_name()) # 输出 "Alice"
    person.set_age(26)
    print(person.get_age()) # 输出 26

  • 这样,就实现了对属性的封装,保护了数据的安全性。

什么是多态?

  • 多态是指同一个类的对象在不同情况下有不同的表现形式。
  • 在 python 中,多态可以通过继承和方法重写来实现。当子类继承父类并重写父类的方法时,子类的对象可以使用子类的方法,而不是父类的方法。这种情况下,子类的对象表现出了不同于父类的行为,实现了多态。
  • 举个例子,假设有一个 Animal 类和一个 Dog 类,Dog 类继承自 Animal 类并重写了 Animal 类的 speak() 方法。当我们创建一个 Animal 对象和一个 Dog 对象并调用它们的 speak() 方法时,Animal 对象会调用 Animal 类的 speak() 方法,而 Dog 对象会调用 Dog 类的 speak() 方法。这就是多态的体现。
  • Dog 类继承自 Animal 类并重写了 speak() 方法:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    class Animal:
    def speak(self):
    print("Animal is speaking")


    class Dog(Animal):
    def speak(self):
    print("Dog is barking")


    a = Animal()
    d = Dog()

    a.speak() # 输出 "Animal is speaking"
    d.speak() # 输出 "Dog is barking"

什么是 Python 中的猴子补丁?

  • 猴子补丁是指在运行时动态修改类或模块的代码,而不改变源代码。这种技术在 Python 中非常常见,因为 Python 是一种动态语言,允许在运行时修改代码。
  • 在 Python 中,可以使用猴子补丁来修改类的方法或属性。例如,假设我们有一个名为 MyClass 的类,它有一个名为 my_method 的方法。我们可以使用猴子补丁来修改 my_method 的行为,如下所示:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class MyClass:
    def my_method(self):
    print("Original behavior")


    def monkey_patch(self):
    print("Patched behavior")


    MyClass.my_method = monkey_patch

    obj = MyClass()
    obj.my_method() # Output: Patched behavior

  • 在这个例子中,我们定义了一个名为 monkey_patch 的函数,它将替换 MyClassmy_method 方法。我们然后将 MyClass.my_method 设置为 monkey_patch 函数,这样在调用 obj.my_method()时,将输出 Patched behavior 而不是 Original behavior
  • 猴子补丁可以用于许多不同的情况,例如在测试期间替换依赖项,或者在运行时修改第三方库的行为。但是,需要注意的是,猴子补丁可能会导致代码变得难以理解和维护,因此应该谨慎使用。

Python 支持多重继承吗?

  • Python 是支持多重继承的。
  • 多重继承是指一个类可以从多个父类中继承属性和方法。在 Python 中,如果一个类继承了多个父类,那么它就拥有了所有父类的属性和方法。
  • 例如,下面的代码演示了一个类从两个父类中继承属性和方法的情况:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    class Parent1:
    def method1(self):
    print("This is method 1 of Parent1")


    class Parent2:
    def method2(self):
    print("This is method 2 of Parent2")


    class Child(Parent1, Parent2):
    pass


    c = Child()
    c.method1() # Output: This is method 1 of Parent1
    c.method2() # Output: This is method 2 of Parent2

  • 在上面的代码中,Child 类继承了 Parent1 和 Parent2 两个父类,因此它可以调用 Parent1 和 Parent2 中的所有方法。

Python 中的 zip 函数是什么?

  • zip() 函数是 Python 内置函数之一,它接受任意多个(包括 0 个和 1 个)序列作为参数,返回一个 tuple 列表,其中第 i 个 tuple 包含每个参数序列的第 i 个元素。如果传入参数的长度不等,则返回列表长度与最短的序列相同。
  • 例如:
    1
    2
    3
    4
    5
    6
    7
    8
    a = [1, 2, 3]
    b = [4, 5, 6]
    c = [7, 8, 9, 10]
    zipped = zip(a, b)
    print(list(zipped)) # [(1, 4), (2, 5), (3, 6)]
    zipped = zip(a, b, c)
    print(list(zipped)) # [(1, 4, 7), (2, 5, 8), (3, 6, 9)]

  • 在 Python 3 中,zip() 函数返回的是一个 zip 对象,需要使用 list() 函数将其转换为列表。

Python 中的 all 函数是什么?

  • all() 函数用于判断给定的可迭代参数 iterable 中的所有元素是否都为 TRUE,如果是返回 True,否则返回 False。如果可迭代对象为空,返回 True。
  • 语法:
    1
    all(iterable)
  • 参数: iterable -- 元组或列表。返回值: 如果 iterable 的所有元素不为 0、''、False 或者 iterable 为空,返回 True,否则返回 False
  • 示例:
    1
    2
    3
    4
    print(all(['a', 'b', 'c', 'd']))  # True
    print(all(['a', 'b', '', 'd'])) # False
    print(all([0, 1, 2, 3])) # False
    print(all([])) # True

HTTP 和 HTTPS 的区别?

  • HTTP 和 HTTPS 都是应用层协议,HTTP 协议传输的数据都是明文的,HTTPS 协议传输的数据都是加密的,所以 HTTPS 协议比 HTTP 协议更加安全。
  • HTTPS 协议需要使用 SSL 证书,而 HTTP 协议不需要。
  • HTTPS 协议的默认端口是 443,而 HTTP 协议的默认端口是 80。

Python 中常用的线程锁?

  1. Lock:最基本的锁,一次只能被一个线程持有,其他线程需要等待锁被释放后才能获得锁。
  2. RLock:可重入锁,同一个线程可以多次获得同一把锁,但是需要释放相同次数的锁才能真正释放锁。
  3. Semaphore:信号量,可以允许多个线程同时访问同一资源。
  4. Event:事件锁,一个线程等待另一个线程的状态改变。
  5. Condition:条件锁,可以在某些条件下阻塞线程,直到某个条件满足时唤醒线程。
  6. Barrier:屏障锁,可以让多个线程在某个点上等待,直到所有线程都到达这个点时才能继续执行。
  7. Timer:定时器锁,可以在指定时间后自动释放锁。

Python 中线程之间如何通信?

  • 在 Python 中,线程之间可以通过共享内存或者消息传递来进行通信。
  • 共享内存是指多个线程可以访问同一块内存区域,通过读写该内存区域来进行通信。Python 中可以使用 threading.Lock 来实现线程之间的同步,避免多个线程同时访问同一块内存区域导致的数据竞争问题。
    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
    import threading

    # 共享内存
    shared_data = []

    # 创建锁
    lock = threading.Lock()

    def worker():
    global shared_data
    with lock:
    shared_data.append("some data")

    # 创建线程
    threads = []
    for i in range(10):
    t = threading.Thread(target=worker)
    threads.append(t)

    # 启动线程
    for t in threads:
    t.start()

    # 等待线程结束
    for t in threads:
    t.join()

    # 打印共享内存中的数据
    print(shared_data)

    在上面的示例中,我们创建了一个共享内存 shared_data,然后创建了 10 个线程,每个线程都会向 shared_data 中添加一些数据。由于多个线程同时访问 shared_data 可能会导致数据竞争问题,因此我们使用了 threading.Lock 来进行同步,保证每个线程都能够安全地访问 shared_data

  • 消息传递是指线程之间通过发送和接收消息来进行通信。Python 中可以使用 queue.Queue 来实现线程之间的消息传递。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    import threading
    import queue

    # 创建消息队列
    message_queue = queue.Queue()

    def worker():
    while True:
    # 从消息队列中获取消息
    message = message_queue.get()
    print("Received message:", message)

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

    # 发送消息
    message_queue.put("Hello, world!")

    在上面的示例中,我们创建了一个消息队列 message_queue,然后创建了一个线程 worker,该线程会不断地从消息队列中获取消息并打印出来。我们还创建了一个线程,向消息队列中发送了一条消息。由于消息队列是线程安全的,因此我们不需要使用锁来进行同步。

Python 中的访问权限?

  • Python 中的访问权限是指控制类中属性和方法的访问权限的机制。在 Python 中,访问权限并不是严格强制执行的,而是通过一些约定来实现的。
  • Python 中的访问权限约定有两种:
    1. 单下划线前缀(_):将属性或方法名称以单下划线开头,表示它是受保护的(protected),应该被视为私有属性或方法。这种约定告诉其他程序员不要直接访问这些属性或方法。
    2. 双下划线前缀(__):将属性或方法名称以双下划线开头,表示它是私有的(private),不应该被从外部访问。在 Python 中,双下划线前缀会使属性或方法名称进行名称重整(name mangling),以避免名称冲突。
  • 以下是一个示例,展示如何使用单下划线和双下划线前缀来实现访问权限:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    class MyClass:
    def __init__(self):
    self.public_attr = 0 # 公有属性
    self._protected_attr = 1 # 受保护的属性
    self.__private_attr = 2 # 私有属性

    def public_method(self):
    print("This is a public method")

    def _protected_method(self):
    print("This is a protected method")

    def __private_method(self):
    print("This is a private method")


    obj = MyClass()
    print(obj.public_attr) # 输出 0
    print(obj._protected_attr) # 输出 1
    print(obj._MyClass__private_attr) # 输出 2,名称重整后的属性名称
    obj.public_method() # 输出 This is a public method
    obj._protected_method() # 输出 This is a protected method
    obj._MyClass__private_method() # 输出 This is a private method,名称重整后的方法名称
  • 从输出结果可以看出,我们可以通过对象访问公有属性和方法、受保护的属性和方法,但最好不要直接访问它们。私有属性和方法不能通过对象直接访问,只能通过名称重整后的名称来访问。
  • 需要注意的是,Python 的访问权限机制是基于约定的,并不是严格执行的。程序员可以选择忽略这些约定,直接访问对象的属性和方法,从而绕过访问权限。这是 Python 语言设计上的一个特点,可以让程序员更加灵活地使用代码。