《老鸟python 系列》视频上线了,全网稀缺资源,涵盖python人工智能教程,爬虫教程,web教程,数据分析教程以及界面库和服务器教程,以及各个方向的主流实用项目,手把手带你从零开始进阶高手之路!点击 链接 查看详情




定制类

阅读:227568340    分享到

在其它高级语言中,我们可以利用语法,自己编程来扩展类和对象的特性,比如在 C++ 中,我们可以做各种运算符重载来实现这种效果,这样很繁琐麻烦,在 Python 中我们可以通过 __xx__ 的形式来扩展类和对象的这种特性。

前面我们已经学习了 __slots__ 起到限制类的对象添加属性的功能,本节我们主要学习其它的 __xx__ 的作用。

使用 __len__ 函数

我们调用 Python 内置的 len 函数作用于 python 内置的容器(str,list,tuple,dict,set)对象,可以得到容器元素的个数, 实际上调用的是容器的 __len__ 函数进行计算的,下面我们以容器 list 为例来说明问题。

mylist = ["hello", 1, (3, 4), {"a": 1, "b": 2}]
print(len(mylist))  # 实际调用的是 mylist.__len__() 函数
print(mylist.__len__())

如果我们想用 Python 内置的 len 函数作用于我们自定义的类,则需要自己在类里面写 __len__ 函数。

class MyObject(object):
    def __init__(self, *data):
        self.__data = data

    def __len__(self):
        return len(self.__data)

myobject = MyObject(4, "hello", [2, 3])
print(len(myobject))

至于 __len__ 函数返回什么值,是由我们自己决定的,但要注意返回值的类型必须是数字类型,如果是数字类型中的 float 类型,则会返回浮点数的整数部分。

class MyObject(object):
    def __init__(self, *data):
        self.__data = data

    def __len__(self):
        return 250  # 必须为数字类型,否则报异常

myobject = MyObject(4, "hello", [2, 3])
print(len(myobject))

使用 __str__ 函数或 __unicode__ 函数

我们定义一个 MyObject 类,然后用 print 函数打印一个 MyObject 类的对象,则打印出一堆 的东西,这个信息主要标注了我们对象的信息,如果我们想让作用在我们对象上的 print 函数输出我们自己自己想输出的信息,则需要在类里面写 __str__ 函数来实现。

class MyObject(object):
    def __init__(self, *data):
        self.__data = data

    def __str__(self):
        return "I'm MyObject"   # 或者 return "I'm MyObject"

myobject = MyObject(4, "hello", [2, 3])
print(myobject)

当然我们也可以返回中文。

class MyObject(object):
    def __init__(self, *data):
        self.__data = data

    def __str__(self):
        return "大嘎好,我系渣渣辉"  # 或者 return u"大嘎好,我系渣渣辉"

myobject = MyObject(4, "hello", [2, 3])
print(myobject)

我们也可以让我们的对象返回的信息像 Python 内置的容器对象返回的信息一样,但要注意返回类型必须为 str 或 unicode 类型。

class MyObject(object):
    def __init__(self, *data):
        self.__data = data

    def __str__(self):
        return str(self.__data)

myobject = MyObject(4, "hello", [2, 3])
print(myobject)

Python 还提供一个 __unicode__ 函数,在 Python2 中该函数用于返回中文,而 __str__ 函数用于返回 ASCII。然而,在 Python3 中, __str__ 即可以返回中文也可以返回 ASCII,而 __unicode__ 函数,我们并不需要使用。

使用 __repr__ 函数

我们在 Python 的交互模式下,定义一个 MyObject 类,然后定义了 __str__ 函数,然后直接敲入对象名称,显式的还是类似于< xx.MyObject object at 0xxxxxxxx > 的东西,这是因为直接显示变量调用的不是 __str__ 函数,而是 __repr__ 函数,两者的区别是 __str__ 函数返回用户看到的字符串,而 __repr__ 函数返回程序开发者看到的字符串,也就是说,__repr__ 函数是为调试服务的。

...class MyObject(object):
...    def __init__(self, *data):
...        self.__data = data
...
...    def __repr__(self):
...        return "I'm MyObject"
...
>>> myobject = MyObject(4, "hello", [2, 3])
>>> myobject
I'm MyObject   # 控制台输出

使用 __iter__ 函数

我们知道 for...in... 只能遍历迭代器,虽然 Python 内置的容器(list,tuple,dict,set)等等都不是迭代器, 但是却都可以用 for ... in 来遍历,我们通过查找源码发现:实际上 for...in... 在第一次遍历这些内置容器时,会先调用 Python 内置的 iter 函数,该 iter 函数会自动调用这些内置容器类内部的 __iter__ 函数,这些内置容器类内部的 __iter__ 函数返回一个迭代器(迭代对象),然后 Python 的 for 循环就会不断调用该迭代器的 __next__ 函数拿到循环的下一个值,直到遇到 StopIteration 异常时退出循环。同样我们也可以在自己定义的类中重写 __iter__ 函数,让该函数返回一个迭代器,以便支持 Python 的 for 循环。

class MyObject(object):
    def __init__(self, *data):
        self.__data = data

    def __iter__(self):
        return iter(self.__data)  # 返回一个迭代器(返回的是元组对象转换的迭代器)

myobject = MyObject(4, "hello", [2, 3])

for item in myobject:
    print(item)

大家仔细观察一下,我们类中的 __iter__ 函数返回的迭代器是一个元组对象(self.__data 是元组对象)构建的迭代器(iter(self.__data)),在 for...in... 的每次遍历中,都是转换为调用这个元组生成的迭代器的 __next__ 函数。当然,我们 也可以自己在类中实现 __next__ 函数,这样,我们的类就成了迭代器。

