Python魔术方法

发布于:2024-06-04 ⋅ 阅读:(136) ⋅ 点赞:(0)

前言

没有系统的学过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 面向对象编程中发挥了重要作用。