python 面向对象之魔法方法(双下方法)
面向对象之魔法方法(双下方法)
魔法方法指类中定义的一些双下方法,不需要人为调用,在特定的条件下回自动触发运行。
类中的双下方法常常是类产生的对象的一些行为所触发的。
类的常用魔法方法
双下方法 | 自动触发条件 |
---|---|
__init__ |
对象添加独有数据的时候自动触发 |
__str__ |
对象被执行打印操作的时候自动触发 |
__call__ |
对象加括号调用的时候自动触发 |
__getattr__ |
访问对象不存在的属性时自动触发 |
__getattribute__ |
只要访问对象的名字就会触发(不常用) |
__setattr__ |
给对象添加或者修改数据的时候自动触发(会使原本的属性添加或修改失效) |
__enter__ |
当对象被当做with上下文管理操作的开始自动触发 并且该方法返回什么 as后面的变量名就会接收到什么 |
__exit__ |
与enter相对,在上下文管理结束时触发 |
-
__init__
class A: def __init__(self, a, b): self.a = a self.b = b obj = A(11, 22) print(obj.__dict__)
我们可以简单的理解为,在用类名()调用时会触发双下init
-
__str__
class B: def __str__(self): return '我是类B' # 这个方法必须得有一个返回值 obj = B() print(obj) # 我是类B
这个方法必须有一个字符串返回值。
注意这个返回值不能是对象本身或者包含对象的字符串,会导致无限递归。
-
__call__
class C: cid = 114514 def __call__(self, *args, **kwargs): print(self) # 可以接收调用的对象本身 print(args, kwargs) # 可以接收参数 return self.cid # 可以有返回值 obj = C() print(obj(11, 22, name='leethon')) # 当对象被加括号调用时自动触发 """ <__main__.C object at 0x0000023919566610> # print(self) (11, 22) {'name': 'leethon'} # print(args, kwargs) 114514 # return self.cid --> print(返回追) """
拥有call双下方法的类,产生的对象可以加括号被调用,调用时自动触发双下
__call__
来接收参数并返回返回值。 -
__getattr__
class D: cid = 114514 def __getattr__(self, item): return f'没有{item}这个属性' obj = D() print(obj.cid) # 114514 # 当有属性时正常拿值 print(obj.ccid) # 没有ccid这个属性 # 当没有属性时自动调用getattr双下方法,并得到它的返回值
-
__getattribute__
class E: cid = 114514 def __getattr__(self, item): return f'没有{item}这个属性' def __getattribute__(self, item): return '我是老大' obj = E() print(obj.cid) # 我是老大 print(obj.ccid) # 我是老大
无论有没有这个属性,在拿值时都会触发。
而且会覆盖双下getattr的触发。一般不用,因为很容易造成递归。
-
__setattr__
class F: my_flow = [] def __init__(self, name, age): self.name = name self.age = age def __setattr__(self, key, value): self.my_flow.append(f'某一个对象添加了键值对{key}:{value}') obj1 = F('leethon', 18) obj2 = F('jack', 19) print(obj1.__dict__) print(F.my_flow) """ {} # 原本添加属性的动作失效 ['某一个对象添加了键值对name:leethon', '某一个对象添加了键值对age:18', '某一个对象添加了键值对name:jack', '某一个对象添加了键值对age:19'] """
上述程序中,我们在通过类名()触发了双下init,然后得到运行了self.属性的添加操作,从而触发了双下setattr,往我们的类属性列表中依次添加了几条信息。
-
__enter__
和__exit__
class Context: def do_something(self): # 6.开始执行do_something print('hehehe') # 7.第二个被打印的 def __enter__(self): # 2.上下文管理触发来着 print('hahaha') # 3.最先打印 return self # 4.将产生的对象返回给f def __exit__(self, exc_type, exc_val, exc_tb): # 9.上下文结束时执行 print('enenen') # 10.最后被打印 with Context() as f: # 1.先运行context()产生一个对象,对象再被上下文管理触发enter双下 f.do_something() # 5.对象调用绑定对象的动态方法do_something() # 8.子代码结束,触发双下exit方法 """运行结果 hahaha hehehe enenen """
这是一道面试题的改版,代码中也注释了代码运行的顺序。
自定义字典类型并让字典能够通过句点符的方式操作键值对
class MyDict(dict): # 继承原有的字典类
def __init__(self, **kwargs): # 派生原本产生字典对象的方法
super().__init__(self, **kwargs)
def __setattr__(self, key, value): # 设置值的时候会触发这句(并且原本的属性添加失效)
self[key] = value
def __getattr__(self, item): # 拿没有的属性时会触发这句
return self[item]
d1 = {'name': 'leethon', 'age': '18'} # 原本的字典定义方式
d = MyDict(**d1) # 用关键字实参来赋值键值对
print(d.name) # 可以用句点的方式来拿值
d.name = 'lee' # 可以用句点的方式改值
print(d.name)
面向对象的更多双下属性和方法
双下方法 | 自动触发条件 |
---|---|
__repr__ 方法 |
在解释器环境下输出的字符串(通__str__ ) |
__del__ 方法 |
当对象在内存中被释放时,自动触发执行 |
__format__ 方法 |
被格式化输出的字符串 |
__item__ 系列 |
对象进行类字典操作时触发 |
__doc__ 属性 |
定义类的描述信息,该信息不会被继承 |
__iter__和__next__ 方法 |
对象成为可迭代对象,在被迭代时自动触发 |
__len__ 方法 |
让对象可以被len(obj),返回长度 |
__hash__ 方法 |
让对象可以被hash(obj),返回重述值(hash意为混杂、拼凑) |
__eq__ 方法 |
让对象可以定义相等判断 |
-
__item__
系列class Foo: def __init__(self, name): self.name = name def __getitem__(self, item): print(self.__dict__[item]) def __setitem__(self, key, value): print('obj[key]=lqz赋值时,执行我') 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') print(f1.__dict__) f1['age'] = 18 f1.hobby = '泡妞' del f1.hobby del f1['age'] f1['name'] = 'lqz' print(f1.__dict__)
-
__eq__
class A: def __init__(self,x,y): self.x = x self.y = y def __eq__(self,obj): # 打印出比较的第二个对象的x值 print(obj.x) if self.x +self.y == obj.x+obj.y: return True else: return False a = A(1,2) b = A(2,1) print(a == b)