Notice: Function _load_textdomain_just_in_time was called incorrectly. Translation loading for the wp-pagenavi domain was triggered too early. This is usually an indicator for some code in the plugin or theme running too early. Translations should be loaded at the init action or later. Please see Debugging in WordPress for more information. (This message was added in version 6.7.0.) in /var/www/blog.zhujinhui.net/wp-includes/functions.php on line 6114

Notice: 函数 _load_textdomain_just_in_time 的调用方法不正确twentyseventeen 域的翻译加载触发过早。这通常表示插件或主题中的某些代码运行过早。翻译应在 init 操作或之后加载。 请查阅调试 WordPress来获取更多信息。 (这个消息是在 6.7.0 版本添加的。) in /var/www/blog.zhujinhui.net/wp-includes/functions.php on line 6114
面试 – 煅魂-JeffreyChu的修炼屋

面试必问(2):迭代器和生成器是什么?有什么区别?

迭代器(Iterator)和生成器(Generator)都是 Python 中与迭代操作相关的概念,但它们在功能和使用上有所不同。

概念

迭代器(Iterator)
  • 迭代器是一种对象,它允许你逐一遍历某个数据集合。一个对象是迭代器,必须实现两个方法:__iter__()__next__()
  • __iter__() 返回迭代器对象本身,使得对象可以用于循环遍历。
  • __next__() 在每次调用时返回集合中的下一个元素。如果没有元素可以返回,则会抛出 StopIteration 异常。
    你可以通过调用 iter() 函数来获取对象的迭代器,例如:iter(my_list) 将返回一个列表 my_list 的迭代器。

注意:可迭代对象(iterable) 和迭代器(Iterator)是不同概念,可迭代对象(iterable)是没有实现 __next__()方法的,但有__iter__() ,所以像list、dict、str这些都不是迭代器,而是可迭代对象。

下面用一段代码展示下迭代器和生成器

# a是可迭代对象
a = [i for i in range(20)]
# iter(a)才是迭代器
it_dir = dir(iter(a))
# 检查iter(a)迭代器是否含有__iter__和__next__
print("__iter__" in it_dir and "__next__" in it_dir)


def generator():
    for i in range(10):
        yield i


# g是迭代器,也是生成器
g = generator()
g_dir = dir(g)
# 检查g生成器(迭代器)是否含有__iter__和__next__
print("__iter__" in g_dir and "__next__" in g_dir)

# Outputs
# True
# True
生成器(Generator):
  • 生成器是一个特殊类型迭代器,用于生成序列。它是通过函数来定义的,但与普通函数不同,它使用 yield 关键字来返回值。
  • 当生成器函数被调用时,它并不立即执行函数体,而是返回一个生成器对象。这个生成器对象在每次调用其 __next__() 方法时,执行生成器函数体中的代码,直到遇到 yield,然后暂停执行并返回 yield 后面的值。
  • 生成器可以保存函数的局部状态,因此当函数被再次调用时,它从上一次暂停的地方继续执行。
两者区别:
  • 定义方式:迭代器是通过类来定义并实现 __iter__()__next__() 方法;生成器是通过使用 yield 关键字的函数来定义的。不过生成器实际上是一种特殊的迭代器。
  • 使用方式:迭代器需要通过实现类来定义和管理数据迭代,而生成器通过函数的代码执行和 yield 表达式自动生成数据。
  • 内存效率:生成器通常更节省内存,因为它们会根据需要产生值,而不是一次性生成所有值。

应用

迭代器和生成器在 Python 中都有重要的作用,主要用于数据的迭代、处理和生成。它们在编写可读性和内存效率更高的代码方面特别有用。

大数据处理/惰性计算

当你处理大量数据时,使用生成器可以避免将整个数据集加载到内存中,有些数据需要惰性计算(延迟计算),从而节省内存。例如,逐行读取大文件或者逐个处理大型数据集。

比如统计大文件有多少行:

def read_large_file(file_path):
    """使用生成器逐行读取大型文件。"""
    try:
        with open(file_path, 'r') as file:
            for line in file:
                # 在这里使用yield暂停并返回每一行
                yield line.strip()  # 去除每行的换行符
    except FileNotFoundError:
        print(f"文件 {file_path} 未找到。")
        return

def process_large_file(file_path):
    """示例函数,用于演示如何使用生成器处理大型文件。"""
    line_count = 0  # 行计数器
    for line in read_large_file(file_path):
        # 对每一行进行处理
        # 这里可以添加你自己的处理逻辑
        print(f"Processing line {line_count}: {line}")
        # 示例:简单地统计行数
        line_count += 1
    print(f"Total lines processed: {line_count}")

if __name__ == "__main__":
    # 在这里指定要读取的文件路径
    file_path = "large_file.txt"
    # 使用process_large_file函数处理大文件
    process_large_file(file_path)
流式数据处理:

生成器适用于流式数据处理,数据流源源不断地产生,你可以通过生成器逐一处理它们。例如,读取数据流(如网络流或数据库流)并逐一处理。

比如下面这段源源不断从redis队列拉取数据:

import time
import redis

