python魔法方法用处大吗_Python 魔法方法指南(进阶)

目录:构造方法

类的表示

操作符比较操作符

数值操作符一元操作符

常见算数操作符

反射算数运算符

增强复制运算符

类型转换操作符

访问控制(高级)

自定义序列(高级)

反射(高级)

本文介绍剩余的高级部分。

访问控制

通常情况下,我们在访问类或者实例对象的时候,会牵扯到一些属性访问的魔法方法,主要包括:__getattr__(self, name)

当用户试图访问不存在的属性时调用,你可以通过这个魔法方法来定义类的行为。

class Frob():

def __getattr__(self, item):

return f'{item}属性不存在'

if __name__ == '__main__':

f = Frob()

print(f.nb)

# 输出

nb属性不存在__getattribute__(self, name)

__getattribute__ 在访问存在的属性时调用,(先调用该方法,查看是否存在该属性,若不存在,接着去调用__getattr__(self, name))

举例:

class Frob():

def __getattr__(self, item):

return f'{item}属性不存在'

def __getattribute__(self, item):

print(f'正在获取属性{item}')

# super()必须写,如果属性不存在,需要手动继承父类(object)的__getattribute__()方法

super().__getattribute__(item)

if __name__ == '__main__':

f = Frob()

print(f.nb)

# 输出

正在获取属性nb

nb属性不存在

说明:

1)查看属性是否存在是会先调用__getattribute__();

2)因为 f 没有 nb这个属性,所以又会调用__getattr__()__setattr__(self, name, value) 设置实例对象的一个新的属性时调用。注意:如果__setattr __()希望分配给实例属性,则不应简单地执行self.name = value 这将导致对其自身的递归调用。而是应将值插入实例属性的字典中,例如self .__ dict __ [name] = value。

class Frob():

def __setattr__(self, key, value):

# 使用self.key = value,导致无限递归

# 抛RecursionError: maximum recursion depth exceeded

# self.key = value

self.__dict__[key] = value.upper()

if __name__ == '__main__':

f = Frob()

f.bamf = 'bamf'

print(f.bamf)

# 输出

BAMF__delattr__(self, name) 删除一个实例对象的属性时调用。和 __setattr__ 一样,使用它时也需要多加小心,防止产生无限递归(在 __delattr__ 的实现中调用 del self.name 会导致无限递归)。

class Frob():

def __setattr__(self, key, value):

self.__dict__[key] = value.upper()

def __delattr__(self, item):

print(f'正在删除{item}属性')

if __name__ == '__main__':

f = Frob()

f.bamf = 'bamf'

print(f.bamf)

del f.bamf

# 输出

BAMF

正在删除bamf属性

自定义序列

有许多办法可以让你的Python类表现得像是内建序列类型(字典,元组,列表,字符串等)。这些魔法方式是目前为止我最喜欢的。它们给了你难以置信的控制能力,可以让你的类与一系列的全局函数完美结合。在了解激动人心的内容之前,首先你需要掌握一些预备知识。

预备知识

既然讲到创建自己的序列类型,就不得不说一说协议了。协议类似某些语言中的接口,里面包含的是一些必须实现的方法。在Python中,协议完全是非正式的,也不需要显式的声明,事实上,它们更像是一种参考标准。

为什么我们要讲协议?因为在Python中实现自定义容器类型需要用到一些协议。首先,不可变容器类型有如下协议:想实现一个不可变容器,你需要定义 __len__ 和 __getitem__ (后面会具体说明)。可变容器的协议除了上面提到的两个方法之外,还需要定义 __setitem__和 __delitem__。最后,如果你想让你的对象可以迭代,你需要定义 iter ,这个方法返回一个迭代器。迭代器必须遵守迭代器协议,需要定义 __iter__(返回它自己)和 next 方法。

容器背后的魔法方法__len__(self)

返回容器的长度,可变和不可变类型都需要实现。

__getitem__(self, key)

