Python魔术方法
前言
没有系统的学过python,看ML perf storage的源码发现这中双下划线的函数什么情况,明明grep找不到在哪里调用了可是就是调用了n次,觉得有点诡异了,查了一下原来如此,魔术方法。
魔术方法
魔术方法(magic methods)或双下划线方法(dunder methods,“dunder” 是 “double underscore” 的缩写)。魔术方法是 Python 中具有特殊意义的函数,通常由双下划线包围,如 init、str、getitem 等。这些方法使得类实例可以与内置操作和函数无缝集成,从而实现自定义行为。
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_typeself.dataset_type = dataset_typeself.epoch = epochself.total_num_workers = total_num_workersself.total_num_samples = total_num_samplesself.samples_per_worker = samples_per_workerself.batch_size = batch_sizeself.worker_index = worker_indexself.shuffle = shuffleself.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 = seedif 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_indexlogging.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 epochraise 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 = valuedef __call__(self):return f"Called with value: {self.value}"# 自动调用 __call__
obj = MyClass(10)
print(obj()) # 输出: Called with value: 10
iter ,next
iter 和 next 方法在使用迭代(例如 for 循环)时自动调用。
class MyClass:def __init__(self, values):self.values = valuesself.index = 0def __iter__(self):self.index = 0return selfdef __next__(self):if self.index < len(self.values):result = self.values[self.index]self.index += 1return resultelse:raise StopIteration# 自动调用 __iter__ 和 __next__
obj = MyClass([1, 2, 3])
for value in obj:print(value)
getitem , setitem
getitem 和 setitem 方法在使用索引访问和设置元素时自动调用。
class MyClass:def __init__(self, values):self.values = valuesdef __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
str ,len
str 方法在调用 print() 函数或使用 str() 函数时自动调用。len 方法在调用 len() 函数时自动调用。
class MyClass:def __init__(self, value):self.value = valuedef __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 运算符检查成员资格的行为。
优点和应用
魔术方法通过定义类的行为方式,增强了代码的可读性和可维护性。例如,通过实现 getitem 和 setitem,类实例可以像列表或字典一样使用索引访问和修改元素;通过实现 iter 和 next,类实例可以参与迭代操作。
这些方法在实现运算符重载、容器类型行为、迭代器协议、上下文管理等方面提供了强大的支持,使自定义类与 Python 内置类型和功能无缝集成,从而实现更自然和直观的接口。这种灵活性和强大功能,使得魔术方法在 Python 面向对象编程中发挥了重要作用。