面向对象的魔法方法
目录
面向对象的魔法方法
一 类型判断、反射与importlib
1 类型判断
1.1 issubclass
"issubclass(x, y)" 这个内置函数可以帮我们判断x类是否是y类型的子类。
官方解释:"issubclass(class, classinfo)"`如果 class 是 classinfo 的 (直接、间接或 虚拟) 子类则返回 True。 类会被视作其自身的子类。 classinfo 也以是类对象的元组,在此情况下 classinfo 中的每个条目都将被检查。 在任何其他情况下,都将引发 TypeError 异常。`
class Base:
pass
class Foo(Base):
pass
class Bar(Foo):
pass
print(issubclass(Bar, Foo)) # True
print(issubclass(Foo, Bar)) # False
print(issubclass(Bar, Base)) # True
1.2 type
"type(object)" 表示查看object是由哪个类创建的。
官方解释:`传入一个参数时,返回 object 的类型。 返回值是一个 type 对象,通常与 object.__class__ 所返回的对象相同。推荐使用 isinstance() 内置函数来检测对象的类型,因为它会考虑子类的情况。`
class Foo:
pass
obj = Foo()
print(obj, type(obj)) # 查看obj的类
1.3 isinstace
"isinstace(x, y)" 可以判断x是否是y类的对象,isinstance可以判断该对象是否是家族体系钟的(只能往上判断类)。
官方解释:"isinstace(object, classinfo)"`如果参数 object 是参数 classinfo 的实例或者是其 (直接、间接或 虚拟) 子类则返回 True。 如果 object 不是给定类型的对象,函数将总是返回 False。 如果 classinfo 是类型对象元组(或由其他此类元组递归组成的元组),那么如果 object 是其中任何一个类型的实例就返回 True。 如果 classinfo 既不是类型,也不是类型元组或类型元组的元组,则将引发 TypeError 异常。`
class Base:
pass
class Foo(Base):
pass
class Bar(Foo):
pass
print(isinstance(Foo(), Foo)) # True
print(isinstance(Foo(), Base)) # True
print(isinstance(Foo(), Bar)) # False
2 反射四函数
2.1 hasattr(obj, str)
# 判断obj中是否包含str成员
2.2 getattr(obj, str[, default])
# 从obj中获取str成员。如果指定的属性不存在,且提供了 default 值,则返回它,否则触发 AttributeError。
2.3 setattr(obj, str, value)
# 把obj中的str成员设置成value。这⾥的value可以是值,也可以是函数或者⽅法。
2.4 delattr(obj, str)
# 把obj中的str成员删除掉。
3 importlib库
Importlib是python的一个可以根据字符串的模块名实现动态导入模块的库,通过导入importlib,调用import_module()方法,传入用户想要获取的模块对应的'路径字符串',即可获取一个模块module,module可以调用这个test模块下的所有属性和方法
# 举个例子# 目录结构:├── aaa.py├── bbb.py└── mypackage ├── __init__.py └── xxx.py # 使用importlib动态导入模块:bbb.pyimport importlibfunc = importlib.import_module('aaa')print(func)func.f1()m = importlib.import_module('mypackage.xxx')print(m.age)
二 面向对象的特殊方法
方法(绑定给类或者对象的方法),函数(普通的函数)。 一个类可以通过定义具有特殊名称的方法,来实现由特殊语法所引发的特定操作(例如算术运算或下标与切片)。这是Python实现"操作符重载"的方式,允许每个类自行定义基于操作符的特定行为。
1 基本的方法
1.1 object.__new__(cls[,...])
"造出一个空对象"。可以人为地控制类生成一个空对象,或者其他任意对象,使用"return"将生成的对象返回出来。
1.2 object.__init__(self[,...])
"把属性放入对象中"。在类实例化的时候,为默认的空对象进行传参,将定制的属性放入对象中,如果空对象是使用`__new__`制造出来的,那么就对这个对象进行传参、添加属性。
"比喻:__new__制造了一个没有装备的人物,__init__就是给这个人物穿戴装备"。
# 代码示例:"""如果返回值是1,那么我们就是制造出了一个>>>int对象<<<,那么它将没有name属性,调用__init__()时会报错;如果是Car的对象,它也没有name属性,无法使用__init__()放置name属性。"""class Car: passclass Foo(): def __new__(cls, *args, "kwargs): return 1 # return Car() # 执行逻辑:obj=Car() obj.__init__() def __init__(self, name): self.name = name print('我是__init__')f = Foo('XXX')print(f, type(f))# print(f.name)
1.3 object.__del__(self)
析构方法,当对象在内存中被释放时,自动触发执行。 "注:"此方法一般无须定义,因为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
1.4 object.__repr__(self)
在python解释器环境下,会默认显示对象的repr表示。
>>> class Student:... def __init__(self, name, age):... self.name = name... self.age = age... def __repr__(self):... return self.name... >>> s1 = Student('张三', 24)>>> s1张三
1.5 __str__
改变对象的字符串显示。可以理解为使用print函数打印一个对象时,会自动调用对象的`__str__`方法.
class Student: def __init__(self, name, age): self.name = name self.age = age # 定义对象的字符串表示 def __str__(self): return self.name s1 = Student('张三', 24)print(s1) # 会调用s1的__str__方法
"总结": 1) str函数或者print函数调用的是obj.__str__() 2) repr函数或者交互式解释器调用的是obj.__repr__()"注意": 1)、如果__str__没有被定义,那么就会使用__repr__来代替输出。 2)、__str__和__repr__方法的返回值都必须是字符串。
1.6 __format__
class Student: def __init__(self, name, age): self.name = name self.age = age __format_dict = { 'n-a': '名字是:{obj.name}-年龄是:{obj.age}', # 名字是:lqz-年龄是:18 'n:a': '名字是:{obj.name}:年龄是:{obj.age}', # 名字是:lqz:年龄是: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('lqz', 24)ret = format(s1, 'n/a')print(ret) # lqz/24
1.7 富比较方法
1.7.1 __ep__
在对象 == 比较的时候触发执行。拥有`__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 Falsea = A(1,2)b = A(2,1)print(a == b)
2 自定义属性访问
2.1 object.__getattr__(self, name)
触发条件:当无法通过正常机制`self.name`找到属性(即取值属性不存在),就会触发`__getattr__()`的调用,(根据子代码的控制来执行)。
2.2 object.__setattr__(self, name, value)
此方法在一个属性被尝试赋值时被调用。这个调用会取代正常机制(即将值保存到实例字典,"【可以自定义条件限制/控制赋值操作】")。赋值给一个实例属性:`object.__setattr__(self,name,value)`。
2.2.1 递归现象的出现的两种情况
1)`通过反射赋值 setattr(obj,key,value)`,会造成递归问题; 2)`self.key=value`,也会造成递归问题。
# 递归现象出现示例:class Foo: def __init__(self, name): self.name = name def __getattr__(self, item): print(item) # return 99 # 无论如何都可以返回99 def __setattr__(self, key, value): # print(key, value) if key == 'age': if isinstance(value, int): # if value.isdigit(): # 往对象中放属性,反射 # setattr(self, key, value) # setattr() 本质还是self.key=value,与__setattr__一起无限循环递归 # self.age = value # RecursionError: maximum recursion depth exceeded while calling a Python object # 可以使用的方式: # self.__dict__[key] = value # 这种方式解决递归问题 # object 是一个类,那么调用对象的绑定方法__setattr__就是一个普通函数,有几个函数,就传几个参数 object.__setattr__(self, key, value) else: raise Exception('不能这么放') def __delattr__(self, item): print('delete') def printsize(self): print(self.__size)f = Foo('lxx')# print(f.__dict__)# f.age = '19' # 字符串,不符合定义的场景f.age = 19print(f.age)object.__setattr__(f, '_Foo__size', 178) # 私有化属性# print(f.size)f.printsize()print(f._Foo__size)
2.2.2 避免递归
1) self.__dict__[key] = value2) object.__setattr__(self, key, value)
2.2.3 属性私有化之后的赋值操作
object.__setattr__(f, '_Foo__size', 178)的使用
2.2.4 应用场景:字典支持中括号取值和赋值,让它也支持 .取值和赋值
# 自定义类,既支持字典的取值赋值方式,也支持 `.`取值与赋值:class Mydic(dict): # ————> 继承了字典类,就支持了字典的"[]"赋值、取值 # pass def __setattr__(self, key, value): # 对象.赋值会触发。 ————> 【当有__setattr__时,会优先使用此方法进行赋值操作,而`.`赋值是对象赋值的方法,所以被识别为赋值操作】 self[key] = value # ————> 触发赋值操作后,内部实现还是使用字典的方法进行赋值。 def __getattr__(self, item): # 对象.取值会触发。 ————> 【由于字典不能使用.取值,所以正常的取值机制不能找到属性,触发了__getattr__的运行】 return self[item] # ————> 使用类自己正常的取值方法/机制,将取值的结果返回。 d = Mydic(name='奥观海', age=50)print(d['name'])print(d['age'])d.name = '涛哥'print(d['name'])"""奥观海50涛哥"""
2.3 object.__delattr__(self, name)
# 触发条件:del obj.name 时触发调用。
2.4 __slots__ 和 __dict__
# Python中的类,都会从object里继承一个__dict__属性,这个属性中存放着类的属性和方法对应的键值对。一个类实例化之后,这个类的实例也具有这么一个__dict__属性。但是二者并不相同。
class A: some = 1 def __init__(self, num): self.num = numa = A(10)print(a.__dict__) # {'num': 10}a.age = 10print(a.__dict__) # {'num': 10, 'age': 10}
从上面的例子可以看出来,实例只保存实例的属性和方法,类的属性和方法它是不保存的。正是由于类和实例有 __dict__ 属性,所以类和实例可以在运行过程动态添加属性和方法。 但是由于每实例化一个类都要分配一个 __dict__ 变量,容易浪费内存。因此在Python中有一个内置的__slots__属性。当一个类设置了__slots__属性后,这个类的__dict__属性就不存在了(同理,该类的实例也不存在__dict__属性),如此一来,设置了__slots__属性的类的属性,只能是预先设定好的。 当你定义__slots__后,__slots__就会为实例使用一种更加紧凑的内部表示。实例通过一个很小的固定大小的小型数组来构建的,而不是为每个实例都定义一个__dict__字典,在__slots__中列出的属性名在内部被映射到这个数组的特定索引上。使用__slots__带来的副作用是我们没有办法给实例添加任何新的属性了。 "注意":尽管__slots__看起来是个非常有用的特性,但是除非你十分确切的知道要使用它,否则尽量不要使用它。比如定义了__slots__属性的类就不支持多继承。__slots__通常都是作为一种优化工具来使用。--摘自《Python Cookbook》8.4
# __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__`可以达到这样的目的,但是这个并不是它的初衷。它更多的是用来作为一个内存优化工具。
3 模拟可调用对象
3.1 object.__call__(self [, args...])
# 官方解释:模拟可调用对象# 类是可以调用的对象。任意类的实例化对象通过在所属的类中定义`__call__()`方法即能称为可调用对象。object.__call__(self[, args...]) 此方法会在实例作为一个函数被“调用”时被调用;如果定义了此方法,则 x(arg1, arg2, ...) 就相当于x.__call__(arg1, arg2, ...) 的快捷方式。
class Foo: def __call__(self,*args,"kwargs): print(self) print(args) print(kwargs) return '这是返回值'obj = Foo()"""# 1、要想让obj这个对象变成一个可调用的对象,需要在该对象的类中定义一个方法__call__方法,该方法会在调用对象时自动触发# 2、调用obj的返回值就是__call__方法的返回值"""res = obj(1,2,3,x=1,y=2)# res = obj()print('===>')print(res)"""<__main__.Foo object at 0x000002702D2C4E80>(1, 2, 3){'x': 1, 'y': 2}===>这是返回值"""
4 模拟容器类型
4.1 object.__getitem__(self, key)
调用此方法实现"`self[key]`"的求值。
# 官方文档的注意事项:1). 对于序列类型【列表(list),元组(tuple),字符串(str),字节数组(bytes)】,接受的键应为整数和切片对象。2). 请注意负数索引(如果类想要模拟序列类型)的特殊解读是取决于__getitem__()方法。3). 如果"key"的类型不正确则会引发"TypeError"异常;如果为序列索引集范围以外的值(在进行任何符数索引的特殊解读之后)则应引发"IndexError"异常。4). 对于映射类型,如果"key"找不到(不在容器中)则应引发"KeyError"异常。5). "for"循环在有不合法索引时会期待捕获"IndexError"以便正确地检测到序列地结束。
4.2 object.__setitem__(self, key, value)
调用此方法实现"`self[key]`"的赋值。注意事项与上同。为对象实现此方法,应该仅限于需要映射允许基于键修改值或添加键,或是序列允许元素被替换时。不正确地`key`值所引发地异常与`__getitem__`方法地情况相同。
# # 示例:不继承字典,还支持[]赋值取值# 取值、赋值地时候触发方法地允许,并通过反射进行class Foo: pass def __setitem__(self, key, value): # setattr(self, key, value) # ————>反射地进行放值 object.__setattr__(self, key, value) def __getitem__(self, item): # return getattr(self, item) # ————>反射地进行取值 return object.__getattribute__(self, item)f = Foo()f.name = 'lxx'print(f.name)f['age'] = 19 # 不重写setitem与getitem方法 # TypeError: 'Foo' object does not support item assignmentprint(f.age)print(f['age'])
5 模拟上下文管理器
5.1 object.__enter__(self)
5.2 object.__exit__(self, exc_type, exc_value, traceback)
一个对象如果实现了`__enter__` 和 `__exit__` 方法,那么这个对象就支持上下文管理协议,即with语句。 "使用场景:"上下文管理协议适用于那些进入和退出之后自动执行一些代码的场景,比如文件、网络连接、数据库连接或使用锁的编码场景等。
class A: def __enter__(self): print('进入with语句块时执行此方法,此方法如果有返回值会赋值给as声明的变量') return 'oo' def __exit__(self, exc_type, exc_val, exc_tb): print('退出with代码块时执行此方法') print('1', exc_type) print('2', exc_val) print('3', exc_tb)with A() as f: print('进入with语句块') # with语句中代码块出现异常,则with后的代码都无法执行。 # raise AttributeError('sb') print(f) #f打印出ooprint('嘿嘿嘿')
6 type 与 object 的关系
6.1 type类:
继承object,是所有类的类(所有的类都是type的实例),包括自己(type是对象,也是自己类的对象)。
class type(object): """ type(object_or_name, bases, dict) type(object) -> the object's type type(name, bases, dict) -> a new type """
6.2 object类:
谁都没继承,它是由type类实例化得到的,是所有类的父类,包括type。

7 其他方法
7.1 __doc__
定义类的描述信息。注意该信息无法被继承。
class A: """我是A类的描述信息""" passprint(A.__doc__)
三 其他对象的操作
1 函数对象
Python中一切皆是对象。"官方文档:对象 是Python中对数据的抽象。Python程序中的所有数据都是由对象或对象间关系来表示的。(从某种意义上来说,按照冯·诺伊曼的“存储程序计算机”模型,代码本身也是由对象来表示的)。" 因此,函数也是一种对象, 它有对象的所有特性。
# 函数对象的 `.`赋值def test(): print('听涛')test.name = '奥观海'test() # ————> 听涛print(test.name) # ————> 奥观海

浙公网安备 33010602011771号