class MyObject(object):
    def __init__(self, *data):
        self.index = -1
        self.__data = data

    def __iter__(self):
        return self  # 返回自己,自己本身就是迭代器

    def __next__(self):
        self.index += 1
        try:
            return self.__data[self.index]
        except:
            raise StopIteration

myobject = MyObject(4, "hello", [2, 3])

for item in myobject:
    print(item)

为了让大家更清晰的了解迭代器类是如何实现的,我们仔细分析一下上面代码中 for...in... 遍历的过程。

for item in myobject:
    print(item)

######################## Python 解释器把 for...in... 转变成以下代码 ########################
'''
Python 解释器在第一次执行 for...in 语句之前会先调用 Python 内置的 iter 函数,
该 iter 函数会自动调用 MyObject 类的 __iter__ 函数,
我们自己实现 MyObject 类的 __iter__ 函数返回了对象本身,
注意:在此处 it_myobject 就是 myobject。
'''
it_myobject = iter(myobject)

'''
第一次 for...in 时,直接用 item 带入 it_myobject.__next__(),
此时 item 的值为:4
'''
item = it_myobject.__next__()
print(item)

'''
第二次 for...in 时,直接用 item 带入 it_myobject.__next__(),
此时 item 的值为 "hello"
'''
item = it_myobject.__next__()
print(item)

'''
第三次 for...in 时,直接用 item 带入 it_myobject.__next__(),
此时 item 的值为 [2, 3]
'''
item = it_myobject.__next__()
print(item)

'''
第四次 for...in 时,直接用 item 带入 it_myobject.__next__(),
此时越界触发异常,我们在 except 里面处理掉越界异常,并抛出 StopIteration 异常,
for...in... 在遇到 StopIteration 异常后,终止循环。
'''
item = it_myobject.__next__()  # 引发越界异常,我们处理异常,并抛出 StopIteration 异常,for...in... 终止循环。

使用 __getitem__ 函数

如果我们想让自己写的类像 Python 内置的有序集合一样,支持通过对象加下标的形式获取值, 我们可以在类里面写入 __getitem__ 函数来实现。

class MyObject(object):
    def __init__(self, *data):
        self.__data = data

    def __getitem__(self, index):
        return self.__data[index]

myobject = MyObject(4, "hello", [2, 3])
print(myobject[1])

使用 __getattr__ 函数

正常情况下,当我们调用类的属性不存在时就会抛出异常,但是如果我们重写了 __getattr__ 函数 Python 解释器则会调用 __getattr__ 函数来处理不存在的属性。

class Human(object):
    def __init__(self):
        self.age = 18

    def __getattr__(self, attr):
        return attr + "属性不存在"

ruhua = Human()
print(ruhua.age)
print(ruhua.name)

同样如果我们调用的类的方法不存在时,Python 解释器则会调用 __getattr__ 函数来处理不存在的方法,但要注意该函数要返回一个函数。

def myfunc():
    print("该函数不存在")

class Human(object):
    def __init__(self):
        self.age = 18

    def __getattr__(self, attr):
        return myfunc

ruhua = Human()
ruhua.talk()

因为 __getattr__ 函数的第二个形参接收的是不存在的属性或方法名字,我们可以自己灵活的处理我们想处理的不存在的属性或方法。

def myfunc():
    print("你瞅啥")

class Human(object):
    def __init__(self):
        self.age = 18

    def __getattr__(self, attr):
        if attr == "age":
            return 18
        if attr == "talk":
            return myfunc

ruhua = Human()
print(ruhua.age)
ruhua.talk()

使用 __call__ 函数

类的对象可以有自己的属性和方法,当我们调用对象的函数时,我们用对象加上点加上函数名来调用, 如果我们直接通过在对象的后面加上括号调用,则是调用类的 __call__ 函数,这样一来,对象看起来像个函数,我们称之为仿函数或者函数对象。

class MyObject(object):
    def __init__(self):
        pass
    def __call__(self, arg):
        print(arg)

myobject = MyObject()
myobject("hello")

函数对象和函数之间没有本质区别,函数对象其实就是函数,Python 的 callable 函数可以判断一个变量是否是函数,是则返回True反之返回False。

class MyObject(object):
    def __init__(self):
        pass
    def __call__(self, arg):
        pass

myobject = MyObject()
print(callable(myobject))

本节重要知识点

灵活使用定制类。

注意定制类的注意事项。

作业

自己写个迭代器类,在类里面实现 __iter__ 函数和 __next__ 函数。


如果以上内容对您有帮助,请老板用微信扫一下赞赏码,赞赏后加微信号 birdpython 领取免费视频。


登录后评论

user_image
stormzhang
2020年9月21日 21:27 回复

牛逼的很呐,我看懂了哈哈


user_image
lanjunfu
2020年6月11日 22:52 回复
class IterClass(object):
    def __init__(self,*data):
        self.index=0
        self.data=data
    def __iter__(self):
        return self
    def __next__(self):
        if self.index>=len(self.data):
            raise StopIteration
        else:
            self.index+=1
            return self.data[self.index-1]
iterClass= IterClass([1,'a',3,'b',[1,3],(1,4)])
for item in iterClass:
    print(item)

user_image
大蚂蚁
2019年10月6日 06:19 回复

一脸懵逼来,一脸懵逼走[捂脸]


user_image
doodlewind
2019年6月15日 16:17 回复

迭代器讲的好啊,看完这节,我自己也会写迭代器了