面向对象进阶
类型判断
issubclass
首先,我们先看issubclass()这个内置函数可以帮我们判断x类是否是y类型的子类
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
type
type(obj) 表示查看obj是由哪个类创建的
class Foo:
pass
obj = Foo()
print(obj, type(obj)) # 查看obj的类
isinstance
isinstance可以判断x是否是y类的对象,isinstance可以判断该对象是否是家族体系中的(只能往上判断类)
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
反射
反射的四个函数
1.hasattr(obj, str) 判断obj中是否包含str成员
2.getattr(obj,str) 从obj中获取str成员。
3.setattr(obj, str, value)把obj中的str成员设置成value。这⾥的value可以是值,也可以是函数或者⽅法。
4.delattr(obj, str) 把obj中的str成员删除掉。
importlib
importlib是一个可以根据字符串的模块名实现动态导入模块的库。
举个例子:
目录结构:
├── aaa.py
├── bbb.py
└── mypackage
├── __init__.py
└── 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)
类的其他成员
__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__方法
__repr__
在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
张三
总结:
str函数或者print函数调用的是obj.__str__()
repr函数或者交互式解释器调用的是obj.__repr__()
注意:
如果__str__没有被定义,那么就会使用__repr__来代替输出。
__str__和__repr__方法的返回值都必须是字符串。
__format__
自定制格式化字符串
__format__()传参方法:someobject.__format__(specification)
specification为指定格式,当应用程序中出现"{0:specification}".format(someobject)或format(someobject, specification)时,会默认以这种方式调用
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
__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__通常都是作为一种优化工具来使用。--摘自《Python Cookbook》8.4
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__可以达到这样的目的,但是这个并不是它的初衷。它更多的是用来作为一个内存优化工具。
__item__系列
class Foo:
def __init__(self, name):
self.name = name
def __getitem__(self, item):
print('查值时执行我')
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')
f1['age'] = 18 #obj[key]=lqz赋值时,执行我
print(f1.__dict__) #{'name': 'sb', 'age': 18}
f1.__getitem__('age') #查值时执行我 18
del f1['age'] #del obj[key]时,执行我 注意上下两个del的不同
f1.hobby = '泡妞' #这里无打印 说明没有调用 __setitem__
print(f1.__dict__) #{'name': 'sb', 'hobby': '泡妞'}
del f1.hobby #del obj.key时,执行我
f1['name'] = 'lqz' #obj[key]=lqz赋值时,执行我
print(f1.__dict__) #{'name': 'lqz'}
__init__
使用Python写面向对象的代码的时候我们都会习惯性写一个__init__ 方法,__init__方法通常用在初始化一个类实例的时候。例如:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self):
return '<Person: {}({})>'.format(self.name, self.age)
p1 = Person('张三', 24)
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__,创建类实例
# 调用__init__,初始化实例
# <Person: 张三(24)>
__new__方法在类定义中不是必须写的,如果没定义的话默认会调用object.__new__去创建一个对象(因为创建类的时候默认继承的就是object)。
如果我们在类中定义了__new__方法,就是重写了默认的__new__方法,我们可以借此自定义创建对象的行为。
举个例子:
重写类的__new__方法来实现单例模式。
class Singleton:
# 重写__new__方法,实现每一次实例化的时候,返回同一个instance对象
def __new__(cls, *args, **kw):
if not hasattr(cls, '_instance'):
cls._instance = super().__new__(Singleton)
return cls._instance
def __init__(self, name, age):
self.name = name
self.age = age
s1 = Singleton('张三', 24)
s2 = Singleton('李四', 20)
print(s1, s2) # 这两实例都一样
print(s1.name, s2.name)
# <__main__.Singleton object at 0x000001A4F9BD7F10> <__main__.Singleton object at 0x000001A4F9BD7F10>
# 李四 李四
__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可以调用
注意:
__new__、__init__、__call__等方法都不是必须写的。
__doc__
定义类的描述信息。注意该信息无法被继承。
class A:
"""我是A类的描述信息"""
pass
print(A.__doc__) #我是A类的描述信息
__iter__和__next__
如果一个对象拥有了__iter__和__next__方法,那这个对象就是可迭代对象
可迭代对象的意思是指存储了元素的一个容器对象,且容器中的元素可以通过iter()方法或getitem()方法访问。并不是指某种具体的数据类型 。
迭代是Python最强大的功能之一,是访问集合元素的一种方式。
迭代器是一个可以记住遍历的位置的对象。
迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。
迭代器有两个基本的方法:iter() 和 next()。
字符串,列表或元组对象都可用于创建迭代器:
class A:
def __init__(self, start, stop=None):
if not stop:
start, stop = 0, start
self.start = start
self.stop = stop
def __iter__(self):
return self
def __next__(self):
if self.start >= self.stop:
raise StopIteration #如果start大于或等于stop 停止迭代
n = self.start
self.start += 1
return n
a = A(1)
from collections.abc import Iterator #Iterator 迭代器
print(isinstance(a, Iterator)) #true 说明a是一个迭代器
print(a.__dict__) #{'start': 0, 'stop': 1}
for i in A(6, 5):
print(i) #什么也不打印
for i in A(1, 5):
print(i) # 1 2 3 4
for i in A(5): #此时end为0
print(i) # 0 1 2 3 4
aaa=A(1)
print(next(aaa)) # 第一次打印 0 next() 返回迭代器的下一个项目。
print(next(aaa)) # 第二次 抛异常
__enter__和__exit__
一个对象如果实现了__enter__和___exit__方法,那么这个对象就支持上下文管理协议,即with语句
上下文管理协议:自动清理/释放代码块中程序占用的资源,无须考虑这些问题
执行流程:
1.with obj --->触发obj.__enter__(),需要在obj里写__enter__(self),在它里边写返回值,返回给 as f
2.with obj as f 等同于 f = obj.__enter__()
3.执行with代码块里的程序
4.执行正常时,顺序执行obj中的__exit__()方法,exc_type, exc_val, exc_tb的值都是None.
存在异常时,__exit__()方法中
a. return False返回假时,抛出系统异常。
b. return True 返回假时,吞掉系统异常,自己定制异常信息。
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打印出oo
print('嘿嘿嘿')
# 进入with语句块时执行此方法,此方法如果有返回值会赋值给as声明的变量
# 进入with语句块
# oo
# 退出with代码块时执行此方法
# 1 None
# 2 None
# 3 None
# 嘿嘿嘿
上下文管理协议适用于那些进入和退出之后自动执行一些代码的场景,比如文件、网络连接、数据库连接或使用锁的编码场景等。
__len__
拥有__len__方法的对象支持len(obj)操作。
class A:
def __init__(self):
self.x = 1
self.y = 2
def __len__(self):
return len(self.__dict__)
a = A()
print(len(a)) # 2
__hash__
拥有__hash__方法的对象支持hash(obj)操作。
class A:
def __init__(self):
self.x = 1
self.x = 2
def __hash__(self):
return hash(str(self.x) + str(self.x))
a = A()
print(hash(a)) #-5456844205465054042
__eq__
拥有__eq__方法的对象支持相等的比较操作
class A:
def __init__(self,x,y):
self.x = x
self.y = y
def __eq__(self,obj):
# 打印出比较的第一个对象的x值
print(self.x)
# 打印出比较的第二个对象的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)
#1
# 2
# True
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?