Python3学习笔记——特殊方法和迭代器

Python3学习笔记——特殊方法和迭代器

前言

本笔记仅个人认知和见解,水平有限,还请见谅。

如有错误,还请指出,若有想法,欢迎共享!

本笔记的代码在IDLE中运行,除了说明中断外均是连续的。


1.特殊方法

名称开头和结尾都有两个下划线的方法称之为特殊方法(魔法方法),如__init__这些方法会在特殊情况下被python自动调用,调用时机取决于特殊方法的名称,而你几乎不需要直接调用这些方法。同时也要注意避免创建这样的名称。

1.1构造函数 (constructor)

命名:__init__

功能:对象的初始化方法,其可以在对象被创建后自动调用。

>>> class ClassName:
	def __init__(self):
		print('我自己打印,不劳烦您')

		
>>> c=ClassName()
我自己打印,不劳烦您

如上,我仅仅是创建实例,__init__内的内容自动执行了。

1.1.1为__init__添加几个参数
>>> class ClassName:
	def __init__(self,speak):
		print(speak)

		
>>> c=ClassName('言出法随')
言出法随

在创建实例的时候附上这个参数即可。

1.1.2实际意义

在创建实例的时候初始化对象状态。

>>> class ClassName:
	def __init__(self):
		self.a=100
		self.b=99

>>> c=ClassName()
>>> c.a
100
>>> c.b
99

在创建实例的时候自动完成了对属性的创建和赋值(初始化)。

1.1.3重写构造函数

如果我继承了这个类,重写构造函数呢?

>>> class ClassB(ClassName):
	def __init__(self):
		self.c=101

		
>>> cB=ClassB()
>>> cB.c
101
>>> cB.a
Traceback (most recent call last):
  File "<pyshell#25>", line 1, in <module>
    cB.a
AttributeError: 'ClassB' object has no attribute 'a'

重写后,在ClassB类中初始化了属性c,但是调用属性a时报错了——没有属性a。简而言之,新的构造函数取代了以前的构造函数,导致超类初始化方法丢失了,自然没有创建属性a。

解决办法:调用未关联的超类构造函数和使用函数super。

调用未关联的超类构造函数:
>>> class ClassB(ClassName):
	def __init__(self):
		ClassName.__init__(self)
		self.c=101

>>> cB=ClassB()
>>> cB.c
101
>>> cB.a
100
>>> cB.b
99

这时候超类的属性a,b和自己的属性c都成功初始化了。那么形如 ClassName.__init__ 这样通过类调用方法叫做未关联的,即ClassName类没有实例与其相关联。在这种情况下,可以设置第一个参数(self)来关联自己的实例。

相当于先调用了ClassName类中的__init__方法,并把自己的实例作为第一个参数传递过去执行,然后再执行自己的初始化函数。

使用函数super

使用函数super比调用未关联的超类构造函数要好理解且简单得多:

>>> class ClassC(ClassName):
	def __init__(self):
		super().__init__()
		self.c=102

		
>>> cC=ClassC()
>>> cC.a
100
>>> cC.b
99
>>> cC.c
102

同时函数super还有个显著的优点:如果超类还有有超类,也只需要调用一次super函数(前提是所有超类的构造函数都使用了super函数),如果有两个超类从一个类派生而来,super也能自动处理。

super()方法语法:

super(type[, object-or-type])

type – 类

object-or-type – 类,一般是self

上面的super函数可以改写为super(ClassC,self),首先找到ClassC的超类(ClassName),然后把类ClassC的实例转化为ClassName的实例。

但是注意,函数super在多重继承方面有如下问题:

>>> class ClassName1:
	def __init__(self):
		self.a=103

		
>>> class ClassName2:
	def __init__(self):
		self.b=104

		
>>> class ClassD(ClassName1,ClassName2):
	def __init__(self):
		super().__init__()
		self.c=105

		
>>> cD=ClassD()
>>> cD.c
105
>>> cD.a
103
>>> cD.b
Traceback (most recent call last):
  File "<pyshell#84>", line 1, in <module>
    cD.b
AttributeError: 'ClassD' object has no attribute 'b'

这就会导致多重继承带来bug:ClassD类多重继承类ClassName1和类ClassName2,然后根据方法解析顺序(MRO),类ClassName1的构造函数取代类ClassName2继承到类ClassD中,然后super函数只初始化了继承到的ClassName1的构造函数(继承到了ClassName1的方法),于是ClassD少初始化了一个变量。

