什么是魔法函数?
Python的一种高级语法
一般用于在类中自定义函数(函数名格式一般为__xx__),并且绑定到类的特殊方法中。
比如在类A中自定义__str__()
函数,则在调用str(A())时,会自动调用__str__()
函数,并返回相应的结果。
-
Python中以双下划线(xx)开始和结束的函数(不可自己定义)为魔法函数。
-
调用类实例化的对象的方法时自动调用魔法函数。
-
在自己定义的类中,可以实现之前的内置函数。
魔法函数的应用
魔法函数可以为你写的类增加一些额外功能,
举个简单的例子,我们定义一个“人”的类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实例时自动调用。
非数学运算——魔法函数
一、字符串表示
1. __repr__函数
函数__str__()
用于将值转化为适于人阅读的形式,而__repr__()
转化为供解释器读取的形式,某对象没有适于人阅读的解释形式的话,__str__()
会返回与__repr__()
,所以print展示的都是__str__()
的格式。
class CLanguage:
pass
clangs = CLanguage()
print(clangs)
>>>
<__main__.CLanguage object at 0x000001A7275221D0>
默认情况下,我们得到的信息只会是“类名+object at+内存地址”,对我们了解该实例化对象帮助不大.
有没有可能自定义输出实例化对象时的信息呢?答案是肯定,通过重写类的 __repr__()
方法即可。事实上,当我们输出某个实例化对象时,其调用的就是该对象的__repr__()
方法,输出的是该方法的返回值。
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()
clangs
>>>
CLanguage[name=C语言中文网,add=http://c.biancheng.net]
通过 自定义__repr__()
方法实现重写
2. __str__函数
未修改__str__()
前,示例:
class Cat:
"""定义一个猫类"""
def __init__(self, new_name= "汤姆", new_age= 20):
"""在创建完对象之后 会自动调用, 它完成对象的初始化的功能"""
self.name = new_name
self.age = new_age
tom = Cat("Tom",30)
print(tom)
>>>
<__main__.Cat object at 0x0000012F7F80D040>
修改后:
class Cat:
"""定义一个猫类"""
def __init__(self, new_name= "汤姆", new_age= 20):
"""在创建完对象之后 会自动调用, 它完成对象的初始化的功能"""
self.name = new_name
self.age = new_age # 它是一个对象中的属性,在对象中存储,即只要这个对象还存在,那么这个变量就可以使用
def __str__(self):
"""返回一个对象的描述信息"""
# print(num)
return "名字是:%s , 年龄是:%d" % (self.name, self.age)
# 创建了一个对象
tom = Cat("汤姆", 30)
print(tom)
>>>
名字是:汤姆 , 年龄是:30
__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输出的信息却改变了
二、集合序列相关
3. __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
4.__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')
s[0]
s[1]
>>>
The
time
通过测试发现,示例 s 可以正常迭代。但是没有定义getitem() 测试则会报错,TypeError: '***' object is not subscriptable
。
列可以迭代:
我们都知道序列是可以迭代,下面具体说明原因。
解释器需要迭代对象x时, 会自动调用iter(x)
方法。内置的 iter(x)
方法有以下作用:
-
检查对象是否实现了
__iter__
方法,如果实现了就调用它(也就是我们偶尔用到的特殊方法重载),获取一个迭代器。 -
如果没有实现
iter()
方法, 但是实现了__getitem__
方法,Python会创建一个迭代器,尝试按顺序(从索引0开始,可以看到我们刚才是通过s[0]取值)获取元素。 -
如果尝试失败,Python抛出TypeError异常,通常会提示
TypeError: '***' object is not ``subscriptable
。
任何Python序列都可迭代的原因是,他们都实现了__getitem__
方法。其实,标准的序列也都实现了__iter__
方法。
注意: 从python3.4 开始,检查对象x能否迭代,最准确的方法是: 调用iter(x)
方法,如果不可迭代,在处理TypeError异常。这比使用isinstance(x,abc.Iterable)
更准确,因为iter()
方法会考虑到遗留的__getitem__()
方法,而abc.Iterable
类则不考虑。
凡是在类中定义了这个__getitem__
方法,那么它的实例对象(假定为p),可以像这样p[key] 取值,当实例对象做p[key] 运算时,会调用类中的方法__getitem__
。
一般如果想使用索引访问元素时,就可以在类中定义这个方法(getitem(self, key) ),当实例对象通过[] 运算符取值时,会调用它的方法__getitem__
。
class DataBase:
'''Python 3 中的类'''
def __init__(self, id, address):
'''初始化方法'''
self.id = id
self.address = address
self.d = {self.id: 1,
self.address: "192.168.10.10",
}
def __getitem__(self, key):
# return self.__dict__.get(key, "100")
return self.d.get(key, "default")
data = DataBase(1, "192.168.2.11")
print(data["hi"])
print(data[data.id])
>>>
default
1
or
class DataBase:
'''Python 3 中的类'''
def __init__(self, id, address):
'''初始化方法'''
self.id = id
self.address = address
def __getitem__(self, key):
return self.__dict__.get(key, "100")
data = DataBase(1, "192.168.2.11")
print(data["hi"])
print(data["id"])
>>>
100
1
还可以用在对象的迭代上
class STgetitem:
def __init__(self, text):
self.text = text
def __getitem__(self, index):
result = self.text[index].upper()
return result
p = STgetitem("Python")
print(p[0])
print("------------------------")
for char in p:
print(char)
>>>
P
------------------------
P
Y
T
H
O
N
5.__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
6.__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'}
7.__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