def redis_stream_generator(redis_client, queue_name):
    """生成器函数,用于从 Redis 队列中流式获取数据。"""
    while True:
        # 尝试从 Redis 队列中弹出一个数据项
        data = redis_client.blpop(queue_name, timeout=2)
        if data:
            # `data` 是一个元组 (queue_name, data)
            _, value = data
            # 使用 yield 返回数据
            yield value
        else:
            # 如果队列为空,睡眠2秒
            time.sleep(2)

def process_redis_stream(data_stream):
    """处理 Redis 数据流的示例函数。"""
    for data in data_stream:
        # 对数据进行处理
        print(f"Processing data: {data}")

        # 在这里添加你的数据处理逻辑
        # 例如:对数据进行统计、计算等

        # 如果要在特定条件下停止处理,可以在这里添加逻辑

if __name__ == "__main__":
    # 连接到 Redis 服务器
    redis_client = redis.StrictRedis(host='localhost', port=6379, db=0)
    # 定义 Redis 队列的名称
    queue_name = 'my_queue'
    # 创建 Redis 数据流生成器
    data_stream = redis_stream_generator(redis_client, queue_name)
    # 处理 Redis 数据流
    process_redis_stream(data_stream)
无限序列:

生成器可以用来生成无限序列,例如斐波那契数列、素数序列等。这些序列往往无法通过列表来表示,因为它们是无限的,但可以通过生成器逐
一生成和处理。

def fibonacci_generator():
    """生成器函数,用于生成斐波那契数列。"""
    a, b = 0, 1
    while True:
        # 使用 yield 返回当前的斐波那契数
        yield a
        # 计算下一个斐波那契数
        a, b = b, a + b


def process_fibonacci_sequence(n):
    """示例函数,用于演示如何处理生成的斐波那契数列。"""
    # 创建 Fibonacci 数列的生成器
    fibonacci_gen = fibonacci_generator()
    print(f"First {n} numbers in the Fibonacci sequence:")
    # 使用生成器逐个获取前 n 个斐波那契数
    for _ in range(n):
        fibonacci_number = next(fibonacci_gen)
        print(fibonacci_number)


if __name__ == "__main__":
    # 指定要生成的斐波那契数的个数
    n = 10  # 例如,获取前 10 个斐波那契数
    # 处理并打印前 n 个斐波那契数
    process_fibonacci_sequence(n)
组合生成器:

通过生成器的组合,你可以创建复杂的数据流水线。例如,将多个生成器组合在一起,进行数据的提取、转换和加载(ETL)操作。

协程和异步编程:

在 Python 的异步编程中,生成器和 async、await 等关键字结合使用,用于实现异步操作和协程。

比如下面这段异步批量采集某个api数据

import aiohttp
import asyncio

async def fetch_url(url):
    """
    异步函数,用于发送网络请求并返回响应的内容。
    """
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as response:
            # 获取响应内容并返回
            content = await response.text()
            return content

async def main():
    """
    主函数,演示如何并发发送多个网络请求。
    """
    # 定义一组要请求的 URL 列表
    urls = [
        'http://xmishu.zhujinhui.net/api/hot_words',
        'http://xmishu.zhujinhui.net/api/hot_words',
        'http://xmishu.zhujinhui.net/api/hot_words',
    ]

    # 创建任务列表,使用列表推导式创建多个异步任务
    tasks = [fetch_url(url) for url in urls]

    # 使用 asyncio.gather 运行所有任务,并等待它们完成
    results = await asyncio.gather(*tasks)

    # 输出所有请求的结果
    for i, result in enumerate(results):
        print(f"Response from URL {urls[i]}:")
        print(result[:100])  # 只打印前100个字符

# 使用 asyncio.run 来运行主函数
if __name__ == '__main__':
    asyncio.run(main())

总结

迭代器是通过类来定义并实现 __iter__()__next__() 方法,生成器是一个使用 yield 关键字来返回值的特殊函数,也是一种特殊类型迭代器,它们在处理一些大数据问题的处理上都更能节省内存。

面试必问(1):python类的init和new的区别和使用场景

在 Python 中,__init____new__ 是两个在类的实例化过程中发挥重要作用的方法,但它们各自的作用和使用场景是不同的。

概念区别

__new__

__new__ 是一个静态方法,用于创建实例。它是类的实例化过程中被调用的第一个方法。__new__ 接受类本身(cls)和其他参数,并返回一个实例。一般情况下,除非你有特殊需求,你可能很少需要覆盖 __new__ 方法,写一些底层框架代码的时候可能会用上。

__init__

__init__ 是实例初始化方法。它在 __new__ 创建实例后被调用,用于初始化实例的属性和设置状态。
__init__ 返回 None,它不负责返回实例,也就是说__init__中的self参数其实是__new__方法的返回值。
以下是一个简单示例,展示了__new____init__ 的使用与区别:

class MyClass:
    def __new__(cls, *args, **kwargs):
        print("Calling __new__")
        instance = super(MyClass, cls).__new__(cls)
        # 在这里,你可以控制实例的创建,比如使用单例模式或者工厂模式
        return instance

    def __init__(self, value):
        print("Calling __init__")
        self.value = value