这警示我们多重继承可能带来很多难以调试的bug,但是你也可以通过调用未关联的超类构造函数解决这个问题,同时深入了解函数super也可以解决这个问题,本篇摆掉。

1.2析构函数(destructor)

命名:__del__

功能:在对象被销毁(当作垃圾回收时)被调用

局限:由于无法知道准确的调用时间,这个函数可能带来一些隐患。

1.3另一些特殊方法(基本的序列与映射基本操作)

协议通常指的时规范行为的规则,协议指定应实现哪些方法以及这些方法应做什么。我这里的基本操作原本是基本行为,又叫协议。这么说根本不理解,故我说操作。

特殊方法有很多,下面这些方法可以实现序列和映射的基本操作,如果对象是不可变的,只需要实现下面2个方法,如果序列可变则以下4个方法均要实现。

  • __len__(self):返回集合包含的项数,对于序列来说是元素个数,对映射而言是键值对的个数。

  • __getitem__(self,key):返回与指定键相关的值。对于序列,键是对于的位置的整数(通常是0~n-1,但是也可能出现负数,即从后往前数,最后位置为-1。n为序列长度)

  • __setitem__(self,key,value):为键赋值,便于用上一个方法读取。使用时对象应可变。

  • __delitem__(self,key):对对象组成部分使用__del__时自动调用,应删除与key相关联的值。使用时对象应可变。

利用这些特色方法可以创建无穷序列,需要注意,无穷序列不能利用负数读取值,否则将引发IndexError异常。对于所有序列,如果键的类型不正确(如对序列用字符串键),将引发TypeError异常。同时,无穷序列没有方法__len__。

以上四个方法好使,利用这些方法可以实现许多其他特殊方法和普通方法,如果我们只是想重置特定的操作,就没有理由去重新实现其他所有方法。想这么偷懒只需要继承内置类型list。这是标准库中模块collections提供的抽象和具体基类的一种。继承list后,内置的方法都可以直接使用,如append,extend等,但是如果你重写了这些方法,将以你重写的方法为准。

1.4property函数(特性)

我很难评价这个函数,因为单词翻译是特性,就把这个方法叫特性,让人难以理解,如果只是冠名倒还好,如果真要用名字表达什么,我觉得这很误导人。

这是个定义属性的函数,通过存储方法定义的属性通常称作特性,放在实例中可能更好理解:

>>> class CName:
	def __init__(self):
		self.len=0
		self.high=0
	def set_num(self,big):
		self.len,self.high=big
	def get_num(self):
		return self.len,self.high
	def del_num(self):
		self.len,self.high=(0,0)

		
>>> cN=CName()
>>> cN.set_num((10,20))
>>> cN.get_num()
(10, 20)

在对象CName中有属性len和high,他们组合在一起是属性big(并没有存在于对象,而是假象出来的“虚拟属性”),通过方法get_num可以得到。

如果我想把big变成实际存在于对象内的属性,那要写更多方法,因为这个big属性是由len和high组成,len或high改变都会改变big值,big值改变也会改变high和len的值。

于是,用property函数就可以解决这个问题:

>>> class CName:
	def __init__(self):
		self.len=0
		self.high=0
	def set_num(self,big):
		self.len,self.high=big
	def get_num(self):
		return self.len,self.high
	def del_num(self):
		self.len,self.high=(0,0)
	big = property(get_num,set_num,del_num)

	
>>> cN2=CName()
>>> cN2.len
0
>>> cN2.high
0
>>> cN2.big
(0,0)
>>> cN2.high=99
>>> cN2.len=10
>>> cN2.big
(10,99)
>>> cN2.big=(88,77)
>>> cN2.len
88
>>> cN2.high
77

说完美实现,只是使用了property函数,没有加入别的新方法。

property函数有四个参数,可以不指定参数,也可以指定一个,前两个,前三个,前四个参数。如果不指定参数,创建的属性将不可读也不可写,指定一个参数,则属性只读。

属性名 = property(fget,fset,fdel,doc),fget是获得属性值的方法,fset是指定获得该属性值的方法,fdel是指定删除该属性的方法,doc是创建一个文档字符串,说明这个属性,用help()查看。

property函数本质上是个类,包含了一些特殊方法,了解即可。

此外还发现@property作为修饰器有别的用途,注意区分但不是本文重点。

1.5静态方法和类方法

这是不用实例化但是可以用的方法。静态方法没有参数self,可以直接通过对象调用,类方法也没有参数self,但包含一个类似的参数,通常命名为cls,这个参数代表类,类似于对一个实例用type函数。

