Python学习笔记(十五):从运算符重载到迭代器和生成器

Python学习笔记(十五):从运算符重载到迭代器和生成器

类特殊成员

  • Python学习笔记(十五):从运算符重载到迭代器和生成器
  • 一.hasattr()、getattr()、setattr()
    • hasattr()
    • getattr()
    • setattr()
  • 二.运算符重载
    • __call __()
  • 三.序列相关操作
  • 四.迭代器
  • 五.生成器

一.hasattr()、getattr()、setattr()

hasattr()

判断某个类实例对象是否包含指定名称的属性或方法

hasattr(obj, name)

obj表示某个类的实例对象,name 表示指定的属性名或方法名,该函数会将判断的结果(TrueFalse)作为返回值

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()

获取某个类实例对象中指定属性的值,只会从类对象包含的所有属性中进行查找

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()

基础的功能是修改类实例对象中的属性值,还可以为实例对象动态添加属性或者方法

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类中重载了reprstr<+ 运算符,并实例化了两个对象c和cl

将c进行 reprstr 运算,程序调用了重载的操作符方法 __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 __()

__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 异常,因为原本要输出的元素10iter() 函数的第 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 循环的遍历过程是类似的

相比迭代器,生成器最明显的优势就是节省内存空间,不会一次性生成所有的数据,而是在需要时才生成

你可能感兴趣的:(Python,Note,python)