(一)面向对象高级
常用的内置函数
上一篇文章讲到了类的定义和基本使用,可以通过点操作符去访问属性和方法,但是如果属性不存在会怎么办呢?同时不希望因为属性不存在而报错又该怎么处理呢?下面介绍几个能够帮助我们解决问题的关键词:
##has 判断属性存不存在
##get 获取属性
##set 设置属性
##attr 属性
class Person:
def __init__(self,name,age):
self.name = name
self.age = age
zs = Person('张三',20)
print(hasattr(zs,'sex'))
print(getattr(zs,'name'))
通过运行可以看到,使用hasattr()
判断属性是否存在时,结果返回的是布尔值(True or False)。而getattr()
和我们在交互模式中使用zs.name
获取的结果也是一样的,这是因为实际上我们在使用zs.name
时,底层方法就是在使用getattr()
,只不过我们平时是使用后者,而系统帮我们自动调用getattr()
。接下来是setattr()
class Person:
def __init__(self,name,age):
self.name = name
self.age = age
zs = Person('张三',20)
setattr(zs,'name','李四')
class Person:
def __init__(self,name,age):
self.name = name
self.age = age
zs = Person('张三',20)
setattr(zs,'sex','男')
从这里对比可以看出,setattr()
方法对于属性是否存在,运行结果是有区别的,如果属性已经存在,则会替换原来属性的内容,如果属性不存在,则会设置添加新的属性。这里的添加是对实例对象的单独添加,与初始时设置无关。比如在设置一个实力对象小蔡,则不会有sex这个属性。当不存在属性且不想要报错时,可以使用if语句。
class Person:
def __init__(self,name,age):
self.name = name
self.age = age
zs = Person('张三',20)
if hasattr(zs,'sex'):
print(getattr(zs,'sex'))
else:
setattr(zs,'sex','男')
这里关于setattr()
没有写print是根据功能来判断的,gerattr()
是我们需要获取到内容,所以需要print来返回给我们,而setattr()
只是添加进新的属性,并不需要返回值,需要值的话只需要对其进行操作就可以了。
最后再补充三个关键词:delattr() #删除属性
type() 查看数据类型并返回类型
isinstance(数据,(类型1,类型2,类型3)) 判断数据是否属于某个类型
(二)魔法方法
①Python中很多内置方法都是两个下划线在两边,中间是单词名字,并且这样的方法名字有一定的特殊的含义,把这种方法都称之为魔法方法。
我们知道实例对象都不是互通的,都有自己独立的空间,如果我想要让实例对象完全互通,可以使用new方法来完成单例模式(单个实例)。
class Person():
pass
a = Person()
b = Person()
这里我们定义两个实例对象,通过id()
方法可以看出这两个实例对象是各自独立的。这个时候如果我们给a赋值,那么b是不会有相同的值的。如果我们想要a和b同步接收数据,这时就要用到单例模式。
class Person():
def __new__(cls): #cls类本身,通过具体的类生成属于这个类的实例对象
if not hasattr(cls,'_instance'):
cls._instance = super().__new__(cls) #super调用父类函数
return cls._instance
a = Person()
b = Person()
这段代码通过if判断是否有_instance
这个属性,如果没有执行下一行的代码,通过调用父类函数生成一个实例对象,通过return
返回给实例对象进行下一次循环,第二次循环已经有了实例对象,则if语句不会执行,还是返回之前的实例对象,如此反复。所以都是同一个实例对象,无论对哪个操作,都会同步实现。
②现在有一个列表实例对象和Person实例对象
可以看到这两个实例对象的访问结果是不一样的,这是因为它们所使用的底层魔法方法是不一样的。我们可以通过修改底层代码来打印我们想要的内容。
class Person():
def __str__(self): # 修改访问实例对象时返回的结果
return '这是str打印的对象'
def __repr__(self):
return'这是repr打印的对象'
a = Person()
b = Person()
可以看到a和print(a)渲染的方式不一样,打印的结果也是不一样的。
③实例对象正常情况下是不能被调用的,如果想要实例对象像函数一样被调用可以用
def __call__(self): #让实例对象可以和函数一样被调用
print('这是Person实例对象调用的结果')
(三)协议
(1)序列协议
把类变成列表或元组这种序列类型,通过下标去获取值。
class IndexTuple:
def __init__(self,*args): #如果不确定有多少个数据,定义一个不定长*args
self.values = args #arg本身就是元组
self.index = tuple(enumerate(self.values)) #获取到每一个元素的下标
def __len__(self):
return len(self.values) #通过len方法获取当前类有多少个元素
def __getitem__(self,key): #通过下标获取值
return self.index[key]
def __repr__(self):
return str(self.values)
a = IndexTuple(1,2,3,'a','b',True)
Python中有很多协议,只要遵守这些协议,即可以说是对应的类型,比如上面定义了序列协议,那么这个类就是序列类型。
(2)迭代器协议
我们所知道的for循环就是遵循迭代器协议。iter 可迭代对象 next 迭代器
class number:
def __init__(self,end=10): #定义初始化条件
self.start = 0
self.end = end
def __iter__(self):
return self
def __next__(self):
res = self.start
self.start+=1 #和rage的步长类似,每次累加为1
if self.start > self.end:
raise StopIteration #抛出异常,for循环会自东处理
return res
for i in number(10):
print(i)
所以我们所使用的类似for循环的方法都不是凭空产生的,都是Python前辈已经写好了这些功能,让我们使用起来更便捷。
(3)上下文协议
开始和结束时会自动调用对应的方法。
import time #时间模块
class Runtime: #运行时长
def __enter__(self): #开始运行要做的事情
self.start_time = time.time()
return self.start_time
def __exit__(self,exc_type,exc_val,exc_tb): #结束的时候要做的事情(self后面三个分别对应报错的类型,报错的内容,报错的信息)
self.end_time = time.time()
print('程序运行时间为',self.end_time-self.start_time)
with Runtime():
for i in range(1000000):
pass