定义对容器中某一项使用 self[key] 的方式进行读取操作时的行为。这也是可变和不可变容器类型都需要实现的一个方法。它应该在键的类型错误式产生 TypeError 异常,同时在没有与键值相匹配的内容时产生 KeyError 异常。

__setitem__(self, key)

定义对容器中某一项使用 self[key] 的方式进行赋值操作时的行为。它是可变容器类型必须实现的一个方法,同样应该在合适的时候产生 KeyError 和 TypeError 异常。

__iter__(self, key)

它应该返回当前容器的一个迭代器。迭代器以一连串内容的形式返回,最常见的是使用 iter() 函数调用,以及在类似 for x in container: 的循环中被调用。迭代器是他们自己的对象,需要定义 __iter__ 方法并在其中返回自己。

__reversed__(self)

定义了对容器使用 reversed() 内建函数时的行为。它应该返回一个反转之后的序列。当你的序列类是有序时,类似列表和元组,再实现这个方法,

__contains__(self, item)

__contains__定义了使用 in 和 not in 进行成员测试时类的行为。你可能好奇为什么这个方法不是序列协议的一部分,原因是,如果__contains__没有定义,Python就会迭代整个序列,如果找到了需要的一项就返回 True 。

__missing__(self ,key)

__missing__ 在字典的子类中使用,它定义了当试图访问一个字典中不存在的键时的行为(目前为止是指字典的实例,例如我有一个字典 d , “george” 不是字典中的一个键,当试图访问 d[“george’] 时就会调用 d.__missing__(“george”))。

例子

让我们来看一个实现了一些函数式结构的列表,可能在其他语言中这种结构更常见(例如Haskell):

class FunctionalList:

'''一个列表的封装类,实现了一些额外的函数式

方法,例如head, tail, init, last, drop和take。'''

def __init__(self, values=None):

if values is None:

self.values = []

else:

self.values = values

def __len__(self):

return len(self.values)

def __getitem__(self, key):

# 如果键的类型或值不合法,列表会返回异常

return self.values[key]

def __setitem__(self, key, value):

self.values[key] = value

def __delitem__(self, key):

del self.values[key]

def __iter__(self):

return iter(self.values)

def __reversed__(self):

return reversed(self.values)

def append(self, value):

self.values.append(value)

def head(self):

# 取得第一个元素

return self.values[0]

def tail(self):

# 取得除第一个元素外的所有元素

return self.valuse[1:]

def init(self):

# 取得除最后一个元素外的所有元素

return self.values[:-1]

def last(self):

# 取得最后一个元素

return self.values[-1]

def drop(self, n):

# 取得除前n个元素外的所有元素

return self.values[n:]

def take(self, n):

# 取得前n个元素

return self.values[:n]

就是这些,一个(微不足道的)有用的例子,向你展示了如何实现自己的序列。当然啦,自定义序列有更大的用处,而且绝大部分都在标准库中实现了(Python是自带电池的,记得吗?),像 Counter , OrderedDict 和 NamedTuple 。

反射

你可以通过定义魔法方法来控制用于反射的内建函数 isinstance 和 issubclass 的行为。下面是对应的魔法方法:__instancecheck__(self, instance)

检查一个实例是否是你定义的类的一个实例(例如 isinstance(instance, class) )。

__subclasscheck__(self, subclass)

检查一个类是否是你定义的类的子类(例如 issubclass(subclass, class) )。

这几个魔法方法的适用范围看起来有些窄,事实也正是如此。我不会在反射魔法方法上花费太多时间,因为相比其他魔法方法它们显得不是很重要。但是它们展示了在Python中进行面向对象编程(或者总体上使用Python进行编程)时很重要的一点:不管做什么事情,都会有一个简单方法,不管它常用不常用。

这些魔法方法可能看起来没那么有用,但是当你真正需要用到它们的时候,你会感到很幸运,因为它们还在那儿(也因为你阅读了这本指南!)

推荐阅读

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值