python 面向对象的魔法方法详解

用户头像
半面人
关注
发布于: 2020 年 05 月 05 日
python面向对象的魔法方法详解

什么是魔法方法

我们都知道我们在创建一个类的时候python默认是继承与object这个基类的,这个基类里面包含的方法就是我们常说的魔法方法,要想学好python中面向对象的编程,了解这些魔法方法是必要的,接下来我们通过其中的dir()方法,将我们创建类的所有方法打印出来,方便我们研究:

class cesi(object):
"""这是一个用于测试的类"""
def __init__(self, name, age):
super(cesi, self).__init__()
self.name = name
self.age = age
def run(self):
self.name = "Tom"
def main():
t = cesi("Jhon", 18)
for method in dir(t):
print(method)
if __name__ == "__main__":
main()

在其中我们使用了dir()方法,那么他的作用应该很明显了,就是获得我们类的方法列表,包括父类方法

__class__:展示当前对象所属的类及父类

__delattr__:delattr(x, 'foobar') 相等于 del x.foobar

__dict__:以字典的形式展示当前对象的属性

__dir__:返回对象方法的列表

__doc__:描述类的信息

__eq__:拥有__eq__方法的对象支持相等的比较操作

__format__:格式化字符串的方法

__ge__:可比较的类,跟__eq__同属一类内置方法

__getattribute__:获取一个计算属性(无条件的)

__gt__:可比较的类

__hash__:拥有此属性的对象能够进行hash

__init__:创建对象过程进行初始化

__init_subclass__:

__le__:比较属性

__lt__:比较属性

__module__:查看该对象属于哪个模块

__ne__:比较属性

__new__:在对象被创建的时候回自动调用,返回内存空间

__reduce__:

__reduce_ex__:

__repr__:将对象转化为供解释器读取的形式

__setattr__:设置某属性的值

__sizeof__:返回地址

__str__:如果一个类中定义了__str__方法,那么在打印 对象 时,默认输出该方法的返回值

__subclasshook__:控制某个类是否是该类的子类

__weakref__:

我们现在先在这里将我们的结论整理出来,后面再进行展示。

方法详解

类的基础方法



  1. 初始化一个实例对象,自动调用__init__方法,这个没什么好说的

  2. 在Pyhton 中,如果想定制化打印出实例对象的信息,那可以使用__str____repr__魔法方法来定制化显示。

在Python中,打印一个自定义类的实例对象所显示的信息往往是很不友好的,如下所示:

>>> class Animal():
... def __init__(self,name):
... self.name = name
...
>>> dog = Animal('monkey')
>>> print(dog)
<__main__.Animal instance at 0x0000000003664F88>

__str____repr__ 所起的作用就是定制化显示输出信息(你想输出什么就是什么),当然,两者也有区别的,区别如下:

  • 对于printstr内置函数,程序会首先尝试__str__,如果没有__str__,则尝试__repr__,如果没有__repr__,则选用默认显示。

  • 在交互式回应,repr函数中__repr__会被调用,你可以这么认为,__str__ 是给用户看的,__repr__则是给开发者看的. 看下面示例:

>>> class Animal():
... def __init__(self,name):
... self.name = name
... def __str__(self):
... return "i am %s" % self.name
... def __repr__(self):
... return "Animal:name is %s" % self.name
...
>>> dog = Animal('kitty')
>>> dog
Animal:name is kitty
>>> print(dog)
i am kitty
>>> str(dog)
'i am kitty'
>>> repr(dog)
'Animal:name is kitty'
  1. __bytes__:返回字节型编码,一般在str型中有类似方法,方便产生字节型编码

  2. __format__格式化字符串,在我们使用python的过程中往往会有字符串格式化输出的需求,我们通常用的%方法,或者format方法,使用format方法的前提是我们的类里面封装有format方法,如果没有,其返回的是str方法的返回值。其使用展示如下:

class Student:
def __init__(self, name, age):
self.name = name
self.age = age
__format_dict = {
'n-a': '名字是:{obj.name}-年龄是:{obj.age}', # 名字是:maple-年龄是:18
'n:a': '名字是:{obj.name}:年龄是:{obj.age}', # 名字是:maple:年龄是:18
'n/a': '名字是:{obj.name}/年龄是:{obj.age}', # 名字是:/年龄是:18
}
def __format__(self, format_spec):
if not format_spec or format_spec not in self.__format_dict:
format_spec = 'n-a'
fmt = self.__format_dict[format_spec]
print(fmt) #{obj.name}:{obj.age}
return fmt.format(obj=self)
s1 = Student('maple', 24)
ret = format(s1, 'n/a')
print(ret) # maple/24

我们如果在类中定义这样一个方法,方便我们进行格式化的输出

类之间的比较

类之间的比较,要使用上述方法进行重新封装,比如代码如下:

class A(object):
def __init__(self, name, x):
self.name = name
self.x = x
def __repr__(self):
return self.name
def __str__(self):
return "I'am right"
def __eq__(self, obj):
if self.name == obj.name :
return True
else:
return False
a = A('zzy', 6)
b = A('zzy', 7)
print(str(a))
print(a == b)

输出结果是显然的,结果为True,其他的方法也是类似,这里就不再一一列举了

迭代器

