python基础教程(第三版)学习笔记(九)

第九章 魔法方法、特性和迭代器

9.1 如果你使用的不是python3
在Python 2.2中,Python对象的工作方式有了很大的变化。这种变化带来了多个方面的影响。这些影响对Python编程新手来说大都不重要,但有一点需要注意:即便你使用的是较新的Python 2版本,有些功能(如特性和函数super)也不适用于旧式类。要让你的类是新式的,要么在模块开头包含赋值语句__metaclass__ = type(这以前提到过),要么直接或间接地继承内置类object或其他新式类。
在Python 3中没有旧式类,因此无需显式地继承object或将__metaclass__设置为type。所有的类都将隐式地继承object。如果没有指定超类,将直接继承它,否则将间接地继承它。

9.2 构造函数
构造函数是几乎所有程序中都用的典型的魔法方法:__init__(是否记得,在定义My_Exception时用过)。
构造函数不同于普通方法的地方在于,将在对象创建后自动调用它们。
9.2.1 重写普通方法和特殊的构造函数
每个类都有一个或多个超类,并从它们那里继承行为。对类B的实例调用方法(或访问其属性)时,如果找不到该方法(或属性),将在其超类A中查找。
'''

class A:
    def hello(self):
        print("你好,这是A类的方法。")

class B(A):
    pass

a=A()
b=B()
a.hello()
b.hello()

'''

你好,这是A类的方法。
你好,这是A类的方法。


------------------
(program exited with code: 0)

请按任意键继续. . .


如果子类中也定义了一个和父类同名的方法,那么子类对象调用这个方法时,它不再调用父类方法而调用子类方法。
'''

class D(A):
    def hello(self):
        print("你好,这是D类的方法。")
        
d=D()
d.hello()

'''

你好,这是A类的方法。
你好,这是A类的方法。
你好,这是D类的方法。


------------------
(program exited with code: 0)

请按任意键继续. . .


这种在子类中重新定义同名方法的方法就叫方法重写。
重写是继承机制的一个重要方面,对构造函数来说尤其重要。构造函数用于初始化新建对象的状态,而对大多数子类来说,除超类的初始化代码外,还需要有自己的初始化代码。虽然所有方法的重写机制都相同,但与重写普通方法相比,重写构造函数时更有可能遇到一个特别的问题:重写构造函数时,必须调用超类(继承的类)的构造函数,否则可能无法正确地初始化对象。
'''

class C:
    def __init__(self):
        self.a=10
        print("我是C类")
    def ser(self):
        print("我是C类的ser方法",self.a)
        
class E(C):
    def __init__(self):
        print("我是E类")
        
c=C()
e=E()
c.ser()
#e.ser()

'''

我是C类
我是E类
我是C类的ser方法 10
Traceback (most recent call last):
  File "xx.py", line29, in <module>
    e.ser()
  File "xx.py", line 20, in ser
    print("我是C类的ser方法",self.a)
AttributeError: 'E' object has no attribute 'a'


------------------
(program exited with code: 1)

请按任意键继续. . .

为何会这样呢?因为在子类E中重写了构造函数,但新的构造函数没有包含任何初始化属性父类a的代码。要消除这种错误,子类E的构造函数必须调用其父类C的构造函数,以确保基本的初始化得以执行。为此,有两种方法:调用未关联父超类构造函数,或者使用函数super。
9.2.2 调用未关联的构造函数
(这主要应用于Python2.x版本,因此略去)
9.2.3使用函数super
'''

class F(C):
    def __init__(self):
        super().__init__()
        print("我是F类")
        
c=C()
f=F()
c.ser()
f.ser()

'''

我是C类
我是F类
我是C类的ser方法 10
我是C类的ser方法 10


------------------
(program exited with code: 0)

请按任意键继续. . .

