Python-入门-类的特殊成员和扩展(十一)
文章目录
- Python-入门-类的特殊成员和扩展
- \_\_new\_\_()方法
- \_\_repr\_\_()方法(打印属性)
- \_\_del\_\_()方法(销毁对象)
- \_\_dir\_\_()用法(列出对象的所有属性(方法)名)
- \_\_dict\_\_属性(查看对象内部所有属性名和属性值组成的字典)
- setattr()、getattr()、hasattr()
- issubclass和isinstance函数(检查类型)
- \_\_call\_\_()方法
- 可重载运算符
- **自定义序列**
- 迭代器
- 生成器
- 装饰器
Python-入门-类的特殊成员和扩展
Python 类中,凡是以双下划线 “__” 开头和结尾命名的成员(属性和方法),都被称为类的特殊成员(特殊属性和特殊方法)。例如,类的 init(self) 构造方法就是典型的特殊方法。
Python 类中的特殊成员,其特殊性类似 C++ 类的 private 私有成员,即不能在类的外部直接调用,但允许借助类中的普通方法调用甚至修改它们。如果需要,还可以对类的特殊方法进行重写,从而实现一些特殊的功能。
当然,除了 __init__(self) 之外,Python 类中还含有很多特殊成员,包括 __del__(self)、__new__(self) 等,下面会一一为你进行详细的讲解。
__new__()方法
我们首先得从__new__(cls[,…])的参数说说起,__new__方法的第一个参数是这个类,而其余的参数会在调用成功后全部传递给__init__方法初始化,这一下子就看出了谁是老子谁是小子的关系。所以,__new__方法(第一个执行)先于__init__方法执行:
我们比较两个方法的参数,可以发现__new__方法是传入类(cls),而__init__方法传入类的实例化对象(self),而有意思的是,__new__方法返回的值就是一个实例化对象(ps:如果__new__方法返回None,则__init__方法不会被执行,并且返回值只能调用父类中的__new__方法,而不能调用毫无关系的类的__new__方法)。我们可以这么理解它们之间的关系,__new__是开辟疆域的大将军,而__init__是在这片疆域上辛勤劳作的小老百姓,只有__new__执行完后,开辟好疆域后,__init__才能工作,结合到代码,也就是__new__的返回值正是__init__中self。
一般来说,不建议重写__new__,除非是在构建str,int,unincode或者tuple不可变类型的子类的时候,这是因为一旦创建了这样不可变的对象实例,就无法在 __init__() 方法中对其进行修改。
class nonZero(int):
def __new__(cls,value):
return super().__new__(cls,value) if value != 0 else None
def __init__(self,skipped_value):
#此例中会跳过此方法
print("__init__()")
super().__init__()
print(type(nonZero(-12)))
print(type(nonZero(0)))
注意,由于 __new__() 不限于返回同一个类的实例,所以很容易被滥用,不负责任地使用这种方法可能会对代码有害,所以要谨慎使用。一般来说,对于特定问题,最好搜索其他可用的解决方案,最好不要影响对象的创建过程
__repr__()方法(打印属性)
我们经常会直接输出类的实例化对象,例如:
class CLanguage:
pass
clangs = CLanguage()
print(clangs)# <__main__.CLanguage object at 0x000001A7275221D0>
通常情况下,直接输出某个实例化对象,本意往往是想了解该对象的基本信息,例如该对象有哪些属性,它们的值各是多少等等。但默认情况下,我们得到的信息只会是“类名+object at+内存地址”,对我们了解该实例化对象帮助不大。
那么,有没有可能自定义输出实例化对象时的信息呢?答案是肯定,通过重写类的 __repr__() 方法即可。事实上,当我们输出某个实例化对象时,其调用的就是该对象的 __repr__() 方法,输出的是该方法的返回值。
Python 中的每个类都包含 __repr__() 方法,因为 object 类包含 __reper__() 方法,而 Python 中所有的类都直接或间接继承自 object 类。
默认情况下,__repr__() 会返回和调用者有关的 “类名+object at+内存地址”信息。当然,我们还可以通过在类中重写这个方法,从而实现当输出实例化对象时,输出我们想要的信息。举个例子:
class CLanguage:
def __init__(self):
self.name = "C语言"
self.add = "python"
def __repr__(self):
return "CLanguage[name="+ self.name +",add=" + self.add +"]"
clangs = CLanguage()
print(clangs) # CLanguage[name=C语言,add=python]
由此可见,__repr__() 方法是类的实例化对象用来做“自我介绍”的方法,默认情况下,它会返回当前对象的“类名+object at+内存地址”,而如果对该方法进行重写,可以为其制作自定义的自我描述信息。
__del__()方法(销毁对象)
我们知道,Python 通过调用 __init__() 方法构造当前类的实例化对象,而本节要学的 __del__() 方法,功能正好和 __init__() 相反,其用来销毁实例化对象。
事实上在编写程序时,如果之前创建的类实例化对象后续不再使用,最好在适当位置手动将其销毁,释放其占用的内存空间(整个过程称为垃圾回收(简称GC))。
大多数情况下,Python 开发者不需要手动进行垃圾回收,因为 Python 有自动的垃圾回收机制(下面会讲),能自动将不需要使用的实例对象进行销毁。
无论是手动销毁,还是 Python 自动帮我们销毁,都会调用 __del__() 方法。举个例子:
class CLanguage:
def __init__(self):
print("调用 __init__() 方法构造对象")
def __del__(self):
print("调用__del__() 销毁对象,释放其空间")
clangs = CLanguage()
del clangs
但是,读者千万不要误认为,只要为该实例对象调用 __del__() 方法,该对象所占用的内存空间就会被释放。
Python 采用自动引用计数(简称 ARC)的方式实现垃圾回收机制。该方法的核心思想是:每个 Python 对象都会配置一个计数器,初始 Python 实例对象的计数器值都为 0,如果有变量引用该实例对象,其计数器的值会加 1,依次类推;反之,每当一个变量取消对该实例对象的引用,计数器会减 1。如果一个 Python 对象的的计数器值为 0,则表明没有变量引用该 Python 对象,即证明程序不再需要它,此时 Python 就会自动调用 __del__() 方法将其回收。
以上面程序中的 clangs 为例,实际上构建 clangs 实例对象的过程分为 2 步,先使用 CLanguage() 调用该类中的 __init__() 方法构造出一个该类的对象(将其称为 C,计数器为 0),并立即用 clangs 这个变量作为所建实例对象的引用( C 的计数器值 + 1)。在此基础上,又有一个 clang 变量引用 clangs(其实相当于引用 CLanguage(),此时 C 的计数器再 +1 ),这时如果调用del clangs
语句,只会导致 C 的计数器减 1(值变为 1),因为 C 的计数器值不为 0,因此 C 不会被销毁(不会执行 __del__() 方法)。
如果在上面程序结尾,添加如下语句:
del cl
print("-----------")
可以看到,当执行 del cl 语句时,其应用的对象实例对象 C 的计数器继续 -1(变为 0),对于计数器为 0 的实例对象,Python 会自动将其视为垃圾进行回收。
需要额外说明的是,如果我们重写子类的 __del__() 方法(父类为非 object 的类),则必须显式调用父类的 __del__() 方法,这样才能保证在回收子类对象时,其占用的资源(可能包含继承自父类的部分资源)能被彻底释放。
__dir__()用法(列出对象的所有属性(方法)名)
前面在介绍 Python 内置函数时,提到了 dir() 函数,通过此函数可以某个对象拥有的所有的属性名和方法名,该函数会返回一个包含有所有属性名和方法名的有序列表
举个例子:
class CLanguage:
def __init__ (self,):
self.name = "C语言"
self.add = "http://www.baidu.com/python"
def say():
pass
clangs = CLanguage()
print(dir(clangs))
程序运行结果为:
[‘__class__’, ‘__delattr__’, ‘__dict__’, ‘__dir__’, ‘__doc__’, ‘__eq__’, ‘__format__’, ‘__ge__’, ‘__getattribute__’, ‘__gt__’, ‘__hash__’, ‘__init__’, ‘__init_subclass__’, ‘__le__’, ‘__lt__’, ‘__module__’, ‘__ne__’, ‘__new__’, ‘__reduce__’, ‘__reduce_ex__’, ‘__repr__’, ‘__setattr__’, ‘__sizeof__’, ‘__str__’, ‘__subclasshook__’, ‘__weakref__’, ‘add’, ‘name’, ‘say’]
注意,通过 dir() 函数,不仅仅输出本类中新添加的属性名和方法(最后 3 个),还会输出从父类(这里为 object 类)继承得到的属性名和方法名。
值得一提的是,dir() 函数的内部实现,其实是在调用参数对象 __dir__() 方法的基础上,对该方法返回的属性名和方法名做了排序。
所以,除了使用 dir() 函数,我们完全可以自行调用该对象具有的 __dir__() 方法:
clangs = CLanguage()
print(clangs.__dir__())
[‘name’, ‘add’, ‘__module__’, ‘__init__’, ‘say’, ‘__dict__’, ‘__weakref__’, ‘__doc__’, ‘__repr__’, ‘__hash__’, ‘__str__’, ‘__getattribute__’, ‘__setattr__’, ‘__delattr__’, ‘__lt__’, ‘__le__’, ‘__eq__’, ‘__ne__’, ‘__gt__’, ‘__ge__’, ‘__new__’, ‘__reduce_ex__’, ‘__reduce__’, ‘__subclasshook__’, ‘__init_subclass__’, ‘__format__’, ‘__sizeof__’, ‘__dir__’, ‘__class__’]
显然,使用 __dir__() 方法和 dir() 函数输出的数据是相同,仅仅顺序不同。
__dict__属性(查看对象内部所有属性名和属性值组成的字典)
在 Python 类的内部,无论是类属性还是实例属性,都是以字典的形式进行存储的,其中属性名作为键,而值作为该键对应的值。
为了方便用户查看类中包含哪些属性,Python 类提供了 __dict__ 属性。需要注意的一点是,该属性可以用类名或者类的实例对象来调用,用类名直接调用 __dict__,会输出该由类中所有类属性组成的字典;而使用类的实例对象调用 __dict__,会输出由类中所有实例属性组成的字典。举个例子:
class CLanguage:
a = 1
b = 2
def __init__ (self):
self.name = "C语言"
self.add = "http://www.baidu.com/python"
#通过类名调用__dict__
print(CLanguage.__dict__)
#通过类实例对象调用 __dict__
clangs = CLanguage()
print(clangs.__dict__)
{‘__module__’: ‘__main__’, ‘a’: 1, ‘b’: 2, ‘__init__’: <function CLanguage.__init__ at 0x0000022C69833E18>, ‘__dict__’: <attribute ‘__dict__’ of ‘CLanguage’ objects>, ‘__weakref__’: <attribute ‘__weakref__’ of ‘CLanguage’ objects>, ‘__doc__’: None}
{‘name’: ‘C语言中文网’, ‘add’: ‘http://c.biancheng.net’}
不仅如此,对于具有继承关系的父类和子类来说,父类有自己的 __dict__,同样子类也有自己的 __dict__,它不会包含父类的 __dict__。
借助由类实例对象调用 __dict__ 属性获取的字典,可以使用字典的方式对其中实例属性的值进行修改,例如:
class CLanguage:
a = "aaa"
b = 2
def __init__ (self):
self.name = "C语言"
self.add = "http://www.baidu.com/python"
#通过类实例对象调用 __dict__
clangs = CLanguage()
print(clangs.__dict__)
clangs.__dict__['name'] = "Python教程"
print(clangs.name)
注意: 无法通过类似的方式修改类变量的值。 只是修改实例变量
setattr()、getattr()、hasattr()
hasattr()函数
hasattr() 函数用来判断某个类实例对象是否包含指定名称的属性或方法。该函数的语法格式如下:hasattr(obj, name)
`
其中 obj 指的是某个类的实例对象,name 表示指定的属性名或方法名。同时,该函数会将判断的结果(True 或者 False)作为返回值反馈回来。
举个例子:
class CLanguage:
def __init__ (self):
self.name = "C语言"
self.add = "http://www.baidu.com/python"
def say(self):
print("我正在学Python")
clangs = CLanguage()
print(hasattr(clangs,"name"))
print(hasattr(clangs,"add"))
print(hasattr(clangs,"say"))
显然,无论是属性名还是方法名,都在 hasattr() 函数的匹配范围内。因此,我们只能通过该函数判断实例对象是否包含该名称的属性或方法,但不能精确判断,该名称代表的是属性还是方法。
getattr() 函数
getattr() 函数获取某个类实例对象中指定属性的值。没错,和 hasattr() 函数不同,该函数只会从类对象包含的所有属性中进行查找。
getattr() 函数的语法格式如下:getattr(obj, name[, default])
`
其中,obj 表示指定的类实例对象,name 表示指定的属性名,而 default 是可选参数,用于设定该函数的默认返回值,即当函数查找失败时,如果不指定 default 参数,则程序将直接报 AttributeError 错误,反之该函数将返回 default 指定的值。举个例子:
class CLanguage:
def __init__ (self):
self.name = "C语言"
self.add = "http://www.baidu.com/python"
def say(self):
print("我正在学Python")
clangs = CLanguage()
print(getattr(clangs,"name"))
print(getattr(clangs,"add"))
print(getattr(clangs,"say"))
print(getattr(clangs,"display",'nodisplay'))
对于类中已有的属性,getattr() 会返回它们的值,而如果该名称为方法名,则返回该方法的状态信息;反之,如果该明白不为类对象所有,要么返回默认的参数,要么程序报 AttributeError 错误。
setattr()函数
setattr() 函数的功能相对比较复杂,它最基础的功能是修改类实例对象中的属性值。其次,它还可以实现为实例对象动态添加属性或者方法。
setattr() 函数的语法格式如下:setattr(obj, name, value)
`
首先,下面例子演示如何通过该函数修改某个类实例对象的属性值:
class CLanguage:
def __init__ (self):
self.name = "C语言"
self.add = "http://c.biancheng.net"
def say(self):
print("我正在学Python")
clangs = CLanguage()
print(clangs.name)
print(clangs.add)
setattr(clangs,"name","Python教程")
setattr(clangs,"add","http://www.baidu.com/python")
print(clangs.name)
print(clangs.add)
甚至利用 setattr() 函数,还可以将类属性修改为一个类方法,同样也可以将类方法修改成一个类属性。例如:
def say(self):
print("我正在学Python")
class CLanguage:
def __init__ (self):
self.name = "C语言"
self.add = "http://www.baidu.com/python"
clangs = CLanguage()
print(clangs.name)
print(clangs.add)
setattr(clangs,"name",say)
clangs.name(clangs)
显然,通过修改 name 属性的值为 say(这是一个外部定义的函数),原来的 name 属性就变成了一个 name() 方法。
使用 setattr() 函数对实例对象中执行名称的属性或方法进行修改时,如果该名称查找失败,Python 解释器不会报错,而是会给该实例对象动态添加一个指定名称的属性或方法。
issubclass和isinstance函数(检查类型)
Python 提供了如下两个函数来检查类型:
- issubclass(cls, class_or_tuple):检查 cls 是否为后一个类或元组包含的多个类中任意类的子类。
- isinstance(obj, class_or_tuple):检查 obj 是否为后一个类或元组包含的多个类中任意类的对象。
通过使用上面两个函数,程序可以方便地先执行检查,然后才调用方法,这样可以保证程序不会出现意外情况。
如下程序示范了通过这两个函数来检查类型:
# 定义一个字符串
hello = "Hello";
# "Hello"是str类的实例,输出True
print('"Hello"是否是str类的实例: ', isinstance(hello, str))
# "Hello"是object类的子类的实例,输出True
print('"Hello"是否是object类的实例: ', isinstance(hello, object))
# str是object类的子类,输出True
print('str是否是object类的子类: ', issubclass(str, object))
# "Hello"不是tuple类及其子类的实例,输出False
print('"Hello"是否是tuple类的实例: ', isinstance(hello, tuple))
# str不是tuple类的子类,输出False
print('str是否是tuple类的子类: ', issubclass(str, tuple))
# 定义一个列表
my_list = [2, 4]
# [2, 4]是list类的实例,输出True
print('[2, 4]是否是list类的实例: ', isinstance(my_list, list))
# [2, 4]是object类的子类的实例,输出True
print('[2, 4]是否是object类及其子类的实例: ', isinstance(my_list, object))
# list是object类的子类,输出True
print('list是否是object类的子类: ', issubclass(list, object))
# [2, 4]不是tuple类及其子类的实例,输出False
print('[2, 4]是否是tuple类及其子类的实例: ', isinstance([2, 4], tuple))
# list不是tuple类的子类,输出False
print('list是否是tuple类的子类: ', issubclass(list, tuple))
通过上面程序可以看出,issubclass() 和 isinstance() 两个函数的用法差不多,区别只是 issubclass() 的第一个参数是类名,而 isinstance() 的第一个参数是变量,这也与两个函数的意义对应:issubclass 用于判断是否为子类,而 isinstance() 用于判断是否为该类或子类的实例。
issubclass() 和 isinstance() 两个函数的第二个参数都可使用元组。例如如下代码:
data = (20, 'fkit')
print('data是否为列表或元组: ', isinstance(data, (list, tuple))) # True
# str不是list或者tuple的子类,输出False
print('str是否为list或tuple的子类: ', issubclass(str, (list, tuple)))
# str是list或tuple或object的子类,输出True
print('str是否为list或tuple或object的子类 ', issubclass(str, (list, tuple, object)))
__call__()方法
Python 类中一个非常特殊的实例方法,即 __call__()。使得类实例对象可以像调用普通函数那样,以“对象名()”的形式使用。举个例子:
class CLanguage:
# 定义__call__方法
def __call__(self,name,add):
print("调用__call__()方法",name,add)
clangs = CLanguage()
clangs("C语言","http://c.biancheng.net")
可以看到,通过在 CLanguage 类中实现 __call__() 方法,使的 clangs 实例对象变为了可调用对象。
Python 中,凡是可以将 () 直接应用到自身并执行,都称为可调用对象。可调用对象包括自定义的函数、Python 内置函数以及本节所讲的类实例对象。
对于可调用对象,实际上“名称()”可以理解为是“名称.__call__()”的简写。仍以上面程序中定义的 clangs 实例对象为例,其最后一行代码还可以改写为如下形式:
clangs.__call__("C语言","http://c.biancheng.net")
用 __call__() 弥补 hasattr() 函数的短板
前面绍了 hasattr() 函数的用法,该函数的功能是查找类的实例对象中是否包含指定名称的属性或者方法,但该函数有一个缺陷,即它无法判断该指定的名称,到底是类属性还是类方法。要解决这个问题,我们可以借助可调用对象的概念。要知道,类实例对象包含的方法,其实也属于可调用对象,但类属性却不是。举个例子:
class CLanguage:
def __init__ (self):
self.name = "C语言"
self.add = "http://c.biancheng.net"
def say(self):
print("我正在学Python")
clangs = CLanguage()
if hasattr(clangs,"name"):
print(hasattr(clangs.name,"__call__"))
if hasattr(clangs,"say"):
print(hasattr(clangs.say,"__call__"))
可以看到,由于 name 是类属性,它没有以 __call__ 为名的 __call__() 方法;而 say 是类方法,它是可调用对象,因此它有 __call__() 方法。
可重载运算符
Python 中的各个序列类型,每个类型都有其独特的操作方法,例如列表类型支持直接做加法操作实现添加元素的功能,字符串类型支持直接做加法实现字符串的拼接功能,也就是说,同样的运算符对于不同序列类型的意义是不一样的,这是怎么做到的呢?
其实在 Python 内部,每种序列类型都是 Python 的一个类,例如列表是 list 类,字典是 dict 类等,这些序列类的内部使用了一个叫作“重载运算符”的技术来实现不同运算符所对应的操作。
所谓重载运算符,指的是在类中定义并实现一个与运算符对应的处理方法,这样当类对象在进行运算符操作时,系统就会调用类中相应的方法来处理。
这里给大家举一个与重载运算符相关的实例:
class MyClass: #自定义一个类
def __init__(self, name , age): #定义该类的初始化函数
self.name = name #将传入的参数值赋值给成员交量
self.age = age
def __str__(self): #用于将值转化为字符串形式,等同于 str(obj)
return "name:"+self.name+";age:"+str(self.age)
__repr__ = __str__ #转化为供解释器读取的形式
def __lt__(self, record): #重载 self<record 运算符
if self.age < record.age:
return True
else:
return False
def __add__(self, record): #重载 + 号运算符
return MyClass(self.name, self.age+record.age)
myc = MyClass("Anna", 42) #实例化一个对象 Anna,并为其初始化
mycl = MyClass("Gary", 23) #实例化一个对象 Gary,并为其初始化
print(repr(myc)) #格式化对象 myc,
print(myc) #解释器读取对象 myc,调用 repr
print (str (myc)) #格式化对象 myc ,输出"name:Anna;age:42"
print(myc < mycl) #比较 myc<mycl 的结果,输出 False
print (myc+mycl) #进行两个 MyClass 对象的相加运算,输出 "name:Anna;age:65"
这个例子中,MyClass 类中重载了 repr、str、<、+
运算符,并用 MyClass 实例化了两个对象 myc 和 mycl。
通过将 myc 进行 repr、str 运算,从输出结果中可以看到,程序调用了重载的操作符方法 __repr__ 和 __str__。而令 myc 和 mycl 进行 < 号的比较运算以及加法运算,从输出结果中可以看出,程序调用了重载 < 号的方法 __lt__ 和 __add__ 方法。
那么,Python 类支持对哪些方法进行重载呢?这个给大家提供一个表格,列出了 Python 中常用的可重载的运算符,以及各自的含义。
自定义序列
序列是字符串,元组,列表的统称。序列有以下特点:
- 都可以通过索引得到每一个元素
- 默认索引值总是从零开始
- 可以通过切片的方法得到一个范围内的元素的集合
- 有很多共同的操作符(重复操作符、拼接操作符、成员关系操作符)
如果需要自定义序列那么需要实现下面几个方法:
__len__(self) 返回序列类中存储元素的个数。
__contains__(self, value) 判断当前序列中是否包含 value 这个指定元素。
__getitem__(self, key) 通过指定的 key(键),返回对应的 value(值)。
__setitem__(self, key) 修改指定 key(键)对应的 value(值)。
__delitem__(self, key) 删除指定键值对。
假如我们要自定义一个序列,可以通过重写这几个类特殊方法。不过并不是全部方法都要重写,如果该自定义序列是一个不可变序列(即序列中的元素不能做修改),则无需重写 __setitem__() 和 __delitem__() 方法;反之,如果该自定义序列是一个可变序列,可以重写以上 5 个特殊方法。
一旦实现了序列那么就能使用Python里面针对序列的很多内置函数:list(), tuple(), str(), len(), max(), min(), sum(), sorted(), reversed(),enumerate(), zip()
`等等。
列实现了只能存储 int 类型的元素的序列:
class IntDic:
def __init__(self):
# 用于存储数据的字典
self.__date = {}
def __len__(self):
return len(list(self.__date.values()))
def __getitem__(self, key):
# 如果在self.__changed中找到已经修改后的数据
if key in self.__date :
return self.__date[key]
return None
def __setitem__(self, key, value):
#判断value是否为整数
if not isinstance(value, int):
raise TypeError('必须是整数')
#修改现有 key 对应的 value 值,或者直接添加
self.__date[key] = value
def __delitem__(self, key):
if key in self.__date : del self.__date[key]
dic = IntDic()
#输出序列中元素的个数,调用 __len__() 方法
print(len(dic))
#向序列中添加元素,调用 __setitem__() 方法
dic['a'] = 1
dic['b'] = 2
print(len(dic))
dic['a'] = 3
dic['c'] = 4
print(dic['a'])
#删除指定元素,调用 __delitem__() 方法
del dic['a']
print(dic['a'])
print(len(dic))
迭代器
迭代器指的就是可以迭代的容器类对象,这里的容器可以是列表、元组等这些 Python 提供的基础容器,也可以是自定义的容器类对象,只要该容器支持迭代即可。
上面已经可以自定义一个序列类,但该序列类对象并不支持迭代,因此还不能称之为迭代器。如果要自定义实现一个迭代器,则类中必须实现如下 2 个方法:
__next__(self):返回容器的下一个元素。
__iter__(self):该方法返回一个迭代器(iterator)
下面程序自定义了一个简易的列表容器迭代器,支持迭代:
class listDemo:
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):
#实例对象本身就是迭代器对象,因此直接返回 self 即可
return self
#添加元素
def __setitem__(self,key,value):
self.__date.insert(key,value)
self.__step += 1
mylist = listDemo()
mylist[0]=1
mylist[1]=2
for i in mylist:
print (i)
Python 内置的 iter() 函数也会返回一个迭代器,该函数的语法格式如下:iter(obj[, sentinel])
`
obj 必须是一个可迭代的容器对象,而 sentinel 作为可选参数,如果使用此参数,要求 obj 必须是一个可调用对象。
示例:通过传入一个可迭代的容器对象,我们可以获得一个迭代器,通过调用该迭代器中的 __next__() 方法即可实现迭代。
# 将列表转换为迭代器
myIter = iter([1, 2, 3])
# 依次获取迭代器的下一个元素
print(myIter.__next__())
print(myIter.__next__())
print(myIter.__next__())
print(myIter.__next__())
也可以使用 next() 内置函数来迭代,即 next(myIter),和 __next__() 方法是完全一样的。
如果使用__next__那么当迭代完存储的所有元素之后,如果继续迭代,则 __next__() 方法会抛出 StopIteration 异常。
案例: 迭代器实现字符串的逆序输出
class Reverse:
def __init__(self, string):
self.__string = string
self.__index = len(string)
def __iter__(self):
return self
def __next__(self):
if self.__index == 0:
raise(StopIteration)
self.__index -= 1
return self.__string[self.__index]
revstr = Reverse('Python')
for c in revstr:
print(c,end=" ")
生成器
列表所有数据都在内存中,如果有海量数据的话将会非常耗内存。
如:仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。
如果列表元素按照某种算法推算出来,那我们就可以在循环的过程中不断推算出后续的元素,这样就不必创建完整的list,从而节省大量的空间。
简单一句话:我又想要得到庞大的数据,又想让它占用空间少,那就用生成器!。
不仅如此,生成器的创建方式也比迭代器简单很多,大体分为以下 2 步:
- 定义一个以 yield 关键字标识返回值的函数;
- 调用刚刚创建的函数,即可创建一个生成器。
举个例子:
def intNum():
print("开始执行")
for i in range(5):
yield i
print("继续执行")
num = intNum()
由此,我们就成功创建了一个 num 生成器对象。显然,和普通函数不同,intNum() 函数的返回值用的是 yield 关键字,而不是 return 关键字,此类函数又成为生成器函数。
和 return 相比,yield 除了可以返回相应的值,还有一个更重要的功能,即每当程序执行完该语句时,程序就会暂停执行。不仅如此,即便调用生成器函数,Python 解释器也不会执行函数中的代码,它只会返回一个生成器(对象)。
要想使生成器函数得以执行,或者想使执行完 yield 语句立即暂停的程序得以继续执行,有以下 2 种方式:
- 通过生成器(上面程序中的 num)调用 next() 内置函数或者 __next__() 方法;
- 通过 for 循环遍历生成器。
例如,在上面程序的基础上,添加如下语句:
#调用 next() 内置函数
print(next(num))
#调用 __next__() 方法
print(num.__next__())
#通过for循环遍历生成器
for i in num:
print(i)
程序执行结果为:
开始执行
0
继续执行
1
继续执行
2
继续执行
3
继续执行
4
继续执行
这里有必要给读者分析一个程序的执行流程:
- 首先,在创建有 num 生成器的前提下,通过其调用 next() 内置函数,会使 Python 解释器开始执行 intNum() 生成器函数中的代码,因此会输出“开始执行”,程序会一直执行到
yield i
,而此时的 i==0,因此 Python 解释器输出“0”。由于受到 yield 的影响,程序会在此处暂停。 - 然后,我们使用 num 生成器调用 __next__() 方法,该方法的作用和 next() 函数完全相同(事实上,next() 函数的底层执行的也是 __next__() 方法),它会是程序继续执行,即输出“继续执行”,程序又会执行到
yield i
,此时 i==1,因此输出“1”,然后程序暂停。 - 最后,我们使用 for 循环遍历 num 生成器,之所以能这么做,是因为 for 循环底层会不断地调用 next() 函数,使暂停的程序继续执行,因此会输出后续的结果。
注意,在 Python 2.x 版本中不能使用 __next__() 方法,可以使用 next() 内置函数,另外生成器还有 next() 方法(即以 num.next() 的方式调用)。
除此之外,还可以使用 list() 函数和 tuple() 函数,直接将生成器能生成的所有值存储成列表或者元组的形式。例如:
num = intNum()
print(list(num)) # [0, 1, 2, 3, 4]
num = intNum()
print(tuple(num)) # (0, 1, 2, 3, 4)
通过输出结果可以判断出,list() 和 tuple() 底层实现和 for 循环的遍历过程是类似的。相比迭代器,生成器最明显的优势就是节省内存空间,即它不会一次性生成所有的数据,而是什么时候需要,什么时候生成。
装饰器
装饰器是什么呢,学过java的spring架构应该都知道aop这个概念就是对方法进行增强,或者知道java动态代理的也行都差不多, 什么是方法的增强呢?
假设你执行一个A方法那么,在执行A方法执行前会先执行B方法并且将A方法嵌套进B方法里,这个过程对于我们而言是看不见的,但是对于程序而言是在执行B方法然后在执行A方法,而A方法结果返回给B方法,并且将B方法作为最后结果返回,而在我们的角度就是A方法执行完毕了,模拟代码如下:
def deco(func):
print("before myfunc() called.")
func() # 执行myfunc()
print("after myfunc() called.")
return func
def myfunc():
print(" myfunc() called.")
if __name__ == '__main__':
myfunc = deco(myfunc)
以上和python的装饰器有啥关系,没错上面代码就是python帮我们处理后的结果,而我们眼中的装饰器如下:
# funA 作为装饰器函数
def funA(fn):
print("C语言")
fn() # 执行传入的fn参数 相当于执行funB()
print("python")
return "装饰器函数的返回值"
@funA
def funB():
print("学习 Python")
if __name__ == '__main__':
funB
显然,被“@函数”修饰的函数不再是原来的函数,而是被替换成一个新的东西(取决于装饰器的返回值),即如果装饰器函数的返回值为普通变量,那么被修饰的函数名就变成了变量名;同样,如果装饰器返回的是一个函数的名称,那么被修饰的函数名依然表示一个函数。
实际上,所谓函数装饰器,就是通过装饰器函数,在不修改原函数的前提下,来对函数的功能进行合理的扩充。
带参数的函数装饰器
在分析 funA() 函数装饰器和 funB() 函数的关系时,细心的读者可能会发现一个问题,即当 funB() 函数无参数时,可以直接将 funB 作为 funA() 的参数传入。但是,如果被修饰的函数本身带有参数,那应该如何传值呢?比较简单的解决方法就是在函数装饰器中嵌套一个函数,该函数带有的参数个数和被装饰器修饰的函数相同。例如:
def funA(fn):
# 定义一个嵌套函数
def say(arc):
fn(arc) # funB(arc)
print("Python教程:", arc)
return say
@funA
def funB(arc):
print("funB():", arc)
if __name__ == '__main__':
funB("python")
显然 funB() 函数被装饰器 funA() 修饰,funB 就被赋值为 say。这意味着,虽然我们在程序显式调用的是 funB() 函数,但其实执行的是装饰器嵌套的 say() 函数
但还有一个问题需要解决,即如果当前程序中,有多个(≥ 2)函数被同一个装饰器函数修饰,这些函数带有的参数个数并不相等,怎么办呢?
最简单的解决方式是用 *args 和 **kwargs 作为装饰器内部嵌套函数的参数,*args 和 **kwargs 表示接受任意数量和类型的参数。举个例子:
def funA(fn):
# 定义一个嵌套函数
def say(*args, **kwargs):
fun=fn(*args, **kwargs)
return say
@funA
def funB(arc):
print("C语言:", arc)
@funA
def other_funB(name, arc):
print(name, arc)
if __name__ == '__main__':
funB("http://c.baidu.net")
other_funB("Python教程:", "http://c.baidu.net/python")
带返回值的装饰器
def test(f):
def test1(*args, **kwargs): # 这里的test1函数要和被装饰函数func2的结构保持一致
print('==========')
res = f(*args, **kwargs) # 这里相当于把被装饰函数的结果拿过来赋值,f(*args, **kwargs)的执行结果就是func2的返回值
return res # 没有返回值也可以这样写,返回结果就是None
return test1
@test
def func2(a, b, c):
# print(args, kwargs)
print('*********')
return a + b + c
if __name__ == '__main__':
print(func2(10, 5, c=88))
让装饰器带参数
众多函数调用了你写的装饰器,但客户有需求说,我想实现我可以随之控制装饰器是否生效。,那么解决办法就是在装饰器上加一个开关
def func(data):
def wrapper_func(data_func):
def wrapper_inner(*args):
if data:
print('before')
ret = data_func(*args)
print('after')
else:
ret =data_func(*args)
return ret
return wrapper_inner
return wrapper_func
@func(False)
def func_name(param):
print("func_name")
print(param)
@func(True)
def func_name1(param):
print("func_name")
print(param)
if __name__ == '__main__':
print(func_name("hello"))
print("===================")
print(func_name1("hello1"))
注意: 装饰器本质也是函数,那么函数的参数是和普通函数的参数一样可以任意类型的
装饰器可以嵌套
上面示例中,都是使用一个装饰器的情况,但实际上,Python 也支持多个装饰器,比如:
@funA
@funB
@funC
def fun():
#...
上面程序的执行顺序是里到外,所以它等效于下面这行代码:
fun = funA( funB ( funC (fun) ) )