判断某个类实例对象是否包含指定名称的属性或方法
hasattr(obj, name)
obj表示某个类的实例对象,name 表示指定的属性名或方法名,该函数会将判断的结果(True、False)作为返回值
class test:
def __init__ (self):
self.name = "you"
self.add = "you.com"
def say(self):
print("youchanwill")
test1 = test()
print(hasattr(test1,"name"))
print(hasattr(test1,"add"))
print(hasattr(test1,"say"))
True
True
True
通过hasattr()函数判断实例对象是否包含该名称的属性或方法,但不能精确判断该名称代表的是属性还是方法
获取某个类实例对象中指定属性的值,只会从类对象包含的所有属性中进行查找
getattr(obj, name[, default])
obj表示指定的类实例对象,name 表示指定的属性名,default 是可选参数,用于设定该函数的默认返回值
当函数查找失败时,如果不指定 default 参数,则程序将直接报 AttributeError 错误,反之该函数将返回 default 指定的值
class test:
def __init__ (self):
self.name = "you"
self.add = "you.com"
def say(self):
print("youchanwill")
test1 = test()
print(getattr(test1,"name"))
print(getattr(test1,"add"))
print(getattr(test1,"say"))
print(getattr(test1,"display",'nofind'))
you
you.com
<bound method test.say of <__main__.test object at 0x02A1A690>>
nofind
对于类中已有的属性,getattr()会返回它们的值,如果该名称为方法名,则返回该方法的状态信息
基础的功能是修改类实例对象中的属性值,还可以为实例对象动态添加属性或者方法
setattr(obj, name, value)
class test:
def __init__ (self):
self.name = "you"
self.add = "you.com"
def say(self):
print("youchanwill")
test1 = test()
print(test1.name)
print(test1.add)
setattr(test1,"name","youchanwill")
setattr(test1,"add","you.net")
print(test1.name)
print(test1.add)
you
you.com
youchanwill
you.net
可以将类属性修改为一个类方法,也可以将类方法修改成一个类属性
def say(self):
print("say")
class test:
def __init__ (self):
self.name = "you"
self.add = "you.com"
test1 = test()
print(test1.name)
print(test1.add)
setattr(test1,"name",say)
test1.name(test1)
you
you.com
say
通过修改name属性的值为say(外部定义的函数),name属性变成了name()方法
setattr()函数对实例对象中执行名称的属性或方法进行修改时,如果该名称查找失败,解释器不会报错,而会给该实例对象动态添加一个指定名称的属性或方法
def say(self):
print("say")
class test:
pass
test1 = test()
setattr(test1,"name",'youchanwill')
setattr(test1,"say",say)
print(test1.name)
test1.say(test1)
youchanwill
say
虽然test为空类,但通过setattr()函数,为test1对象动态添加了name 属性和say()方法
重载运算符,指的是在类中定义并实现一个与运算符对应的处理方法,当类对象在进行运算符操作时,系统就会调用类中相应的方法来处理
每种序列类型都是一个类,列表是 list 类,字典是 dict 类等,通过使用重载运算符来实现不同运算符所对应的操作
所以同样的运算符对于不同序列类型的意义是不一样的
class Class: #定义一个类
def __init__(self, name , age):
self.name = name #将传入的参数值赋值给name变量
self.age = age
def __str__(self): #将值转化为字符串形式,等于str(obj)
return "name:"+self.name+";age:"+str(self.age)
__repr__ = __str__ #格式转换,转化为解释器读取的形式
def __lt__(self, record): #__lt__对应于< ,重载self
if self.age < record.age:
return True
else:
return False
def __add__(self, record): #__add__加法运算符+ ,重载+号运算符
return Class(self.name, self.age+record.age)
c = Class("you", 20) #实例化一个对象,并为其初始化
cl = Class("chanwill", 21)
print(repr(c)) #格式化对象c
print(c) #读取对象c,调用 repr
print (str (c)) #格式化对象c
print(c < cl) #比较 c
print (c+cl) #进行两个Class对象的相加运算
name:you;age:20
name:you;age:20
name:you;age:20
True
name:you;age:41
Class类中重载了repr、str、<、+ 运算符,并实例化了两个对象c和cl
将c进行 repr、str 运算,程序调用了重载的操作符方法 __repr__ 和 __str__
c和cl 进行 < 号的比较运算以及加法运算,程序调用了重载的__lt__方法和 __add__方法
Python 常用重载运算符
重载运算符 | 含义 |
---|---|
_new_ | 创建类,在 __init __ 之前创建对象 |
_init_ | 类的构造函数,创建类对象时做初始化工作 |
_del_ | 析构函数,销毁对象时进行回收资源的操作 |
_add_ | 加法运算符 +,如果类中对 __iadd __ 方法进行了重载,则类对象 X 在做 X+=Y 类似操作时,会优先选择调用 _iadd_ 方法 |
_radd_ | 当类对象 X 做类似 Y+X 的运算时,调用此方法 |
_iadd_ | 重载 += 运算符,当类对象 X 做类似 X+=Y 的操作时,会调用此方法 |
_or_ | “或”运算符 |,如果没有重载 __ior__,则在类似 X|Y、X|=Y 这样的语句中,该符号生效 |
_repr_ _str_ | 格式转换方法,对应函数 repr(X)、str(X) |
_call_ | 函数调用,类似于 X(*args, **kwargs) 语句 |
_getattr_ | 点号运算,来获取类属性 |
_setattr_ | 属性赋值语句,类似于 X.any=value |
_delattr_ | 删除属性,类似于 del X.any |
_getattribute_ | 获取属性,类似于 X.any |
_len_ | 计算长度,类似于 len(X) |
_get_ _set_ _delete_ | 描述符属性,类似于 X.attr,X.attr=value,del X.attr |
__lt_,__gt_,__le_,__ge_,__eq__,_ne_ | 比较,分别对应于 <、>、<=、>=、=、!= 运算符 |
__iter__,_next_ | 迭代环境下,生成迭代器与取下一条,类似于 I=iter(X) 和 next() |
_contains_ | 成员关系测试,类似于 item in X |
_index_ | 整数值,类似于 hex(X),bin(X),oct(X) |
_enter_,_exit_ | 在对类对象执行类似 with obj as var 的操作之前,先调用 _enter_ 方法,其结果传给 var;在最终结束该操作之前,调用 _exit_ 方法 |
__call__()方法的功能类似于在类中重载 () 运算符,使类实例对象可以像调用普通函数那样,以“对象名()”的形式使用
class test:
# 定义__call__方法
def __call__(self,name,add):
print("调用__call__()方法",name,add)
test1 = test()
test1("youchanwill","you.com")
调用__call__()方法 youchanwill you.com
通过在类中实现__call__() 方法,使test1实例对象变为了可调用对象
凡是可以将 () 直接应用到自身并执行,都称为可调用对象(包括自定义的函数、Python内置函数以及类实例对象)
可调用对象,“名称()”可以理解为是“名称.__call__()”的简写
test1("youchanwill","you.com")可以改写为如下形式:
test1.__call__("youchanwill","you.com")
hasattr() 函数无法判断该指定的名称,到底是类属性还是类方法,可用 __call__() 弥补 hasattr() 函数的缺陷
类实例对象包含的方法,也属于可调用对象,但类属性却不是
class test:
def __init__ (self):
self.name = "you"
self.add = "you.com"
def say(self):
print("say")
test1 = test()
if hasattr(test1,"name"):
print(hasattr(test1.name,"__call__"))
if hasattr(test1,"say"):
print(hasattr(test1.say,"__call__"))
False
True
name 是类属性,没有以 __call__ 为名的 __call__() 方法
say 是类方法,是可调用对象,有 __call__() 方法
序列最重要的特征就是可包含多个元素, 和序列有关的特殊方法如下:
__len__(self):该方法的返回值决定序列中元素的个数
__getitem__(self, key):获取指定索引对应的元素,该方法的key应为整数值或slice对象,否则会引发 KeyError 异常
__contains__(self, item):判断序列是否包含指定元素
__setitem__(self, key, value):设置指定索引对应的元素,该方法的key应为整数值或slice对象,否则会引发 KeyError 异常
__delitem__(self, key):删除指定索引对应的元素
程序要实现不可变序列(不能修改序列中的元素),只要实现上面前3个方法
程序要实现可变序列(能获取、修改序列中的元素),需要实现上面5个方法
def check_key (key):
'''
该函数检查序列的索引,该索引必须是整数值,否则引发TypeError,且索引必须为非负整数,否则引发IndexError
'''
if not isinstance(key, int): raise TypeError('索引值必须是整数')
if key < 0: raise IndexError('索引值必须是非负整数')
if key >= 26 ** 3: raise IndexError('索引值不能超过%d' % 26 ** 3)
class StringSeq:
def __init__(self):
self.__changed = {
} # 存储被修改的数据
self.__deleted = [] # 存储已删除元素的索引
def __len__(self):
return 26 ** 3
def __getitem__(self, key): # 根据索引获取序列中元素
check_key(key)
if key in self.__changed : # 找到已经修改后的数据
return self.__changed[key]
if key in self.__deleted : # 说明该元素已被删除
return None
three = key // (26 * 26) # 根据计算规则返回序列元素
two = ( key - three * 26 * 26) // 26
one = key % 26
return chr(65 + three) + chr(65 + two) + chr(65 + one)
def __setitem__(self, key, value):
check_key(key) # 根据索引修改序列中元素
self.__changed[key] = value #将修改的元素以key-value对的形式保存在__changed中
def __delitem__(self, key):
check_key(key) # 根据索引删除序列中元素
if key not in self.__deleted : self.__deleted.append(key) # 如果__deleted列表中没有包含被删除key,添加被删除的key
if key in self.__changed : del self.__changed[key] # 如果__changed中包含被删除key,删除key
sq = StringSeq() # 创建序列
print(len(sq)) # 获取序列的长度(返回__len__()方法的返回值)
print(sq[10*16])
print(sq[1]) # 没修改之后的sq[1]
sq[1] = 'chanwill' # 修改sq[1]元素
print(sq[1]) # 修改之后的sq[1]
del sq[1] # 删除sq[1]
print(sq[1])
sq[1] = 'you' # 再次对sq[1]赋值
print(sq[1])
17576
AGE
AAB
chanwill
None
you
该类实现了__len__()、 __getitem__()、 __setitem__() 和 __delitem__() 方法
__len__() 方法返回该序列包含的元素个数,__getitem__() 方法根据索引返回元素,__setitem__()方法根据索引修改元素的值,__delitem__() 方法用于根据索引删除元素
该序列本身并不保存序列元素,会根据索引动态计算序列元素,因此序列需要保存被修改、被删除的元素,使用 __changed 实例变量保存被修改的元素,使用 __deleted 实例变量保存被删除的索引
迭代器指的是支持迭代的容器类对象
容器可以是列表、元组等这些 Python提供的基础容器,也可以是自定义的容器类对象,只要该容器支持迭代即可
要自定义实现一个迭代器,类中必须实现如下方法:
__iter__(self):返回一个迭代器(iterator)
__next__(self):返回容器的下一个元素
class iter_list:
def __init__(self):
self.__date=[]
self.__step = 0
def __next__(self):
if self.__step <= 0:
raise StopIteration
self.__step -= 1
return self.__date[self.__step] #返回下一个元素
def __iter__(self):
return self #实例对象本身就是迭代器对象,直接返回self即可
def __setitem__(self,key,value): #添加元素
self.__date.insert(key,value)
self.__step += 1
test1 = iter_list()
test1[0]=10
test1[1]=16
for i in test1:
print (i)
16
10
iter() 函数会返回一个迭代器
iter(obj[, sentinel])
obj 必须是一个可迭代的容器对象,sentinel 为可选参数,如果使用此参数则要求obj必须是一个可调用对象
Iter_list = iter([1, 2, 3]) # 将列表转换为迭代器
print(Iter_list.__next__()) # 依次获取迭代器的下一个元素
print(Iter_list.__next__())
print(Iter_list.__next__())
print(Iter_list.__next__())
1
2
3
Traceback (most recent call last):
File "C:/Users/Administrator/Desktop/2021-1-14/迭代器.py", line 24, in <module>
print(Iter_list.__next__())
StopIteration
当迭代完存储的所有元素之后,如果继续迭代,则 __next__() 方法会出现StopIteration 异常
也可用 next() 内置函数来迭代(next(myIter)),和 __next__() 方法一样
如果使用iter()函数的第2个参数,则要求第一个 obj 参数必须传入可调用对象(可以不支持迭代)
当使用返回的迭代器调用 __next__() 方法时,会通过执行 obj() 调用 __call__() 方法
如果该方法的返回值和第 2 个参数值相同,则输出 StopInteration 异常;反之,则输出 __call__() 方法的返回值
class iter_list:
def __init__(self):
self.__date=[]
self.__step = 0
def __setitem__(self,key,value): #添加元素
self.__date.insert(key,value)
self.__step += 1
def __call__(self): #使该类实例对象成为可调用对象
self.__step-=1
return self.__date[self.__step]
test1 = iter_list()
test1[0]=10
test1[1]=16
test2 = iter(test1,10) #将test1变为迭代器
print(test2.__next__())
print(test2.__next__())
16
Traceback (most recent call last):
File "C:/Users/Administrator/Desktop/2021-1-14/迭代器.py", line 40, in <module>
print(test2.__next__())
StopIteration
输出结果中,最终出现StopIteration 异常,因为原本要输出的元素10和 iter() 函数的第 2 个参数相同
迭代器是一个底层的特性和概念,在程序中并不常用,但为生成器这一特性提供了基础
在使用list 容器迭代一组数据时,必须先将所有数据存储到容器中,才能开始迭代;而生成器可以实现在迭代的同时生成元素
对于可以用某种算法推算得到的多个数据,生成器并不会一次性生成它们,而是在需要时才生成
生成器的创建方式分为以下步骤
定义一个以 yield 关键字标识返回值的函数
调用刚刚创建的函数,即创建了一个生成器
def test():
print("开始执行")
for i in range(5):
yield i
print("继续执行")
test1 = test()
test()函数的返回值用的是 yield 关键字,而不是return 关键字,此类函数又成为生成器函数
yield 可以返回相应的值,还有一个功能就是每当程序执行完该语句时,程序就会暂停执行
即便调用生成器函数,解释器也不会执行函数中的代码,它只会返回一个生成器(对象)
要使生成器函数得以执行或使执行完 yield 语句立即暂停的程序得以继续执行,有以下方式:
通过生成器调用 next() 内置函数或者 __next__() 方法
通过 for 循环遍历生成器
def test():
print("开始执行")
for i in range(3):
yield i
print("继续执行")
test1 = test()
print(next(test1)) #调用 next() 内置函数
print(test1.__next__()) #调用 __next__() 方法
for i in test1: #通过for循环遍历生成器
print(i)
开始执行
0
继续执行
1
继续执行
2
继续执行
程序的执行流程:
1.在创建有 num 生成器的前提下,通过其调用 next() 内置函数,使解释器开始执行test()生成器函数中的代码
2.会输出“开始执行”,程序会一直执行到yield i,此时的 i==0,解释器输出“0”,因为yield的存在,程序在此处暂停
3.使用 test1 生成器调用 __next__() 方法(next() 函数的底层执行的是 __next__() 方法),使程序继续执行,输出“继续执行”,程序又会执行到yield i,此时 i==1,输出“1”,然后程序暂停
4.使用 for 循环遍历 test1 生成器, for 循环底层会不断地调用 next() 函数,使暂停的程序继续执行,输出后续结果
使用 list() 函数和 tuple() 函数,直接将生成器生成的所有值存储成列表或者元组的形式
def test():
print("开始执行")
for i in range(3):
yield i
print("继续执行")
test1 = test()
print(list(test1))
test1 = test()
print(tuple(test1))
开始执行
继续执行
继续执行
继续执行
[0, 1, 2]
开始执行
继续执行
继续执行
继续执行
(0, 1, 2)
list() 和 tuple() 底层实现和 for 循环的遍历过程是类似的
相比迭代器,生成器最明显的优势就是节省内存空间,不会一次性生成所有的数据,而是在需要时才生成