9.3 元素访问
虽然__init__无疑是你目前遇到的最重要的特殊方法,但还有不少其他的特殊方法,让你能够完成很多很酷的任务。
9.3.1 基本的序列和映射协议
序列和映射基本上是元素(item)的集合,要实现它们的基本行为(协议),不可变对象需要实现2个方法,而可变对象需要实现4个。    
i、__len__(self):这个方法应返回集合包含的项数,对序列来说为元素个数,对映射来说为键-值对数。如果__len__返回零(且没有实现覆盖这种行为的__nonzero__),对象在布尔上下文中将被视为假(就像空的列表、元组、字符串和字典一样)。
ii、__getitem__(self, key):这个方法应返回与指定键相关联的值。对序列来说,键应该是0~n -1的整数(也可以是负数,这将在后面说明),其中n为序列的长度。对映射来说,键可以是任何类型。
iii、__setitem__(self, key, value):这个方法应以与键相关联的方式存储值,以便以后能够使用__getitem__来获取。当然,仅当对象可变时才需要实现这个方法。
iv、__delitem__(self, key):这个方法在对对象的组成部分使用__del__语句时被调用,应删除与key相关联的值。同样,仅当对象可变(且允许其项被删除)时,才需要实现这个方法。
对于这些方法,还有一些额外的要求。
i、对于序列,如果键为负整数,应从末尾往前数。换而言之,x[-n]应与x[len(x)-n]等效。
ii、如果键的类型不合适(如对序列使用字符串键),可能引发TypeError异常。
iii、对于序列,如果索引的类型是正确的,但不在允许的范围内,应引发IndexError异常。
'''

d={"a":10,"b":20,"c":30,"d":20,"e":10,}
len1=d.__len__()
getitem1=d.__getitem__("c")
print(len1,"\t",getitem1)
d.__setitem__("f",90)
print(d.__len__())
print(d.__len__(),"\t",d.__getitem__("f"))
d.__delitem__("f")
print(d.__len__(),"\t",d.__getitem__("c"))

'''

5        30
6
6        90
5        30


------------------
(program exited with code: 0)

请按任意键继续. . .


以上程序如果最后一句改为print(d.__len__(),"\t",d.__getitem__("c"))将会报错。
9.3.2 从list、dict和str派生
可以建立一些继承了list、dict和str的新类,以满足不同的需要。形如:
'''

class NewList(list):
    def __init__(self,*args):
        super().__init__(*args)
        #定义新方法或重写list的方法
        pass


        
'''    
9.4 其他魔法方法9.5 特性
封装是面向对象编程的一个重要的特性,但是经过封装的类,是不能直接访问私有属性的,为了达到访问类中私有属性的目的,在类中含必须创建一些方法,利用这些方法达到间接访问这些私有属性的目的。由这种方法访问的属性称为特性(property)。
9.5.1 函数property
property() 函数的作用是在类中返回属性值。将 property 函数用作装饰器可以很方便的创建只读属性。    
格式为class property([fget[, fset[, fdel[, doc]]]])其中:fget为获取属性值的函数;fset为设置属性值的函数;fdel为删除属性值函数;doc为属性描述信息。调用函数property时,还可不指定参数、指定一个参数、指定三个参数或指定四个参数。如果没有指定任何参数,创建的特性将既不可读也不可写。如果只指定一个参数(获取方法)  ,创建的特性将是只读的。第三个参数是可选的,指定用于删除属性的方法(这个方法不接受任何参数)。第四个参数也是可选的,指定一个文档字符串。这些参数分别名为fget、fset、fdel和doc。如果你要创建一个只可写且带文档字符串的特性,可使用它们作为关键字参数来实现。
'''

class Rect:
    def __init__(self):
        self.__width=0
        self.__leng=0
    def get_size(self):
        if self.__width and  self.__leng:
            return "矩形的宽{},矩形的长{}".format (self.__width,self.__leng)    
        else:
            return 0
                
    def set_size(self,size):
        self.__width,self.__leng=size
    def del_size(self):
        del self.__width
        del self.__leng

    
    size=property(get_size,set_size,del_size,"隐藏了set、get、del方法")


r=Rect()
r.size=10,5
print(r.size)
r.size=150,100
print(r.size)

'''

矩形的宽10,矩形的长5
矩形的宽150,矩形的长100


------------------
(program exited with code: 0)

请按任意键继续. . .


这样不但隐藏了类的私有属性而且把类的set、get及del方法也隐藏了。
9.5.2 静态方法和类方法
静态方法和类方法是这样创建的:将它们分别包装在staticmethod和classmethod类的对象中。静态方法的定义中没有参数self,可直接通过类来调用。类方法的定义中包含类似于self的参数,通常被命名为cls。对于类方法,也可通过对象直接调用,但参数cls将自动关联到类。
'''

class MyClass():
    __a=0
    b=1
    @staticmethod                  #静态方法装饰器(以后说)
    def smeth():                   #定义静态方法
        print("这是静态方法")
        
    @classmethod                   #类方法装饰器(以后说)
    def cmeth(cls):                #定义类方法
        print("这是类方法")
    
    def __init__(self):
        
        print("这是MyClass类")
        
    def run(self):
        print("这是run方法")
        
MyClass.smeth()                    #静态方法和类方法不需要实例,可直接调用。
MyClass.cmeth()
my_class=MyClass()
my_class.run()                     #实例方法不需实例化才能调用

'''

这是静态方法
这是类方法
这是MyClass类
这是run方法


------------------
(program exited with code: 0)

请按任意键继续. . .