# 创建实例
my_instance = MyClass(42)

# Outputs
# Calling __new__
# Calling __init__

__new__ 方法首先被调用,负责创建实例,然后 __init__ 方法负责初始化实例。

常用使用场景

__new__的使用

new 方法在 Python 中可以用于控制类的实例创建过程,包括实现单例模式工厂模式缓存实例以及预防不合适的实例化等。以下是每个使用场景的代码示例:

单例模式

在单例模式中,__new__ 方法确保类只会有一个实例,并返回同一个实例给每次实例化调用,这个是__new__最常用的场景。

class Singleton:
    _instance = None

    def __new__(cls, *args, **kwargs):
        if cls._instance is None:
            cls._instance = super(Singleton, cls).__new__(cls)
        return cls._instance

# 使用示例
s1 = Singleton()
s2 = Singleton()


print(s1 == s2)  # 输出 True,说明两者是同一个实例
工厂模式

在工厂模式中,new 方法可以根据传入的参数返回不同类型的实例。

class AuthStrategy:
    def authenticate(self):
        raise NotImplementedError


class WechatStrategy(AuthStrategy):
    def authenticate(self):
        return "Wechat authenticate"


class EmailStrategy(AuthStrategy):
    def authenticate(self):
        return "Email authenticate"


class AnimalFactory:
    def __new__(cls, auth_type):
        if auth_type == 'wechat':
            return WechatStrategy()
        elif auth_type == 'email':
            return EmailStrategy()
        else:
            raise ValueError("Unknown animal type")


# 使用示例
w = AnimalFactory('wechat')
e = AnimalFactory('email')

print(w.authenticate())  # 输出 "Wechat authenticate"
print(e.authenticate())  # 输出 "Email authenticate"
缓存实例

在缓存实例中,new 方法可以缓存创建的实例,并在重复创建时返回缓存的实例。

class CacheObject:
    _cache = {}

    def __new__(cls, key):
        if key in cls._cache:
            return cls._cache[key]
        else:
            instance = super(CacheObject, cls).__new__(cls)
            cls._cache[key] = instance
            return instance

# 使用示例
obj1 = CacheObject('key1')
obj2 = CacheObject('key1')

print(obj1 == obj2)  # 输出 True,说明两者是同一个实例
预防不合适的实例化

在预防不合适的实例化场景中,new 方法可以通过抛出异常来防止实例化不合适的对象。

class RestrictedInstantiation:
    def __new__(cls, flag):
        if not flag:
            raise ValueError("flag必须为True")
        return super(RestrictedInstantiation, cls).__new__(cls)

# 使用示例
try:
    restricted_instance = RestrictedInstantiation(False)
except ValueError as e:
    print(e)  # 输出 "flag必须为True"

a = RestrictedInstantiation(True)
print("成功创建")  # 这将被打印,因为 flag 是 True

以上示例展示了 __new__ 方法在不同使用场景中的应用,包括单例模式、工厂模式、缓存实例和预防不合适的实例化。

__init__的使用

__init__ 方法在 Python 中用于初始化类的实例。这是类实例化后的第一个方法,它主要用于设置实例的属性和初始化实例的状态,将接下来可能需要的数据或者资源(文件)都准备好。以下是 __init__ 方法的不同使用场景及代码示例:

初始化实例属性

__init__ 方法通常用于初始化实例的属性,以便实例在创建时具备必要的状态。

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

# 使用示例
person = Person("Mike", 30)
print(person.name)  # 输出 "Mike"
print(person.age)   # 输出 30
参数验证和处理

__init__ 方法可以用于验证和处理传入的参数,以确保实例属性符合预期。

import re


class Account:
    # 定义正则表达式来验证电子邮件格式
    EMAIL_PTN = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'

    def __init__(self, email):
        if not self.is_valid_email(email):
            raise ValueError("不正确email格式")
        self.email = email

    @staticmethod
    def is_valid_email(email: str) -> bool:
        # 使用正则表达式匹配电子邮件地址
        return re.match(Account.EMAIL_PTN, email) is not None

# 使用示例
try:
    account = Account("111")
except ValueError as e:
    print(e)  # 输出 "不正确email格式"

valid_account = Account("xmishu@xmishu.com")
print(valid_account.email)   # 输出 "xmishu@xmishu.com"
计算派生属性

__init__ 方法可以用于根据输入参数计算派生附加属性,从而在实例创建时提供额外的信息。

class Rectangle:
    def __init__(self, length, width):
        self.length = length
        self.width = width
        self.area = length * width  # 计算面积
        self.perimeter = 2 * (length + width)  # 计算周长

# 使用示例
rect = Rectangle(4, 30)
print(rect.area)      # 输出 120
print(rect.perimeter) # 输出 68

通过这些例子,你可以看到 __init__ 方法在初始化实例属性、验证和处理参数、计算派生属性等场景中的应用。

总结

__new__ 是一个静态方法,用于创建实例(不一定是新的)并返回这个实例;__init__ 是实例初始化方法,设置属性初始状态,它返回的是None。