利用修饰器@staticmethod和@classmethod创建静态方法和类方法

>>> class ClassN:
	@staticmethod
	def Speak():
		print('这是个静态方法')
	@classmethod
	def Talk(cls):
		print('这是个类方法,属于',cls)

		
>>> ClassN.Speak()
这是个静态方法
>>> ClassN.Talk()
这是个类方法,属于 <class '__main__.ClassN'>

1.6有关属性的特殊方法

这四个特殊方法都与属性相关联,可以拦截对对象属性的所有访问企图。

  • __getattribute__(self,name):在属性被访问的时候自动调用

  • __getattr__(self,name):在访问对象中不存在的属性时自动调用

  • __setattr__(self,name,value):尝试给属性赋值时自动调用

  • __delattr__(self,name):尝试删除属性时调用

需要注意,编写方法__setattr__时需要注意避开无限循环陷阱(尝试给属性赋值调用给属性赋值),同理编写__getattribute__时也要注意这个问题,通过__dict__访问属性也会被拦截。

1.7迭代器方法(iterate)

for循环可以迭代序列和字典。而自定义对象要迭代,可以使用实现了__iter__方法的对象。

__iter__返回一个迭代器,是一个包含了__next__的对象。调用__next__时迭代器会返回其下一个值,当迭代器没有可供返回的值时,引发StopIteration异常。

当一个序列的每一项都可以通过计算得到,而你只想一个个获取计算值,迭代器或许比序列要好用。因为序列储存值过多会占用大量内存。另外,相比于序列,迭代器可抵达的长度是无穷大。

>>> class Addup:
	def __init__(self):
		self.a=1
	def __next__(self):
		self.a+=1
		return self.a
	def __iter__(self):
		return self

	
>>> AU=Addup()
>>> for i in AU:
	if i < 5:
		print(i)
	else:
		break

	
2
3
4
#由于每次调用迭代器都会先执行方法__next__的内容,因此i从2开始,即已经执行过+1了

如果不加以限制,i将去往无穷大。

也可以用list函数将其转化为列表,但是要加上判断语句。

>>> class Addup:
	def __init__(self):
		self.a=1
	def __next__(self):
		if self.a>5:raise StopIteration
		self.a+=1
		return self.a
	def __iter__(self):
		return self

	
>>> AU=Addup()
>>> list(AU)
[2, 3, 4, 5, 6]
#这个例子可能有点令人费解,self.a+=1应该提前一行以实现输入即所得的效果

1.8生成器(generator)

生成器是返回迭代器的函数,python将使用了yield的函数称为生成器。

生成器是一种使用普通函数语法定义的迭代器。

其实yield有点像return,但是每次执行yield语句后函数会暂停运行并且保持当前的运行状态,返回yield的值,并在下次执行next()时从当前位置继续运行。

举个简单的阶乘计算例子:

>>> def Jiechen(n):
    a=1
    for i in range(1,n+1):
        a*=i
        yield a


>>> nl=Jiechen(10)
>>> list(nl)
[1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800]
>>> nl
<generator object Jiechen at 0x00000200893C2D60>
>>> type(nl)
<class 'generator'>

如上,实现了利用函数生成阶乘序列,但这可能不是最优解。检查nl的类型,发现这是个生成器类。

小结:

本篇介绍的内容主要是特殊方法和迭代器。本篇罗列了几种比较重要的特殊方法:

  • 构造函数__init__:在对象创建时自动调用

  • 析构函数__del__:在对象被销毁时自动调用(不推荐)

  • __len__(self):返回集合包含的项数

  • __getitem__(self,key):返回键的值

  • __setitem__(self,key,value):以键值对的方式储存值

  • __delitem__(self,key):对对象的组成部分使用__del__时调用

  • __getattribute__(self,name):在属性被访问的时候自动调用

  • __getattr__(self,name):在访问对象中不存在的属性时自动调用

  • __setattr__(self,name,value):尝试给属性赋值时自动调用

  • __delattr__(self,name):尝试删除属性时调用

  • __iter__:创建迭代器

  • __next__:让迭代器返回下一个值

还有一个比较重要的函数(类),能够创建属性:property函数

以及一个继承超类构造函数的函数:super函数

和一个包含yield语句的生成器函数

最后是两个修饰器,用于修饰函数为静态方法或者类方法:@staticmethod和@calssmethod

这些都是面向对象的重要内容,但是感觉了解和掌握的还不够彻底,应该投入实战会有更深入的体会。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值