9.5.3 __getattr、__setattr__等方法
这些方法大都是拦截对对象属性访问的。
i、__getattribute__(self, name)在属性被访问时自动调用(只适用于新式类)。
ii、__getattr__(self, name)在属性被访问而对象没有这样的属性时自动调用。
iii、__setattr__(self, name, value)试图给属性赋值时自动调用。
iv、__delattr__(self, name)试图删除属性时自动调用。
相比函数property,这些魔法方法使用起来要棘手些(从某种程度上说,效率也更低),但它们很有用,因为你可在这些方法中编写处理多个特性的代码。然而,在可能的情况下,还是使用函数property吧。

9.6 迭代器
将更详细地介绍迭代器。对于魔法方法,这里只介绍__iter__,它是迭代器协议的基础。
9.6.1 迭代器协议
迭代(iterate)意味着重复多次,就像循环那样。本书前面只使用for循环迭代过序列和字典,但实际上也可迭代其他对象:实现了方法__iter__的对象
方法__iter__返回一个迭代器,它是包含方法__next__的对象,而调用这个方法时可不提供任何参数。当你调用方法__next__时,迭代器应返回其下一个值。如果迭代器没有可供返回的值,应引发StopIteration异常。你还可使用内置的便利函数next,在这种情况下,next(it)与it.__next__()等效。
使用迭代器不是像for in那样一次获取所有的值,而是根据需要一次可获取一个或几个值,因此它节省空间.使用迭代器更通用、更简单、更优雅。
还有一些序列不能用列表的方式显示,这时迭代器就是更好的工具。下面来看一个不能使用列表的示例,因为如果使用列表,这个列表的长度必须是无穷大的!这个“列表”为斐波那契数列。
为更好的理解书中的例子,先了解一下下面的程序:
'''

a=0
b=1
a,b=b,a+b 
 


'''
    等价于:a=b,b=a+b,即把后一个数传给前一个数(b赋值给a),
    而后一个数得到与前一个数的和(即把a+b赋给b)。
'''

print(a)
print(b)
for i in range(5):
    a,b=b,a+b
    print("_"*80)
    print(a)
    print(b) 


    
'''

1
1
_______________________________________________________________________________

1
2
_______________________________________________________________________________

2
3
_______________________________________________________________________________

3
5
_______________________________________________________________________________

5
8
_______________________________________________________________________________

8
13


------------------
(program exited with code: 0)

请按任意键继续. . .


斐波那契数列:
'''

class Fibonacci:
    def __init__(self):
        self.a=0
        self.b=1
    def __next__(self):                        #创建斐波那契数列
        self.a,self.b=self.b,self.a+self.b
        return self.a                          #返回迭代器集合
    def __iter__(self):                        #定义集合可迭代
        return self                            #返回实例化对象
        
fib=Fibonacci()
for f in fib:
    if f>1000:
        print(f)
        break

'''

1597


------------------
(program exited with code: 0)

请按任意键继续. . .


9.6.2从迭代器创建序列
除了对迭代器和可迭代对象进行迭代(通常这样做)之外,还可将它们转换为序列。
'''

class Test:
    value = 0
    sums=0
    def __next__(self):
        self.value+=1
        self.sums=1+(1/self.value)  #无限序列(如果以此为基础输出常数e的近似值怎么办?)
        if self.value>10:
            raise StopIteration     #设置异常终止程序(StopIteration异常是在循环对象穷尽所有元素时的报错)
        return self.sums
    def __iter__(self):
        return self
        
ti = Test()
print(list(ti))

'''

[2.0, 1.5, 1.3333333333333333, 1.25, 1.2, 1.1666666666666667, 1.1428571428571428
, 1.125, 1.1111111111111112, 1.1]


------------------
(program exited with code: 0)

请按任意键继续. . .

9.7 生成器
生成器是一种使用普通函数语法定义的迭代器。
9.7.1 创建生成器
生成器(generator)即是一个含有yield的函数。反过来说包含yield语句的函数都被称为生成器。
生成器和一般函数不同,它不是使用return返回一个值,而是可以生成多个值,每次一个。每次使用yield生成一个值后,函数都将冻结,即在此停止执行,等待被重新唤醒。被重新唤醒后,函数将从停止的地方开始继续执行。
生成器的另一个特点就是节省空间,它可以一边循环一边计算,可以在循环的过程中不断推算出后续的元素。
要创建一个generator,有很多种方法。
第一种方法很简单,只有把一个列表生成式的[]中括号改为()小括号,就创建一个generator
'''

a=[x for x in range(10)]
print(type(a))
b=(x for x in range(10))
print(type(b))

'''

<class 'list'>
<class 'generator'>


------------------
(program exited with code: 0)

请按任意键继续. . .


列表和生成器的输出方式不同:
'''

print(a)
print("*"*80)
print(b)
print("*"*80)
print(next(b))        #它必须用next函数输出,每次打印一个数字
print(next(b))        #第二次接着打印第二个数字

'''

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
***********************************************************************

