在其它高级语言中,我们可以利用语法,自己编程来扩展类和对象的特性,比如在 C++ 中,我们可以做各种运算符重载来实现这种效果,这样很繁琐麻烦,在 Python 中我们可以通过 __xx__ 的形式来扩展类和对象的这种特性。
前面我们已经学习了 __slots__ 起到限制类的对象添加属性的功能,本节我们主要学习其它的 __xx__ 的作用。
我们调用 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))
我们定义一个 MyObject 类,然后用 print 函数打印一个 MyObject 类的对象,则打印出一堆
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__ 函数,我们并不需要使用。
我们在 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 # 控制台输出
我们知道 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... 终止循环。
如果我们想让自己写的类像 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__ 函数 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__ 函数,这样一来,对象看起来像个函数,我们称之为仿函数或者函数对象。
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__ 函数。
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)
一脸懵逼来,一脸懵逼走[捂脸]
迭代器讲的好啊,看完这节,我自己也会写迭代器了
牛逼的很呐,我看懂了哈哈