python中如果想要通过for等方法将内部的元素提取出来,首先对象应该是可迭代的,我们有一些简单的方法能够判断一个对象是否可以迭代:

from collections import Iterable
isintance([11,22,33,44],Iterable)
out: True

如果进一步的我们还需要判断一个对象是否为迭代器,那么采用如下方法:

from collections import Iterator
isintance([11,22,33,44],Iterator)
out: True

到了这里,我们就会想了,什么样的对象是可迭代的,什么样的对象又可以称为一个迭代器呢,我们一步一步来

  • 可迭代对象

只要实现了__iter__方法,那么这个对象就是可迭代的,上代码演示:

import time
from collections.abc import Iterable
from collections.abc import Iterator
class person(object):
def __init__(self):
self.name = list()
self.current_num = 0
def add(self, name):
self.name.append(name)
def __iter__(self):
return [11,22,33].__iter__()
def main():
zzy = person()
print("Whether the object can be iterated: {}".format(isinstance(zzy, Iterable)))
for i in zzy:
print(i)
if __name__ == '__main__':
main()

输出为:

Whether the object can be iterated: True

11

22

33

  • 迭代器

除了实现__iter__方法外,还要实现__next__方法,这样的对象,我们称为迭代器

import time
from collections.abc import Iterable
from collections.abc import Iterator
class person(object):
def __init__(self):
self.name = list()
self.current_num = 0
def add(self, name):
self.name.append(name)
def __iter__(self):
return self
def __next__(self):
if self.current_num < len(self.name):
ret = self.name[self.current_num]
self.current_num += 1
return ret
else:
raise StopIteration
def main():
zzy = person()
print("Whether the object can be iterated: {}".format(isinstance(zzy, Iterator)))
zzy.add("Jhon")
zzy.add("yinxiaohui")
zzy.add("DD")
for i in zzy:
print(i)
if __name__ == '__main__':
main()

通过上述方法实现一个简单的迭代器,其输出为:

Whether the object can be iterated: True

Jhon

yinxiaohui

DD

计算属性

上面的方法基本上都是和属性的设置相关的,如果我们希望在设置属性或者更改属性的时候我们还希望去做一些额外的事情,我们只需要对上述方法进行简单的重写就可以了,在这里呢,我们以__setattr__方法为例来进行展示,代码如下:

class person(object):
def __init__(self, name):
self.name = name
self.current_num = 0
def __setattr__(self, name, value):
print("This method is called for: {}".format(name))
#self.__dict__[name] = str(value)
super(person, self).__setattr__(name, value)
def main():
a = person("zzy")
if __name__ == '__main__':
main()

显然,这个方法是简单的,但要注意在重写__setattr__方法的时候要注意不要再调用设置属性,那样的话就会陷入无限循环,这样就不太友好了,其余方法的书写也类似,你要么调用父类方法,要么就通过__dict__方法进行处理,因为本质上,这个父类方法就是调用__dict__方法。

行为方式与函数类似的类

很明显,通过上述的方法描述我们很容易知道,这个方法的作用,但现在比较重要的问题是,这个函数在什么时候会被调用,下面我们测试一下:

class person(object):
def __init__(self, name):
self.name = name
def __call__(self):
print("I was called")
def main():
a = person("zzy")
if __name__ == '__main__':
main()

我们发现,在进行对象创建过程中,属性并没有被调用,那什么时候方法会被调用呢?

class person(object):
def __init__(self, name):
self.name = name
def __call__(self):
print("I was called")
def main():
a = person("zzy")()
if __name__ == '__main__':
main()

这时候,我们发现我们的call方法被调用了,也就是说,如果我们想要去调用call方法,需要在后面再加一个传入函数参数的括号

行为方式与序列类似的类

这个感觉并没有什么好说的,我们在对方法进行重定义的时候注意一下就可以

行为方式与字典相似的类

方法演示:

class Foo(object):
def __getitem__(self, key):
print ('__getitem__',key)
def __setitem__(self, key, value):
print ('__setitem__',key,value)
def __delitem__(self, key):
print ('__delitem__',key)
obj = Foo()
result = obj['k1'] # 自动触发执行 __getitem__
obj['k2'] = 'maple' # 自动触发执行 __setitem__
del obj['k1'] # 自动触发执行 __delitem__

输出结果也是简单并且可预见的:

__getitem__ k1

__setitem__ k2 maple

__delitem__ k1

可序列化的类



这个地方目前涉及比较少,以后遇到再补充

可以再with语块中使用的类

这里我们没有什么要去补充的,其具体的常用语法如下:

# excerpt from io.py:
def _checkClosed(self, msg=None):
'''Internal: raise an ValueError if file is closed '''
if self.closed:
raise ValueError('I/O operation on closed file.' if msg is None else msg)
def __enter__(self):
'''Context management protocol. Returns self.'''
self._checkClosed()
return self
def __exit__(self, *args):
'''Context management protocol. Calls close()'''
self.close()

类的创建中常用的方法



说在后面的话

在文章的整理过程中,有一些参考,下面将感觉写的不错的博客附录在下面,方便大家查阅

https://www.cnblogs.com/angelyan/p/10366961.html#_labelTop

用户头像

半面人

关注

还未添加个人签名 2020.04.29 加入

还未添加个人简介

评论

发布
暂无评论
python面向对象的魔法方法详解