强烈推荐超详解Python-魔法函数(高级语法)
Posted ZSYL
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了强烈推荐超详解Python-魔法函数(高级语法)相关的知识,希望对你有一定的参考价值。
Python-魔法函数(高级语法)
1. 前言
1.1 什么是魔法函数?
在Python中,__xx__()
的函数叫做魔法方法,以双下划线(__xx__
)开始和结束的函数(不可自己定义)为魔法函数。
所谓魔法函数(Magic Methods),是Python的一种高级语法,允许你在类中自定义函数(函数名格式一般为__xx__),并绑定到类的特殊方法中。比如在类A中自定义__str__()函数,则在调用str(A())时,会自动调用__str__()函数,并返回相应的结果。
在我们平时的使用中,可能经常使用__init__函数(构造函数)和__del__函数(析构函数),其实这也是魔法函数的一种。
-
__init__()
方法,在创建一个对象时默认被调用,不需要手动调用 -
__init__(self)
中的self参数,不需要开发者传递,python解释器会自动把当前的对象引用传递过去。 -
当使用print输出对象的时候,默认打印对象的内存地址。如果类定义了
__str__
方法,那么就会打印从在这个方法中 return 的数据。 -
当删除对象时,python解释器也会默认调用
__del__()
方法。 -
调用类实例化的对象的方法时自动调用魔法函数。
-
在自己定义的类中,可以实现之前的内置函数。
1.2 魔法函数有什么作用?
魔法函数可以为你写的类增加一些额外功能,方便使用者理解。
举个简单的例子,我们定义一个“人”的类People,当中有属性姓名name、年龄age。让你需要利用sorted函数对一个People的数组进行排序,排序规则是按照name和age同时排序,即name不同时比较name,相同时比较age。由于People类本身不具有比较功能,所以需要自定义,你可以这么定义People类:
class People(object):
def __init__(self, name, age):
self.name = name
self.age = age
return
def __str__(self):
return self.name + ":" + str(self.age)
def __lt__(self, other):
return self.name < other.name if self.name != other.name else self.age < other.age
if __name__=="__main__":
print("\\t".join([str(item) for item in sorted([People("abc", 18),
People("abe", 19), People("abe", 12), People("abc", 17)])]))
输出结果:
abc:17 abc:18 abe:12 abe:19
上个例子中的__lt__
函数即less than
函数,即当比较两个People实例时自动调用。
类似Java中的重写toString()方法,重写Comparable()方法
2. 常见的魔法函数
我们将魔法方法分为:非数学运算和数学运算两大类。
3. 非数学运算
3.1 字符串表示
__repr__
__repr__
函数和__str__
函数:
函数str() 用于将值转化为适于人阅读的形式,而repr() 转化为供解释器读取的形式,某对象没有适于人阅读的解释形式的话,str() 会返回与repr(),所以print展示的都是str的格式。
我们经常会直接输出类的实例化对象,例如:
class CLanguage:
pass
clangs = CLanguage()
print(clangs)
程序运行结果为:
<__main__.CLanguage object at 0x000001A7275221D0>
通常情况下,直接输出某个实例化对象,本意往往是想了解该对象的基本信息,例如该对象有哪些属性,它们的值各是多少等等。
但默认情况下,我们得到的信息只会是“类名+object at+内存地址”
,对我们了解该实例化对象帮助不大。
那么,有没有可能自定义输出实例化对象时的信息呢?
答案是肯定的,通过重写类的 repr() 方法即可。事实上,当我们输出某个实例化对象时,其调用的就是该对象的 repr() 方法,输出的是该方法的返回值。
以本节开头的程序为例,执行 print(clangs) 等同于执行 print(clangs.repr()),程序的输出结果是一样的(输出的内存地址可能不同)。
和 init(self) 的性质一样,Python 中的每个类都包含 repr() 方法,因为 object 类包含__reper__()
方法,而 Python 中所有的类都直接或间接继承自 object 类。
默认情况下,__repr__()
会返回和调用者有关的 “类名+object at+内存地址”
信息。当然,我们还可以通过在类中重写这个方法,从而实现当输出实例化对象时,输出我们想要的信息。
举个例子:
class CLanguage:
def __init__(self):
self.name = "C语言中文网"
self.add = "http://c.biancheng.net"
def __repr__(self):
return "CLanguage[name="+ self.name +",add=" + self.add +"]"
clangs = CLanguage()
print(clangs)
程序运行结果为:
CLanguage[name=C语言中文网,add=http://c.biancheng.net]
由此可见,__repr__()
方法是类的实例化对象用来做“自我介绍”的方法,默认情况下,它会返回当前对象的“类名+object at+内存地址”,而如果对该方法进行重写,可以为其制作自定义的自我描述信息。
__str__
直接打印对象的实现方法,__ str__是被print函数调用的。打印一个实例化对象时,打印的其实时一个对象的地址。而通过__str__()函数就可以帮助我们打印对象中具体的属性值,或者你想得到的东西。
在Python中调用print()打印实例化对象时会调用__str__()。如果__str__()中有返回值,就会打印其中的返回值。
class Cat:
"""定义一个猫类"""
def __init__(self, new_name= "汤姆", new_age= 20):
"""在创建完对象之后 会自动调用, 它完成对象的初始化的功能"""
self.name = new_name
self.age = new_age # 它是一个对象中的属性,在对象中存储,即只要这个对象还存在,那么这个变量就可以使用
# num = 100 # 它是一个局部变量,当这个函数执行完之后,这个变量的空间就没有了,因此其他方法不能使用这个变量
def __str__(self):
"""返回一个对象的描述信息"""
# print(num)
return "名字是:%s , 年龄是:%d" % (self.name, self.age)
# 创建了一个对象
tom = Cat("汤姆", 30)
print(tom)
名字是:汤姆 , 年龄是:30
总结:当使用print输出对象的时候,只要自己定义了__str__(self)方法,那么就会打印从在这个方法中return的数据。__str__方法需要返回一个字符串,当做这个对象的描写。
__repr__和__str__区别
看下面的例子就明白了:
class Test(object):
def __init__(self, value='hello, world!'):
self.data = value
>>> t = Test()
>>> t
<__main__.Test at 0x7fa91c307190>
>>> print(t)
__main__.Test object at 0x7fa91c307190>
# 看到了么?上面打印类对象并不是很友好,显示的是对象的内存地址
# 下面我们重构下该类的__repr__以及__str__,看看它们俩有啥区别
# 重构__repr__
class TestRepr(Test):
def __repr__(self):
return 'TestRepr(%s)' % self.data
>>> tr = TestRepr()
>>> tr 直接终端显示,不print就是面向程序员
TestRepr(hello, world!)
>>> print(tr) print是面向程序员
TestRepr(hello, world!)
# 重构__repr__方法后,不管直接输出对象还是通过print打印的信息都按我们__repr__方法中定义的格式进行显示了
# 重构__str__
calss TestStr(Test):
def __str__(self):
return '[Value: %s]' % self.data
>>> ts = TestStr()
>>> ts
__main__.TestStr at 0x7fa91c314e50>
>>> print(ts)
[Value: hello, world!]
# 你会发现,直接输出对象ts时并没有按我们__str__方法中定义的格式进行输出,而用print输出的信息却改变了
__repr__和__str__这两个方法都是用于显示的,__str__是面向用户的,而__repr__面向程序员。
- 打印操作会首先尝试__str__和str内置函数(print运行的内部等价形式),它通常应该返回一个友好的显示。
__repr__
用于所有其他的环境中:用于交互模式下提示回应以及repr函数,如果没有使用__str__,会使用print和str。它通常应该返回一个编码字符串,可以用来重新创建对象,或者给开发者详细的显示。
当我们想所有环境下都统一显示的话,可以重构__repr__方法;当我们想在不同环境下支持不同的显示,例如终端用户显示使用__str__,而程序员在开发期间则使用底层的__repr__来显示,实际上__str__只是覆盖了__repr__以得到更友好的用户显示。
3.2 集合、序列相关
__len__函数、__getitem__函数、__setitem__函数、__delitem__函数和__contains__函数
:
__ len__函数
在Python中,如果你调用len()函数试图获取一个对象的长度,实际上,在len()函数内部,它自动去调用该对象的__len__()方法。
class Students():
def __init__(self, *args):
self.names = args
def __len__(self):
return len(self.names)
ss = Students('Bob', 'Alice', 'Tim')
print(len(ss))
输出结果:
3
__getitem__函数
Python的特殊方法__getitem_() 主要作用是可以让对象实现迭代功能。我们通过一个实例来说明。
定义一个Sentence类,通过索引提取单词。
import re
RE_WORD = re.compile(r'\\w+')
class Sentence:
def __init__(self, text):
self.text = text
self.words = RE_WORD.findall(text) # re.findall函数返回一个字符串列表,里面的元素是正则表达式的全部非重叠匹配
def __getitem__(self, index):
return self.words[index]
测试:
>>> s = Sentence('The time has come')
>>> for word in s:
print(word)
The
time
has
come
>>> s[0]
'The'
>>> s[1]
'time'
通过测试发现,示例 s 可以正常迭代。但是没有定义getitem() 测试则会报错,TypeError: ‘***’ object is not iterable。
序列可以迭代:
我们都知道序列是可以迭代,下面具体说明原因。
解释器需要迭代对象x时, 会自动调用iter(x)方法。
任何Python序列都可迭代的原因是,他们都实现了__getitem__方法。其实,标准的序列也都实现了__iter__方法。
注意:从python3.4 开始,检查对象x能否迭代,最准确的方法是: 调用iter(x)方法,如果不可迭代,在处理TypeError异常。这比使用isinstance(x,abc.Iterable)更准确,因为iter()方法会考虑到遗留的__getitem__()方法,而abc.Iterable类则不考虑。
__ setitem__函数
setitem(self,key,value):该方法应该按一定的方式存储和key相关的value。在设置类实例属性时自动调用的。
# -*- coding:utf-8 -*-
class A:
def __init__(self):
self['B']='BB'
self['D']='DD'
def __setitem__(self,name,value):
print "__setitem__:Set %s Value %s" %(name,value)
if __name__=='__main__':
X=A()
输出结果为:
__setitem__:Set B Value BB
__setitem__:Set D Value DD
__ delitem__()
__delitem__(self,key):
这个方法在对对象的组成部分使用__del__语句的时候被调用,应删除与key相关联的值。同样,仅当对象可变的时候,才需要实现这个方法。
class Tag:
def __init__(self):
self.change={'python':'This is python',
'php':'PHP is a good language'}
def __getitem__(self, item):
print('调用getitem')
return self.change[item]
def __setitem__(self, key, value):
print('调用setitem')
self.change[key]=value
def __delitem__(self, key):
print('调用delitem')
del self.change[key]
a=Tag()
print(a['php'])
del a['php']
print(a.change)
输出结果:
调用getitem
PHP is a good language
调用delitem
{'python': 'This is python'}
__contains__函数
在Class里添加__contains__(self,x)函数,可判断我们输入的数据是否在Class里.参数x就是我们传入的数据.
如下代码:
class Graph():
def __init__(self):
self.items = {'a':1,'b':2,'c':3}
def __contains__(self,x): # 判断一个定点是否包含在里面
return x in self.items
a = Graph()
print('a' in a) # 返回True
print('d' in a) # 返回False
>> True
>> False
class Graph():
def __init__(self):
self.items = {'a':1,'b':2,'c':3}
def __str__(self):
return '打印我干嘛'
def __contains__(self,x): # x参数接受的就是我们手动传递的数据
if x<10 and x>0:
return True
return False
print(9 in Graph())
print(5 in Graph())
print(51 in Graph())
>> True
>> True
>> False
3.3 迭代相关
__iter__
函数和__next__
函数:
迭代器就是重复地做一些事情,可以简单的理解为循环,在python中实现了__iter__方法的对象是可迭代的,实现了next()方法的对象是迭代器,这样说起来有点拗口,实际上要想让一个迭代器工作,至少要实现__iter__方法和next方法。
很多时候使用迭代器完成的工作使用列表也可以完成,但是如果有很多值列表就会占用太多的内存,而且使用迭代器也让我们的程序更加通用、优雅、pythonic。
如果一个类想被用于for … in循环,类似list或tuple那样,就必须实现一个__iter__()
方法,该方法返回一个迭代对象,然后,Python的for循环就会不断调用该迭代对象的next()方法拿到循环的下一个值,直到遇到StopIteration错误时退出循环。
容器(container):
容器是用来储存元素的一种数据结构,容器将所有数据保存在内存中,Python中典型的容器有:list,set,dict,str
等等。
class test():
def __init__(self,data=1):
self.data = data
def __iter__(self):
return self
def __next__(self):
if self.data > 5:
raise StopIteration
else:
self.data+=1
return self.data
for item in test(3):
print(item)
输出结果:
4
5
6
for … in… 这个语句其实做了两件事。第一件事是获得一个可迭代器,即调用了__iter__()
函数。 第二件事是循环的过程,循环调用__next__()
函数。
对于test这个类来说,它定义了__iter__和__next__
函数,所以是一个可迭代的类,也可以说是一个可迭代的对象(Python中一切皆对象)。
迭代器:
含有__next__()
函数的对象都是一个迭代器,所以test也可以说是一个迭代器。如果去掉__itet__()
函数,test这个类也不会报错。如下代码所示:
class test():
def __init__(self,data=1):
self.data = data
def __next__(self):
if self.data > 5:
raise StopIteration
else:
self.data+=1
return self.data
t = test(3)
for i in range(3):
print(t.__next__())
输出结果:
4
5
6
生成器:
生成器是一种特殊的迭代器。当调用fib()函数时,生成器实例化并返回,这时并不会执行任何代码,生成器处于空闲状态,注意这里prev, curr = 0, 1并未执行。然后这个生成器被包含在list()中,list会根据传进来的参数生成一个列表,所以它对fib()对象(一切皆对象,函数也是对象)调用__next__
方法。
def fib(end = 1000):
prev,curr=0,1
while curr < end:
yield curr
prev,curr=curr,curr+prev
print(list(fib()))
输出结果:
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987]
上面只是做了几个演示,这里具体说明一下:
当调用iter函数的时候,生成了一个迭代对象,要求__iter__必须返回一个实现了__next__
的对象,我们就可以通过next函数访问这个对象的下一个元素了,并且在你不想继续有迭代的情况下抛出一个StopIteration的异常(for语句会捕获这个异常,并且自动结束for),下面实现了一个自己的类似range函数的功能。
class MyRange(object):
def __init__(self, end):
self.start = 0
self.end = end
def __iter__(self):
return self
def __next__(self):
if self.start < self.end:
ret = self.start
self.start += 1
return ret
else:
raise StopIteration
from collections.abc import *
a = MyRange(5)
print(isinstance(a, Iterable))
print(isinstance(a, Iterator))
for i in a:
print(i)
输出结果是:
True
True
0
1
2
3
4
接下来我们使用next函数模拟一次:
class MyRange(object):
def __init__(self, end):
self.start = 0
self.end = end
def __iter__(self):
return self
def __next__(self):
if self.start < self.end:
ret = self.start
self.start += 1
return ret
else:
raise StopIteration
a = MyRange(5)
print(next(a))
print(next(a))
print(next(a))
print(next(a))
print(next(a))
print(next(a)) # 其实到这里已经完成了,我们在运行一次查看异常
可以看见一个很明显的好处是,每次产生的数据,是产生一个用一个,什么意思呢,比如我要遍历[0, 1, 2, 3…]一直到10亿,如果使用列表的方式,那么是会全部载入内存的,但是如果使用迭代器,可以看见,当用到了(也就是在调用了next)才会产生对应的数字,这样就可以节约内存了,这是一种懒惰的加载方式。
总结
- 可以使用collection.abs里面的Iterator和Iterable配合isinstance函数来判断一个对象是否是可迭代的,是否是迭代器对象。
- iter实际是映射到了__iter__函数。
- 只要实现了__iter__的对象就是可迭代对象(Iterable),正常情况下,应该返回一个实现了__next__的对象(虽然这个要求不强制),如果自己实现了__next__,当然也可以返回自己。
- 同时实现了__iter__和__next__的是迭代器(Iterator),当然也是一个可迭代对象了,其中__next__应该在迭代完成后,抛出一个StopIteration异常。
- for语句会自动处理这个StopIteration异常以便结束for循环。
3.4 可调用
__call__
函数:
该方法的功能类似于在类中重载 () 运算符,使得类实例对象可以像调用普通函数那样,以“对象名()
”的形式使用。作用:为了将类的实例对象变为可调用对象。
class CLanguage:
# 定义__call__方法
def __call__(self,name,add):
print("调用__call__()方法",name,add)
clangs = CLanguage()
clangs("C语言中文网","http://c.biancheng.net")
程序执行结果为:
调用__call__()方法 C语言中文网 http://c.biancheng.net
函数本身可以被调用:
def func():
pass
print(callable(func)强烈推荐一款Python可视化神器!强烈必备!