返回介绍

类的特殊方法

发布于 2024-05-30 23:22:17 字数 15509 浏览 0 评论 0 收藏 0

Python 的 class 中还有许多这种形如__xxx__的变量或者函数名,它们有特殊用途的函数,可以帮助我们定制类。

__len__

如果你调用 len() 函数试图获取一个对象的长度,实际上,在 len() 函数内部,它自动去调用该对象的__len__()方法,所以,下面的代码是等价的:

>>> len('ABC')
3
>>> 'ABC'.__len__()
3

我们自己写的类,如果也想用 len(myObj)的话,就自己写一个__len__()方法:

>>> class Dog:
...   def __len__(self):
...      return 100
...
>>> dog = Dog()
>>> len(dog)
100

__str__

print一个实例时,自定义打印的字符串。

class Student:
    def __str__(self):
        return 'Student object'
s = Student()
print(s)   #输出:   Student object

但是在终端中直接显示变量不起作用,这是因为直接显示变量调用的不是__str__(),而是__repr__()。两者的区别是__str__()返回用户看到的字符串,而__repr__()返回程序开发者看到的字符串,也就是说, __repr__()是为调试服务的。

>>> s
<__main__.Student object at 0x109afb310>

解决办法是再定义一个__repr__()。但是通常__str__()__repr__()代码都是一样的,所以,有个偷懒的写法:

class Student:
    def __str__(self):
        return 'Student object'
    __repr__ = __str__

__iter__

如果一个类想被用于 for ... in 循环,类似 list 或 tuple 那样,就必须实现一个__iter__()方法,该方法返回一个迭代对象(Iterator),然后, Python 的 for循环就会不断调用该迭代对象的__next__()方法拿到循环的下一个值,直到遇到 StopIteration 错误时退出循环。
我们以斐波那契数列为例,写一个 Fib 类,可以作用于 for 循环:

class Fib(object):
    def __init__(self):
        self.a, self.b = 0, 1    # 初始化
    def __iter__(self):
        return self              # 实例本身就是迭代对象,故返回自己
    def __next__(self):
        self.a, self.b = self.b, self.a + self.b    
        if self.a > 100000:      # 退出循环的条件
            raise StopIteration();
        return self.a            # 返回下一个值

现在,试试把 Fib 实例作用于 for 循环:

>>> for n in Fib():
... print(n)

__next__

有些对象里包含对其他对象的引用:这些对象称为容器。Python中的容器,例如list等。

__next__用于容器中的迭代,容器需要定义下面的方法以提供迭代支持:

Container.__iter__()    #返回一个迭代器对象,对象要求支持后面介绍的迭代器协议。

迭代协议
迭代器对象被要求支持下面的两个方法,合起来形成迭代协议。

Iterator.__iter__()  #返回迭代器对象自身。为了允许容器和迭代器被用于for和in语句中,必须实现该方法。
Iterator.__next__()  #返回容器的下一个条目。如果没有更多的条目,抛出StopIteration异常。

手动迭代
内置函数iter,它会自动调用一个对象的__iter__方法,并获得一个迭代器。
内置函数next,它会自动调用一个对象的__next__方法,给定一个可迭代对象X,自身有__next__方法,调用next(X)等同于X.__next__(),但前者简便很多。

列表等的内置对象,自身不是迭代器(没有实现__next__方法),因为它们需要支持多次迭代。对于这样的对象,我们必须调用iter来启动迭代。

>>> L = [1,2,3]
>>> I = iter(L)
>>> I.__next__()
1
>>> next(I)
2

__getitem__

Fib 实例虽然能作用于 for 循环,看起来和 list 有点像,但是,把它当成list 来使用还是不行,比如,取第 5 个元素:

>>> Fib()[5]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'Fib' object does not support indexing

要表现得像 list 那样按照下标取出元素,需要实现__getitem__()方法:

def __getitem__(self, index):
    a, b = 1, 1
    for i in range(index):
        a, b = b, a + b
    return a

现在,就可以按下标访问数列的任意一项了:

>>> f = Fib()
>>> f[0]
1

但是 list还有一个切片方法,__getitem__()传入的参数可能是一个 int,也可能是一个切片对象 slice,所以要做判断。还需要处理step、负数下标等等。要正确实现一个__getitem__()还是有很多工作要做的。

最后,与__getitem__对应的是__setitem__()方法,把对象视作 list 或 dict 一样来对集合赋值。还有一个__delitem__()方法,用于删除某个元素。

总之,通过上面的方法,我们自己定义的类表现得和 Python 自带的 list、tuple、 dict 没什么区别,这完全归功于动态语言的“鸭子类型”,不需要强制继承某个接口。

__getattr__

在访问实例的属性的时候,如果该属性不存在则会自动调用__getattr__来继续查找。
要让class只响应特定的几个属性,我们就要按照约定,抛出 AttributeError 的错误。

class Student:
    def __getattr__(self, attr):
        if attr == 'age':
            return lambda:25
        raise AttributeError("Student's object has no attribute %s" % attr)

这实际上可以把一个类的所有属性和方法调用全部动态化处理了,不需要任何特殊的手段。

class Gen:
    def __init__(self, root_url=''):
        self.url = root_url
    def __getattr__(self, item):
        if item == 'get' or item == 'post':
            return self.url
        return Gen('{}/{}'.format(self.url, item))
gen = Gen('http://')
s = gen.users.show.erick.com.get    #链式调用
print(s)   #输出:http://users.show.erick.com

充分利用__getattr__会在没有查找到相应实例属性时会被调用的特点,方便的通过链式调用生成对应的url。

__call__

当我们调用实例方法时,我们通常用 实例变量.方法() 形式来调用。
如果定义一个__call__()方法,就可以直接对实例进行调用。

class Student(object):
    def __init__(self, name):
        self.name = name
    def __call__(self):   #注:__call__()还可以定义参数。
        print('My name is %s.' % self.name)

调用方式如下:

>>> s = Student('Michael')
>>> s()     # self 参数不要传入
My name is Michael.

这么一来,我们就模糊了对象和函数的界限。
那么,怎么判断一个变量是对象还是函数呢?其实,更多的时候,我们需要判断一个对象是否能被调用,能被调用的对象就是一个 Callable 对象,比如函数和我们上面定义的带有__call__()的类实例:

>>> callable(Student())
True
>>> callable(max)
True
>>> callable([1, 2, 3])
False
>>> callable(None)
False
>>> callable('str')
False

通过 callable()函数,我们就可以判断一个对象是否是“可调用”对象。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文