方法、属性和迭代器
在Python语言中,存在一些特殊方法,这些方法在命名上与普通方法不同。例如,一些方法会在名字前后各加两个下划线(__method__),这种拼写方式有特殊意义,所以在命名普通方法时,千万不要用这种命名方式。如果类实现了这些方法中的某一个,那么这个方法在特殊情况下会被Python调用
一、构造方法
是创建对象的过程中被调用的第一个方法,用于初始化对象中需要的变量。
class Person:
def __init__(self,name="Bill"):
self.name=name
print("构造方法已被调用")
def getName(self):
return self.name
def setName(self,name):
self.name=name
person = Person()
print(person.getName())
person1 = Person("Mike")
print(person1.getName())
person2 = Person("John")
print(person2.getName())
1、重写普通方法和构造方法
当B类继承A类时,B类就会拥有A类的所有成员变量和方法。如果B类中的方法名与A类中的方法名相同,那么B类中的方法会重写A类中的同名方法。也就是说,创建B对象,实际上是调用B类中的构造方法,而不是A类中的构造方法
#由于SongBbird类重写了Bird类的构造方法,所以BIrd类中的初始化的hungry变量不再存在了,因此,调用SongBird类的eat方法会抛出异常
class Bird:
def __init__(self):
self.hungry=True
def eat(self):
if self.hungry:
print("已经吃了虫子")
self.hungry=False
else:
print("已经吃过饭了,不饿了!")
b=Bird()
b.eat()
b.eat()
class SongBird(Bird):
def __init__(self):
self.sound = "向天再借五百年"
def sing(self):
print(self.sound)
sb = SongBird()
sb.sing()
sb.eat()
2、使用super函数
在子类中如果重写了超类的方法,通常需要在子类方法中调用超类的同名方法。(超类:如果C是B的子类,那么B可以称为C的父类;如果B是A的子类,而C也从A继承,只是A不是C的直接父类,那么A可以称作C的超类)
如果要在子类中访问超类中的方法,需要使用super函数。该函数返回的对象代表超类对象,所以访问super函数返回的对象中的资源都属于超类。super函数可以不带任何参数,也可以带两个参数,第一个参数表示当前类的类型,第二个参数需要传入self。
class Animal:
def __init__(self):
print("Animal init")
class Bird(Animal):
def __init__(self,hungry):
super().__init__()
self.hungry=hungry
def eat(self):
if self.hungry:
print("已经吃了虫子了!")
self.hungry=False
else:
print("已经吃过饭了,不饿了!")
b=Bird(False)
b.eat()
b.eat()
class SongBird(Bird):
def __init__(self,hungry):
#调用Bird类的构造方法,如果为super函数指定参数,第一个参数需要是当前类的类型(SongBird)
super(SongBird,self).__init__(hungry)
self.sound="向天再借五百年"
def sing(self):
print(self.sound)
sb=SongBird(True)
sb.sing()
sb.eat()
二、特殊成员方法
除了构造方法,还可以使用如下4个特殊方法定义自己的序列类。
__len__(self):返回序列中元素的个数。使用len函数获取序列对象的长度时会调用该方法。
__getitem__(self,key):返回与所给键对应的值。__getitem__方法的第2个参数表示(key),使用sequence[key]获取值时会调用该方法。
__setitem__(self,key,value):设置key对应的值。使用sequence[key]=value设置序列中键对应的值时调用该方法
__delitem__(self,key):从序列中删除键为key的key-value对。当使用del关键字删除序列中键为Key的key-value对时调用该方法。
class FactorialDict:
def __init__(self):
self.numDict = {}
def factorial(self,n):
if n==0 or n==1:
return 1
else:
return n*self.factorial(n-1)
def __getitem__(self, key):
print("__getitem__方法被调用,key={}".format(key))
if key in self.numDict:
return self.factorial(self.numDict[key])
else:
return 0
def __setitem__(self, key, value):
print("__setitem__方法被调用,key={},value={}".format(key,value))
self.numDict[key]=int(value)
def __delitem__(self, key):
print("__delitem__方法被调用,key={}".format(key))
del self.numDict[key]
def __len__(self):
print("__len__方法被调用")
return len(self.numDict)
d=FactorialDict()
d["4!"]=4
d["7!"]=7
d["12!"]=12
print(d["4!"])
print(d["7!"])
print(d["12!"])
print("len={}".format(len(d)))
del d["7!"]
print("len={}".format(len(d)))
三、静态方法和类方法
Python包含三种方法:实例方法、静态方法和类方法。
实例方法:要实例化类才可以调用。
静态方法:在调用时根本不需要类的实例(静态方法不需要self参数)
类方法:调用和静态方法完全一样,所不同的是,类方法和实例方法的定义方式相同,都需要一个self参数,只不过self参数含义不同。对于实例方法来说,这个self参数就代表当前类的实例,可以通过self访问对象中的方法和属性。而类方法的self参数表示类的元数据,也就是类本身,不能通过self参数访问对象中的方法和属性,只能通过self参数访问类的静态方法和静态属性
class MyClass:
#实例化方法
def instanceMethod(self):
pass
#静态方法
@staticmethod
def staticMethod():
pass
#类方法
@classmethod
def classMethod():
pass
class MyClass:
name = "Bill"
def __init__(self):
print("MyClass的构造方法被调用")
#定义实例变量,静态方法和类方法不能访问
self.value=20
#静态方法
@staticmethod
def run():
#访问MyClass中的name
print("*",MyClass.name,"*")
print("静态方法run被调用")
#定义一个类方法
@classmethod
def do(self):
print(self)
print("[",self.name,"]")
print("调用静态方法run")
self.run()
#在类方法中不能访问实例变量,否则会抛出异常
print("成员方法do被调用")
#定义实例方法
def dol(self):
print(self.value)
print("<",self.name,">")
print(self)
MyClass.run() #调用静态方法
c=MyClass() #创建类的实例
c.do() #通过类的实例调用类方法
print("MyClass2.name","=",MyClass.name) #通过类访问静态变量
MyClass.do() #通过类调用类方法
c.dol() #通过类的实例访问实例方法
总结
1.方法调用:实例化方法只能通过实例来访问,静态方法只能通过类访问,类方法既可以通过类的实例访问,也可以通过类来访问
2.变量访问:通过实例定义的变量只能被实例方法访问,而直接在类中定义的静态变量既可以被实例方法访问,也可以被静态方法和类方法访问。
3.实例方法不能被静态方法和类方法访问,但静态方法和类方法可以被实例方法访问。
四、迭代器:__iter__
为什么要使用迭代器而不是用列表,列表可以获取列表的长度,也可以遍历所有的元素,且容易理解。但是是要付出代价的,列表之所以可以用索引来快速定位其中的任何一个元素,是因为列表一下子将所有的数据都装载在内存中,而且是一块连续的内存空间。当数据量比较小时,实现较为容易;当数据量很大时,会非常消耗内存资源。而迭代就不同,迭代是读取多少元素,就讲多少元素装载到内存中,不读取就不装载。
如果在一个类中定义__iter__方法,那么这个类的实例就是一个迭代器。__iter__方法需要返回一个迭代器,所以就返回对象本身即可(self)。当对象每迭代一次,就会调用迭代器中的另外一个特殊成员方法__next__。该方法需要返回当前迭代的结果。
class RightTriangel:
def __init__(self):
#定义一个变量n,表示当前行数
self.n = 1
def __next__(self):
r = "*" * (2 * self.n - 1) #获取每一行字符串,长度为2*n - 1
self.n += 1
return r
#该方法必须返回一个迭代器
def __iter__(self):
return self
rt = RightTriangel()
for e in rt :
if len(e)>20:
break
print(e)
#迭代器计算斐波那契数列
class Fibonacci:
def __init__(self):
self.a=0
self.b=1
def __next__(self):
result = self.b #self.b为当前要迭代的值
self.a , self.b = self.b , self.a + self.b #计算斐波那契数列的下一个值
return result #返回当前迭代的值
def __iter__(self):
return self
fibs = Fibonacci()
for fib in fibs:
print(fib,end=" ")
if fib>500:
break
将迭代器转换为列表
尽管迭代器很好用,但是仍然不具备某些功能。例如,通过索引获取某个元素,进行分片操作。这些操作都是列表的专利,所以在很多时候,需要将迭代器转换为列表。
但是有很多迭代器都是无限迭代的,因此,在将迭代器转换为列表的时候,需要给迭代器能够迭代的元素限定一个范围,否则内存就会溢出。要想让迭代器停止迭代,只需要抛出StopIteration异常即可。
通过list列表可以直接将迭代器转换为列表
__iter__():返回对象本身
__next__():返回下一个数据,如果没有数据了,需要抛出一个StopIteration异常
class Fibonacci:
def __init__(self):
self.a=0
self.b=1
def __next__(self):
result = self.b #self.b为当前要迭代的值
self.a , self.b = self.b , self.a + self.b #计算斐波那契数列的下一个值
if result > 500: raise StopIteration
return result #返回当前迭代的值
def __iter__(self):
return self
fibs1 = Fibonacci()
print(list(fibs1))
fibs2 = Fibonacci()
for fib in fibs2:
print(fib,end=" ")
五、生成器
如果说迭代器是以类为基础的单值产生器,那么生成器(generator)就是以函数为基础的单值生成器。也就是说,迭代器和生成器都只能一个值一个值地生产。每迭代一次,只能得到一个值。
所不同的是,迭代器需要在类中定义__iter__和__next__方法,在使用时需要创建迭代器的实例。而生成器是通过一个函数实现的,可以直接调用。所以从某种意义上讲,生成器在使用上更简洁。
1、创建生成器
要定义一个生成器,首先要定义一个函数,在该函数对某个集合或迭代器进行迭代,然后使用yield语句产生当前要生成的值,这时函数会被冻结,直到调用生成器的代码继续迭代下一个值,生成器才会继续被执行
def MyGenerator():
num = [1,2,3,4,5,6,7,8]
for n in num:
yield n
for i in MyGenerator():
print(i,end=" ")
2、生成器的使用
生成器函数是一种特殊的函数,可以在迭代过程中逐步产生值,而不是一次性返回所有结果。
如果将yield n 换成print(n)就非常容易理解了,对num列表进行迭代,并输出每一个元素值。
不过这里使用了yield语句来提交当前生成的值,也就是for 循环的n的值,然后MyGenerator函数会被冻结(暂时不往下执行了),直到for循环继续下一次循环,再次对MyGenerator函数进行迭代,
MyGenerator函数才会继续执行,继续使用yield语句提交下一个要生成的值,直到最后一个元素为止
#利用生成器将一个二维的列表转换为一维的列表
L=[[1,2,3],[4,3,2],[1,2,4,5,7]]
#生成器函数
def enumList(L):
#对二维列表进行迭代
for i in L:
for j in i: #每一个i是一个一维列表
yield j
for i in enumList(L):
print(i,end=" ")
print()
#将生成器函数转换为列表
numList = list(enumList(L))
print(numList)
#递归生成器
#在上例中对一个二维列表进行了一维处理。要想对三维、四维甚至更多维的列表进行一维化处理,可以采用递归的方式。
#首先对多维列表进行迭代,然后判断每隔列表元素是否还是列表:如果仍然是列表,继续对该列表进行迭代;如果是一个普通的值,使用yield语句返回生成的值
L=[4,[1,2,[3,5,6]],[4,3,[1,2,[4,5]],2],[1,2,4,5,7]]
#生成器函数
def enumList(L):
try:
#对多维列表进行迭代
for i in L:
#将多维列表的每一个元素传入,如果该元素是一个列表,继续迭代
#否则会抛出一个TypeError异常,处理异常时直接使用yield语句返回这个普通的元素值
for ele in enumList(i):
yield ele
except TypeError:
yield L
for i in enumList(L):
print(i,end=" ")
print()