<generator object <genexpr> at 0x000000000213CCF0>
***********************************************************************

0
1


------------------
(program exited with code: 0)

请按任意键继续. . .


也可以用for in循环:
'''

for i in b:
    print(i)


    
'''

0
1
2
3
4
5
6
7
8
9


------------------
(program exited with code: 0)

请按任意键继续. . .


创建一个generator后,一般不会调用next(),而是通过for循环来迭代,并且不需要关心StopIteration的错误。
第二个创建生成器的方法就是定义含有yield关键字的“函数”。
'''

def fib(num):
    i,x,y=0,0,1
    while i<num:
        yield y
        x,y=y,x+y   #斐波那契数列
        i+=1
    return

for j in fib(10):
    print(j,end="\t")

'''

1       1       2       3       5       8       13      21      34      55


------------------
(program exited with code: 0)

请按任意键继续. . .


9.7.2 递归式生成器
'''

def gen(nums):
    try:
        try:nums+''             #筛选字符串
        except TypeError:pass
        else:
            raise TypeError
        for n in nums:
            for i in gen(n):    #递归
                yield i
    except TypeError:
        yield nums
        
nums=[22,33,44,[44,67,[88,23,19]]]
lis=list(gen(nums))
print(lis)


        
'''

[22, 33, 44, 44, 67, 88, 23, 19]


------------------
(program exited with code: 0)

请按任意键继续. . .


以上可以看出,在同为可迭代的情况下,生成器和函数仅有yield和return的区别。
9.7.4 通用生成器
生成器由两个单独的部分组成:生成器的函数和生成器的迭代器。生成器的函数是由def语句定义的,其中包含yield。生成器的迭代器是这个函数返回的结果。用不太准确的话说,这两个实体通常被视为一个,通称为生成器。
'''

def generator():
    print("一")
    yield 10
    print("二")
    yield 18
    print("三")
    yield 22
    print("四")
    yield 60
g=generator()
print(next(g))
print(next(g))


'''

一
10
二
18


------------------
(program exited with code: 0)

请按任意键继续. . .


用这种方式可以生成任意的数据序列,且能达到节约空间的目的。
9.7.4 生成器(generator}的方法
在生成器开始运行后,可使用生成器和外部之间的通信渠道向它提供值。这个通信渠道包含如下两个端点。
i、外部世界:外部世界可访问生成器的方法send,这个方法类似于next,但接受一个参数(要发送的“消息”,可以是任何对象)。
ii、生成器:在挂起的生成器内部,yield可能用作表达式而不是语句。换而言之,当生成器重新运行时,yield返回一个值——通过send从外部世界发送的值。如果使用的是next,yield将返回None。仅当生成器被挂起(即遇到第一个yield)后,使用send(而不是next)才有意义。要在此之前向生成器提供信息,可使用生成器的函数的参数。
send函数官方文档是这样说的,“恢复执行,并将一个值发送到生成器函数。参数为当前输出的结果。send()方法返回下一个值,如果到达生成器末尾,则引发StopIteration。如果是刚启动生成器就用send函数,那么就用None作为其参数调用send函数,因为不知道当前输出的结果。”——其实在不知道当前输出的结果时也要用None作为参数。
'''

print(g.send(18))
print(g.send(None))

'''
三
22
四
60


------------------
(program exited with code: 0)

请按任意键继续. . .


生成器还包含另外两个方法。
方法throw:用于在生成器中(yield表达式处)引发异常,调用时可提供一个异常类型、一个可选值和一个traceback对象。
方法close:用于停止生成器,调用时无需提供任何参数。
9.7.5 模拟生成器
(主要针对较老的Python版本,略)

9.8 八皇后问题

9.8.1 生成器的回溯
在有些应用程序中,你不能马上得到答案。你必须尝试多次,且在每个递归层级中都如此。打个现实生活中的比方吧,假设你要去参加一个很重要的会议。你不知道会议在哪里召开,但前面有两扇门,而会议室就在其中一扇门的后面。你选择进入左边那扇门后,又看到两扇门。你再次选择进入左边那扇门,但发现走错了。因此你往回走,并进入右边那扇门,但发现也走错了。因此你继续往回走到起点,现在可以尝试进入右边那扇门。这就是回溯.对于逐步得到结果的复杂递归算法,非常适合使用生成器来实现。

伪代码:

for 第一级的可能路径:
    for 第一级的可能路径:
        for 第一级的可能路径:
            .
            .
            .
                for 第一级的可能路径:
                    能解决吗?


9.8.2 问题
    .
    .
    .

(待续)

猜你喜欢

转载自blog.csdn.net/micorjun/article/details/83690418