• Python魔术方法


    前言

    没有系统的学过python,看ML perf storage的源码发现这中双下划线的函数什么情况,明明grep找不到在哪里调用了可是就是调用了n次,觉得有点诡异了,查了一下原来如此,魔术方法。

    魔术方法

    魔术方法(magic methods)或双下划线方法(dunder methods,“dunder” 是 “double underscore” 的缩写)。魔术方法是 Python 中具有特殊意义的函数,通常由双下划线包围,如 initstrgetitem 等。这些方法使得类实例可以与内置操作和函数无缝集成,从而实现自定义行为。

    init

    这个看名字就很明白,对象初始化方法,实例化对象之后立即触发,在构造函数之后调用。我直接拿ML perf storage的源码举例子。

    def __init__(self, format_type, dataset_type, epoch, worker_index,
                 total_num_workers, total_num_samples, samples_per_worker, batch_size, shuffle=Shuffle.OFF, seed=1234):
        self.format_type = format_type
        self.dataset_type = dataset_type
        self.epoch = epoch
        self.total_num_workers = total_num_workers
        self.total_num_samples = total_num_samples
        self.samples_per_worker = samples_per_worker
        self.batch_size = batch_size
        self.worker_index = worker_index
        self.shuffle = shuffle
        self.total_num_steps = self.samples_per_worker//batch_size
        # pdb.set_trace()
        self.reader = ReaderFactory.get_reader(type=self.format_type,
                                               dataset_type=self.dataset_type,
                                               thread_index=worker_index,
                                               epoch_number=self.epoch)
        self.seed = seed
        if not hasattr(self, 'indices'):
            self.indices = np.arange(self.total_num_samples, dtype=np.int64)
        if self.shuffle != Shuffle.OFF:
            if self.shuffle == Shuffle.SEED:
                np.random.seed(self.seed)
            np.random.shuffle(self.indices)
    

    call

    这就是那个让我觉得诡异的函数,grep也没别的地方调用啊,我特地备注了调用了无数次,主要介绍一下call,后面的简单举例。call这个方法允许类的实例被像函数一样调用。

    • 接收一个 sample_info 参数。
    • 记录读取操作的调试信息。
    • 根据 sample_info 计算当前样本的全局索引 sample_idx。
    • 检查是否超过了步数限制或样本数量限制,如果是,则抛出 StopIteration 异常,表示 epoch 结束。
    • 使用 Profile 上下文管理器记录数据加载的性能信息。
    • 从 reader 读取指定索引的图像。
    • 返回图像和相应的索引。
        def __call__(self, sample_info): # 调用无数次
            logging.debug(
                f"{utcnow()} Reading {sample_info.idx_in_epoch} out of {self.samples_per_worker} by worker {self.worker_index}")
            sample_idx = sample_info.idx_in_epoch + self.samples_per_worker * self.worker_index
            logging.debug(
                f"{utcnow()} Reading {sample_idx} on {sample_info.iteration} by worker {self.worker_index}")
            step = sample_info.iteration       
            if step >= self.total_num_steps or sample_idx >= self.total_num_samples:
                # Indicate end of the epoch
                raise StopIteration()
            with Profile(MODULE_DATA_LOADER, epoch=self.epoch, image_idx=sample_idx, step=step):
                image = self.reader.read_index(self.indices[sample_idx], step)
            return image, np.uint8([self.indices[sample_idx]])
    

    举个简单例子:

    class MyClass:
        def __init__(self, value):
            self.value = value
    
        def __call__(self):
            return f"Called with value: {self.value}"
    
    # 自动调用 __call__
    obj = MyClass(10)
    print(obj())  # 输出: Called with value: 10
    

    iternext

    iternext 方法在使用迭代(例如 for 循环)时自动调用。

    class MyClass:
        def __init__(self, values):
            self.values = values
            self.index = 0
    
        def __iter__(self):
            self.index = 0
            return self
    
        def __next__(self):
            if self.index < len(self.values):
                result = self.values[self.index]
                self.index += 1
                return result
            else:
                raise StopIteration
    
    # 自动调用 __iter__ 和 __next__
    obj = MyClass([1, 2, 3])
    for value in obj:
        print(value)
    

    getitemsetitem

    getitemsetitem 方法在使用索引访问和设置元素时自动调用。

    class MyClass:
        def __init__(self, values):
            self.values = values
    
        def __getitem__(self, index):
            return self.values[index]
    
        def __setitem__(self, index, value):
            self.values[index] = value
    
    # 自动调用 __getitem__ 和 __setitem__
    obj = MyClass([1, 2, 3])
    print(obj[1])  # 自动调用 __getitem__,输出: 2
    obj[1] = 10    # 自动调用 __setitem__
    print(obj[1])  # 自动调用 __getitem__,输出: 10
    

    strlen

    str 方法在调用 print() 函数或使用 str() 函数时自动调用。len 方法在调用 len() 函数时自动调用。

    class MyClass:
        def __init__(self, value):
            self.value = value
    
        def __str__(self):
            return f"MyClass with value: {self.value}"
    
        def __len__(self):
            return len(self.value)
    
    
    obj = MyClass([1, 2, 3])
    # 自动调用 __str__
    print(obj)
    # 自动调用 __len__
    print(len(obj))  # 输出: 3
    

    一些其他的

    算术运算符方法

    • add(self, other):实现加法运算 +。
    • sub(self, other):实现减法运算 -。
    • mul(self, other):实现乘法运算 *。
    • truediv(self, other):实现除法运算 /。

    比较运算符方法

    • eq(self, other):实现等于运算 ==。
    • ne(self, other):实现不等于运算 !=。
    • lt(self, other):实现小于运算 <。
    • le(self, other):实现小于等于运算 <=。
    • gt(self, other):实现大于运算 >。
    • ge(self, other):实现大于等于运算 >=。

    容器类型方法

    • getitem(self, key):定义使用索引访问元素的行为。
    • setitem(self, key, value):定义使用索引设置元素的行为。
    • delitem(self, key):定义使用索引删除元素的行为。
    • len(self):定义对象的长度,通常与 len() 函数配合使用。
    • contains(self, item):定义使用 in 运算符检查成员资格的行为。

    优点和应用

    魔术方法通过定义类的行为方式,增强了代码的可读性和可维护性。例如,通过实现 getitemsetitem,类实例可以像列表或字典一样使用索引访问和修改元素;通过实现 iternext,类实例可以参与迭代操作。

    这些方法在实现运算符重载、容器类型行为、迭代器协议、上下文管理等方面提供了强大的支持,使自定义类与 Python 内置类型和功能无缝集成,从而实现更自然和直观的接口。这种灵活性和强大功能,使得魔术方法在 Python 面向对象编程中发挥了重要作用。

  • 相关阅读:
    数据平台数据接入实践
    [Linux入门]---管理者操作系统
    33-Java多线程
    linux运维基础2
    原子性操作
    第一章 概论
    慢SQL的治理思路
    Java入门第116课——向List中插入和删除元素
    某车企笔试题解答(2)
    轻松搭建Linux的环境
  • 原文地址:https://blog.csdn.net/weixin_43912621/article/details/139423728