一、前言
类的特殊方法,其实就是遇到类代码特定的语法 然后去执行指定的特殊方法。只是一个对应的映射关系比如:
这么多特殊方法,其实是为了不同的特定语法设计,大家都遵守这个约定。
python内部根据特殊的语法帮我们映射到特殊的方法,里面的逻辑由我们自己实现
当然你可以打破这个规定,比如__int__是转换成数字类型,你可以写成相加,但是强烈不推荐这样使用,不然你的代码只有你自己看得懂。别人看起来就费劲。
二、各种特殊方法
2.1 类与对象调用自动执行
__init__
类() 自动执行该方法,又叫构造函数,利用该特性,我们一般用来做封装
__call__
和__init__相对应。__call__是对象()自动运行该方法。
__init__: 类()
__call__: 对象()
class Foo: def __call__(self): print('1') obj = Foo() obj() obj1 =Foo()() # 也可以写成这种形式
__del__
在其他语言中都有析构方法,python也有,但是我们在python中使用不到。
因为python的内存管理机制是在 内存定时刷新,当检测到内存没人引用的时候,就自动销毁这块内存。
2.2 类型转换语法相关
__int__ 重点
我们常常会把数字内容的字符串,转换为数字。 int('123')
其实是类内部遇到这种形式,执行__int__方法,返回了一个数字。
我们里面不一定就要返回数字,只是这是一个约定俗成的规定,大家都这样使用,不容易混淆。
__str__ 重点
__str__ 有两个作用:
第一作用:跟__int__一样,遇见类型转换的格式自动执行该方法里面的内容。约定速成,转换为字符串
第二作用:跟print一起使用,规范化输出,使其输出更明确,易懂。
class Foo:
def __init(self, name, age):
self.name = name
self.age = age
def __str__(self):
return self.name
obj = Foo()
print(obj) # 其实经历了两个步骤,print(str(obj)).
# print 内部有个隐藏的str,要把类型先转换为字符串,
# str(obj) 去obj类内部去调用__str__
# 其实就是第一作用的衍生
__dict__ 重点
__dict__并不算类型转换的一种。这里没地方归位了,先算在这里把。
将对象和类中封装的内容,以字典的形式返回。对象与类都可以调用。
class Foo:
county = 'CN'
def __init__(self, name, age):
self.name = name
self.age = age
self.v = 1
self.__a = '私有变量a'
def __str__(self):
return self.name
obj = Foo('li', 18)
print(obj.__dict__) # 只打印与对象相关的成员
print(Foo.__dict__) # 打印方法,属性,特殊方法等等结果:
{'name': 'li', 'age': 18, 'v': 1, '_Foo__a': '私有变量a'}
{'__module__': '__main__', 'county': 'CN', '__init__': <function Foo.__init__ at 0x104edc488>, '__str__': <function Foo.__str__ at 0x104edc510>, '__dict__': <attribute '__dict__' of 'Foo' objects>, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None}
还有其他一些关于类型转换,比如float,list 等等自己探索
2.3 运算符重载语法相关
obj1 + obj2 ,obj1 += obj2,obj1 - obj2,obj1 < obj2 ,obj = '3'
上面这些都是运算符相关的。 都是特定的语法 映射到 指定的方法。中间的映射是python内部帮我们去掉用,但是特殊方法实现的逻辑由我们自己撰写。
obj1 + obj2 映射到 __add__ 我们用这个来做示范。记得在字符串中,两个字符串可以相加吗?其实就是str类中由__eq__方法
class Foo:
def __init__(self, name,age):
self.name = name
self.age = age
def __add__(self, other):
‘在内部我们想实现什么就实现什么’
# self = obj1 (alex,19)
# other = obj2(eric,66)
# return self.age + other.age
#return Foo('tt',99)
return Foo(obj1.name, other.age)
obj1 = Foo('alex', 19)
obj2 = Foo('eirc', 66)
r = obj1 + obj2
2.4 索引与切片语法 重点
索引语法 __getitem__ __setitem__ __delitem__
li[3] # 获取 li[1] = 2 # 设置 del li[2] # 删除
class Foo:
def __init__(self, name, age):
self.name = name
self.age = age
def __getitem__(self, item):
return item + 10
def __setitem__(self, key, value):
print('我是设置', key, value)
def __delitem__(self, key):
print('我是删除', key)
obj = Foo('li', 18)
obj[1] # 对用__getitem__
obj[1] = 1 # 对应__setitem__
del obj[10] # 对用__delitem__
切片语法:
在python2中还有一个特殊方法映射切片语法,在python3中就已经取消了,还是用上面三者进行操作。
class Foo: def __init__(self, name, age): self.name = name self.age = age def __getitem__(self, item): print(item, type(item)) # return item + 10 obj = Foo('li', 18) obj[1] obj[1:6:3]
结果:
1 <class 'int'>
slice(1, 6, 3) <class 'slice'>
在上面输出可得知,当用于切片操作时候,传递进去的是 slice类型,这也是一个数据类型。
我们看源码,可以使用各种方法,比如 help(slice) dir(slice) 或直接看源码。
能发现三个比较有趣的东西:start,stop,step。 就进行尝试把。我们可以根据item的类型来进行判断。
class Foo:
def __init__(self, name, age):
self.name = name
self.age = age
def __getitem__(self, item):
if type(item) is int:
print('索引处理', item)
elif type(item) is slice:
print('切片处理', item)
print(item.start, item.stop, item.step)
obj = Foo('li', 18)
obj[1]
obj[1:6:3]
运行结果:
索引处理 1
切片处理 slice(1, 6, 3)
1 6 3
这里讲解只对切片的__getitem__进行讲解,其他两个语法用法是差不多的。自己扩展。
2.5 __iter__ 对于for语法
__iter__对应的语法 为 for i in obj:
在理解这个之前需要理解几个概念:迭代器的概念去目录中查找。
# 迭代器与可迭代对象
如果类中有__iter__方法,那这个类生成的对象为 可迭代对象
对象.__iter__ 的返回值为 迭代器# for循环:
1.如果遇到迭代器,则直接使用迭代器的next方法取值
2.如果遇到可迭代对象,则需要两步:
第1步:执行该对象类中的__iter__方法,获取返回值(返回值为迭代器)
第2步:使用迭代器的next方法进行迭代
理解了上面的概念以后,我们就可以为我们的对象进行循环了:
class Foo:
def __init__(self, name, age):
self.name = name
self.age = age
def __iter__(self):
return iter([1, 2, 3, 4]) # 注意此处需要返回一个迭代器,使用iter()把一个列表转换为迭代器对象
obj = Foo('li', 18)
for i in obj:
print(i)