面向对象内置方法(进阶)
一、什么是内置方法?又称魔法方法
1、定义在类内部,以__开头并以__结果的方法
1、__str__:在打印对象时会自动触发,然后将返回值(必须是字符串类型)当做本次打印的结果输出
class people: def __init__(self,age,name): self.age = age self.name = name def __str__(self): print('开始运行了') return 'hahaha' #22 返回了非字符串类型 obj = people(18,'egon') print(obj) obj1 = int(10) print(obj.__str__())
1.1、__repr__
在python解释器环境下,会默认显示对象的repr表示。
1 >>> class Student: 2 ... def __init__(self, name, age): 3 ... self.name = name 4 ... self.age = age 5 ... def __repr__(self): 6 ... return self.name 7 ... 8 >>> s1 = Student('张三', 24) 9 >>> s1 10 张三
总结:
str函数或者print函数调用的是obj.__str__()
repr函数或者交互式解释器调用的是obj.__repr__()
注意:
如果__str__没有被定义,那么就会使用__repr__来代替输出。
__str__和__repr__方法的返回值都必须是字符串。
2、__del__:在清理对象时触发,会先执行该方法
class People: def __init__(self, name, age): self.name = name self.age = age self.x = open('a.txt',mode='w') # self.x = 占据的是操作系统资源 def __del__(self): # print('run...') # 发起系统调用,告诉操作系统回收相关的系统资源 self.x.close() obj = People('kylin', 18) # del obj # obj.__del__() print('============>')
一、isinstance(obj,cls)和issubclass(sub,super)
isinstance(obj,cls)检查是否obj是否是类 cls 的对象
1 class Foo(object): 2 pass 3 4 obj = Foo() 5 6 isinstance(obj, Foo)
issubclass(sub, super)检查sub类是否是 super 类的派生类
1 class Foo(object): 2 pass 3 4 class Bar(Foo): 5 pass 6 7 issubclass(Bar, Foo)
importlib
importlib是一个可以根据字符串的模块名实现动态导入模块的库。
举个例子:
目录结构:
1 ├── aaa.py 2 ├── bbb.py 3 └── mypackage 4 ├── __init__.py 5 └── xxx.py
使用importlib动态导入模块:
bbb.py
import importlib func = importlib.import_module('aaa') print(func) func.f1() m = importlib.import_module('mypackage.xxx') print(m.age)
__format__
1 class Student: 2 def __init__(self, name, age): 3 self.name = name 4 self.age = age 5 6 __format_dict = { 7 'n-a': '名字是:{obj.name}-年龄是:{obj.age}', # 名字是:lqz-年龄是:18 8 'n:a': '名字是:{obj.name}:年龄是:{obj.age}', # 名字是:lqz:年龄是:18 9 'n/a': '名字是:{obj.name}/年龄是:{obj.age}', # 名字是:/年龄是:18 10 } 11 12 def __format__(self, format_spec): 13 if not format_spec or format_spec not in self.__format_dict: 14 format_spec = 'n-a' 15 fmt = self.__format_dict[format_spec] 16 print(fmt) #{obj.name}:{obj.age} 17 return fmt.format(obj=self) 18 19 s1 = Student('lqz', 24) 20 ret = format(s1, 'n/a') 21 print(ret) # lqz/24
__del__
析构方法,当对象在内存中被释放时,自动触发执行。
注:此方法一般无须定义,因为Python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给Python解释器来执行,所以析构函数的调用是由解释器在进行垃圾回收时自动触发执行的。
class A: def __del__(self): print('删除了...') a = A() print(a) # <__main__.A object at 0x10164fb00> del a # 删除了... print(a) # NameError: name 'a' is not defined
__dict__和__slots__
Python中的类,都会从object里继承一个__dict__属性,这个属性中存放着类的属性和方法对应的键值对。一个类实例化之后,这个类的实例也具有这么一个__dict__属性。但是二者并不相同。
class A: some = 1 def __init__(self, num): self.num = num a = A(10) print(a.__dict__) # {'num': 10} a.age = 10 print(a.__dict__) # {'num': 10, 'age': 10}
从上面的例子可以看出来,实例只保存实例的属性和方法,类的属性和方法它是不保存的。正是由于类和实例有__dict__属性,所以类和实例可以在运行过程动态添加属性和方法。
但是由于每实例化一个类都要分配一个__dict__变量,容易浪费内存。因此在Python中有一个内置的__slots__属性。当一个类设置了__slots__属性后,这个类的__dict__属性就不存在了(同理,该类的实例也不存在__dict__属性),如此一来,设置了__slots__属性的类的属性,只能是预先设定好的。
当你定义__slots__后,__slots__就会为实例使用一种更加紧凑的内部表示。实例通过一个很小的固定大小的小型数组来构建的,而不是为每个实例都定义一个__dict__字典,在__slots__中列出的属性名在内部被映射到这个数组的特定索引上。使用__slots__带来的副作用是我们没有办法给实例添加任何新的属性了。
注意:尽管__slots__看起来是个非常有用的特性,但是除非你十分确切的知道要使用它,否则尽量不要使用它。比如定义了__slots__属性的类就不支持多继承。__slots__通常都是作为一种优化工具来使用。
class A: __slots__ = ['name', 'age'] a1 = A() # print(a1.__dict__) # AttributeError: 'A' object has no attribute '__dict__' a1.name = '张三' a1.age = 24 # a1.hobby = '泡妞' # AttributeError: 'A' object has no attribute 'hobby' print(a1.__slots__)
注意事项:
__slots__的很多特性都依赖于普通的基于字典的实现。
另外,定义了__slots__后的类不再 支持一些普通类特性了,比如多继承。大多数情况下,你应该只在那些经常被使用到的用作数据结构的类上定义__slots__,比如在程序中需要创建某个类的几百万个实例对象 。
关于__slots__的一个常见误区是它可以作为一个封装工具来防止用户给实例增加新的属性。尽管使用__slots__可以达到这样的目的,但是这个并不是它的初衷。它更多的是用来作为一个内存优化工具。
__setattr__,__delattr__,__getattr__
1 class Foo: 2 def __init__(self,name): 3 self.name = name 4 5 def __getattr__(self, item):#对象.属性取值就会触发 6 print(item) 7 return 99 8 def __setattr__(self, key, value): 9 if key == 'age': 10 if isinstance(value,int): 11 #往对象中放属性,反射 12 #setattr(self,key,value) #setattr反射本质还调用了self.key=value 13 #self.age = value #这种方式也不行 14 self.__dict__[key] = value #这种方式解决了递归问题 15 # object.__setattr__(self,key,value) 这种也可以是类来调是一种普通函数的用法 16 17 else: 18 raise Exception('不让放') 19 f = Foo('lqx') 20 print(f.name) 21 #f.age = '19'#放的东西必须是数字 22 f.age = 19 23 print(f.age) 24 25 object.__setattr__(f,'size',188) 26 print(f,size) 27 28 print(f.name) 29 print(f.xxx) #不重写getattr会报错
__setitem__,__getitem,__delitem__
class Foo: def __init__(self,name): self.name=name def __getitem__(self, item): print(self.__dict__[item]) def __setitem__(self, key, value): self.__dict__[key]=value
def __delitem__(self, key): print('del obj[key]时,我执行') self.__dict__.pop(key) def __delattr__(self, item): print('del obj.key时,我执行') self.__dict__.pop(item) f1=Foo('sb') f1['age']=18 f1['age1']=19 del f1.age1 del f1['age'] f1['name']='alex' print(f1.__dict__)
__init__
使用Python写面向对象的代码的时候我们都会习惯性写一个 __init__ 方法,__init__ 方法通常用在初始化一个类实例的时候。例如:
1 class Person: 2 def __init__(self, name, age): 3 self.name = name 4 self.age = age 5 6 def __str__(self): 7 return '<Person: {}({})>'.format(self.name, self.age) 8 9 p1 = Person('张三', 24) 10 print(p1)
上面是__init__最普通的用法了。但是__init__其实不是实例化一个类的时候第一个被调用的方法。当使用 Persion(name, age) 来实例化一个类时,最先被调用的方法其实是 __new__ 方法。
__new__
其实__init__是在类实例被创建之后调用的,它完成的是类实例的初始化操作,而 __new__方法正是创建这个类实例的方法
class Person: def __new__(cls, *args, **kwargs): print('调用__new__,创建类实例') return super().__new__(Person) def __init__(self, name, age): print('调用__init__,初始化实例') self.name = name self.age = age def __str__(self): return '<Person: {}({})>'.format(self.name, self.age) p1 = Person('张三', 24) print(p1)
__new__方法在类定义中不是必须写的,如果没定义的话默认会调用object.__new__去创建一个对象(因为创建类的时候默认继承的就是object)。
如果我们在类中定义了__new__方法,就是重写了默认的__new__方法,我们可以借此自定义创建对象的行为。
__call__
__call__ 方法的执行是由对象后加括号触发的,即:对象()。拥有此方法的对象可以像函数一样被调用。
class Person: def __init__(self, name, age): self.name = name self.age = age def __call__(self, *args, **kwargs): print('调用对象的__call__方法') a = Person('张三', 24) # 类Person可调用 a() # 对象a可以调用
__doc__
定义类的描述信息。注意该信息无法被继承。
1 class A: 2 """我是A类的描述信息""" 3 pass 4 5 print(A.__doc__)
__iter__和__next__
如果一个对象拥有了__iter__和__next__方法,那这个对象就是可迭代对象
1 class A: 2 def __init__(self, start, stop=None): 3 if not stop: 4 start, stop = 0, start 5 self.start = start 6 self.stop = stop 7 8 def __iter__(self): 9 return self 10 11 def __next__(self): 12 if self.start >= self.stop: 13 raise StopIteration 14 n = self.start 15 self.start += 1 16 return n 17 18 a = A(1, 5) 19 from collections import Iterator 20 print(isinstance(a, Iterator)) 21 22 for i in A(1, 5): 23 print(i) 24 25 for i in A(5): 26 print(i) 27 aaa=A(1) 28 print(next(aaa)) 29 print(next(aaa)) #抛异常
__enter__和__exit__
一个对象如果实现了__enter__和___exit__方法,那么这个对象就支持上下文管理协议,即with语句
1 class A: 2 def __enter__(self): 3 print('进入with语句块时执行此方法,此方法如果有返回值会赋值给as声明的变量') 4 return 'oo' 5 6 def __exit__(self, exc_type, exc_val, exc_tb): 7 print('退出with代码块时执行此方法') 8 print('1', exc_type) 9 print('2', exc_val) 10 print('3', exc_tb) 11 12 with A() as f: 13 print('进入with语句块') 14 # with语句中代码块出现异常,则with后的代码都无法执行。 15 # raise AttributeError('sb') 16 print(f) #f打印出oo 17 print('嘿嘿嘿')
上下文管理协议适用于那些进入和退出之后自动执行一些代码的场景,比如文件、网络连接、数据库连接或使用锁的编码场景等。
__len__
拥有__len__方法的对象支持len(obj)操作。
1 class A: 2 def __init__(self): 3 self.x = 1 4 self.y = 2 5 6 def __len__(self): 7 return len(self.__dict__) 8 9 a = A() 10 print(len(a))
__hash__
拥有__hash__方法的对象支持hash(obj)操作。
1 class A: 2 def __init__(self): 3 self.x = 1 4 self.x = 2 5 6 def __hash__(self): 7 return hash(str(self.x) + str(self.x)) 8 9 a = A() 10 print(hash(a))
__eq__
拥有__eq__方法的对象支持相等的比较操作
1 class A: 2 def __init__(self,x,y): 3 self.x = x 4 self.y = y 5 6 def __eq__(self,obj): 7 # 打印出比较的第二个对象的x值 8 print(obj.x) 9 if self.x +self.y == obj.x+obj.y: 10 return True 11 else: 12 return False 13 14 a = A(1,2) 15 b = A(2,1) 16 print(a == b)