Python基础教程读书笔记(第7章—第8章:更加抽象;异常)

第七章:更加抽象

1:对象的重要优点:多态(Polymorphism)、封装(Encapsulation)、继承(Inheritance)

1)多态:多态意味着就算不知道变量所引用的对象类型是什么,还是能对它进行操作,而它也会根据对象(或类)类型的不同而表现出不同的行为。
(1) 多态和方法——绑定到对象特性上面的函数称为方法。标准库random中包含choice函数,可以从序列中随机选出元素:

>>> from random import choice
>>> x=choice(['Hello,world!', [1,2,'e','e',4]]) #x=[1, 2, 'e', 'e', 4]
>>> x.count('e')
2

很多函数和运算符都是多态的,唯一能毁掉多态的就是使用函数显式的检查类型,如:type、isinstance以及issubclass 函数等

2):封装:封装是对全局作用域中其他区域隐藏多余信息的原则。听起来像多态——使用对象而不知道其内部细节,两者概念类似,因为它们都是抽象的原则——它们都会帮助处理程序组件而不用过多关心多余细节
但封装不等同于多态。多态可以让用户对于不知道是什么类(或对象类型)的对象进行方法调用,而封装是可以不用关心对象是如何构建的而直接进行使用。

3)继承——继承是另一个懒惰(褒义)的行为。

2:类和类型
1)类:python中,习惯使用单数名词来描述对象的类,如Bird和Lark

2)创建类:新式类的语法中,需要在模块或脚本开始的地方放置语句 __metaclass__ = type

View Code
__metaclass__ = type #确定使用新式类

#class语句会在函数定义的地方创建自己的命名空间
class Person:

    #self是对于对象自身的引用
    def setName(self,name):
        self.name = name

    def getName(self):
        return self.name

    def greet(self):
        print("Hello,world! I'm %s." % self.name)


#samples
>>> foo = Person()
>>> bar = Person()
>>> foo.setName('Luke Skywalker')
>>> bar.setName('Anakin Skywalker')
>>> foo.greet()
Hello,world! I'm Luke Skywalker.
>>> bar.greet()
Hello,world! I'm Anakin Skywalker.

在调用foo的setName和greet函数时,foo自动将自己作为第一个参数传入函数中——因此形象的命名为self(习惯上叫做self,可以命名为其他)
如果知道foo是Person的实例的话,还可以把foo.greet()看做Person.greet(foo)方便的简写

3)特性、函数、和方法
self 参数事实上正是方法和函数的区别。方法(更专业一点可称为绑定方法)将它们的第一个参数绑定到所属的实例上,因为这个参数可以不必提供。所以可以讲特性绑定到一个普通函数上,这样就不会有特殊的self参数了

View Code
>>> class Class:
    def method(self):
                #print(self) => <__main__.Class object at 0x02D7AB50>
        print('I have a self!')

        
>>> def function():
    print('I don\'t have a self!')

    
>>> instance = Class()
>>> instance.method()
I have a self!
>>> instance.method = function
>>> instance.method()
I don't have a self!

注意,self 参数并不取决于调用方法的方式,目前使用的是实例调用方法,可以随意使用引用同一个方法的其他变量:

>>> class Bird:
    song = 'Squaawk!'
    def sing(self):
        print(self.song)

        
>>> bird = Bird()
>>> bird.sing()
Squaawk!
>>> birdsong=bird.sing
>>> birdsong()
Squaawk!

尽管最后一个方法调用看起来与函数调用十分相似,但是变量birdsong引用绑定方法bird.sing上,也就意味着这还是对self参数的访问(意即它仍旧绑定到类的相同实例上)。
提示:在第九章将会介绍类是如何调用超类的方法的(具体来说就是超类的构造器)。这些方法直接通过类调用,它们没有绑定自己的self参数到任何东西上,所以叫做非绑定方法

再论私有化:有些人(如SmallTalk之父,SmallTalk的对象特性只允许由同一个对象的方法访问)觉得这样破坏了封装的原则,他们认为对象的状态对于外部应该是完全隐藏(不可访问)的。
使用私有(private)特性,这是对象外部无法访问,但getName和setName等访问器(accessor)能够访问的特性——属性是访问器的好选择

Python不直接支持私有方式,可以用一些小技巧达到私有特性的效果:为了让方法或特性变为私有(从外部无法访问),只要在它的名字前面加上双下划线即可

View Code
>>> class Secretive:
    def __inaccessible(self):
        print("Bet you can't see me...")

    
>>> class Secretive:
    def __inaccessible(self):
        print("Bet you can't see me...")
    def accessible(self):
        print("The secret message is:")
        self.__inaccessible()

        
>>> s = Secretive()
>>> s.__inaccessible()
Traceback (most recent call last):
  File "<pyshell#124>", line 1, in <module>
    s.__inaccessible()
AttributeError: 'Secretive' object has no attribute '__inaccessible'
>>> s.accessible()
The secret message is:
Bet you can't see me...
#在类的内部定义中,双下划线开始的名字被“翻译”成前面加下划线和类名的形式
>>> Secretive._Secretive__inaccessible
<function Secretive.__inaccessible at 0x02D7EA50>
>>> s._Secretive__inaccessible
<bound method Secretive.__inaccessible of <__main__.Secretive object at 0x02D7AD90>>
#所以实际上还是能在类外访问这些私有方法的:
>>> s._Secretive__inaccessible()
Bet you can't see me...

如果不需要使用这种方法但是又想让其他对象不要访问内部数据,那么可以使用单下划线。例如:前面有下划线的名字都不会被带星号的imports语句(from module import *)导入

4)类的命名空间:定义类时,所有位于class语句中的代码都在特殊的命名空间中执行——类命名空间(class namespace)

5)指定超类

View Code
>>> class Filter:
    def init(self):
        self.blocked = []
    def filter(self,sequence):
        return [x for x in sequence if x not in self.blocked]

     
>>> class SPAMFilter(Filter): # SPAMFilter是Filter的子类
    def init(self): #重写Filter超类中的init方法
        self.blocked = ['SPAM']

        
>>> #Filter是个用于过滤序列的通用类,事实上它不能过滤任何东西
>>> f = Filter()
>>> f.init()
>>> f.filter([1,2,3])
[1, 2, 3]
>>> #Filter类的作用是它可以用作其他类的基类(超类),比如SPAMFilter类,可将序列中的“SPAM过滤除去”
>>> s = SPAMFilter()
>>> s.init()
>>> s.filter(['SPAM','SPAM','SPAM','SPAM','eggs','bacon','SPAM'])
['eggs', 'bacon']

6)调查继承:如果想要查看一个类是否是另一个的子类,可以使用内建的issubclass函数

>>> issubclass(SPAMFilter,Filter)
True

如果想要知道已知类的基类,可以直接使用它的特殊特性:__bases__

>>> SPAMFilter.__bases__
(<class '__main__.Filter'>,)

同样还能用isinstance 方法检查一个对象是否是一个类的实例:

>>> s = SPAMFilter()
>>> isinstance(s,SPAMFilter)
True

注意:使用isinstance并不是个好习惯,使用多态会更好一些
如果想要知道一个对象属于哪个类,可以使用__class__特性:

>>> s.__class__
<class '__main__.SPAMFilter'>

7)多个超类:

class Calculator:
    def calculate(self,expression):
        self.value = eval(expression)

class Talker:
    def talk(self):
        print('Hi,my value is ', self.value)

class TalkingCalculator(Calculator, Talker):
    pass


#sample
>>> tc = TalkingCalculator()
>>> tc.calculate('1+2*3')
>>> tc.talk()
Hi,my value is  7

子类(TalkingCalculator)自己不做任何事,它从自己的超类继承所有的行为。这种行为称为多重继承(multiple inheritance)。使用多重继承时,如果一个方法从多个超类继承(也就是说有两个相同名字的不同方法),那么必须注意一下超类的顺序(在class语句中):先继承的类中的方法会重写后继承的类中的方法。所以如果前例中的Calculator类也有一个talk方法,那么它就会重写Talker的talk方法(使其不可访问)。如果把它们的顺序掉过来,像这样:
class TalkingCalculator(Talker,Calculator):pass 就会让Talker类中的talk方法可用了。
如果超类们共享一个超类,那么在查找给定方法或者特性时访问超类的顺序称为MRO(Method Resolution Order,方法判定顺序)

8)接口和内省
“接口”的概念和多态有关,在处理多态对象时,只要关心它的接口(或称“协议”)即可——也就是公开的方法和特性。在Python中,不用显示地指定对象必须包含哪些方法才能作为参数接收。不用(像在java中一样)显式地编写接口,可以在使用对象的时候假定它可以实现你所要求的行为。如果它不能实现的话,程序就会失败。

可以检查所需方法是否已经存在:

>>> hasattr(tc,'talk')
True
>>> hasattr(tc,'fnord')
False

还可以检查talk特性是否可调用:

>>> callable(getattr(tc,'talk',None))
True
>>> callable(getattr(tc,'fnord',None))
False

getatrr函数允许提供默认值,以便在特性不存在时使用,然后对返回的对象使用callable函数。与getattr相对应的函数是setattr,可以用来设置对象的特性:

>>> setattr(tc,'name','Mr.Gumby')
>>> tc.name
'Mr.Gumby'

3:一些关于面向对象设计的思考

1)将属于一类的对象放在一起。如果一个函数操纵一个全局变量,那么两者最好都在类内作为特性和方法出现.
2)不要让对象过于亲密。方法应该只关心自己实例的特性,让其他实例管理自己的状态
3)要小心继承,尤其是多重继承。继承机制有时很有用,但也会在某些情况下让事情变得过于复杂。多重继承难以正确使用,更难调试
4)简单就好。让你的方法小巧。一般来说,多数方法都应能在30秒内被读完(以及理解)

当考虑需要什么类以及类要有什么方法时,应该尝试下面的方法:
1)写下问题的描述(程序要做什么?),把所有名词、动词和形容词加下划线
2)对于所有名词,用作可能的类
3)对于所有动词,用作可能的方法
4)对于所有形容词,用作可能的特性
5)把所有方法和特性分配到类

现在已经有了面向对象模型的草图了。还可以考虑类和对象之间的关系(比如继承或协作)以及它们的作用,可以用以下步骤精炼模型:
1)写下(或者想象)一系列的使用实例——也就是程序应用时的场景,试着包括所有的功能
2)一步步考虑每个使用实例,保证模型包括所有需要的对象。如果有遗漏的话就添加进来。如果某处不太正确则改正。继续,直到满意为止。

当认为已经有了可以应用的模型时,就可以开工了。

4:小结

对象:对象包括特性和方法。特性只是作为对象的一部分的变量,方法则是存储在对象内的函数。(绑定)方法和其他函数的区别在于方法总是将对象作为自己的第一个参数,这个参数一般称为self

类:类代表对象的集合(或一类对象),每个对象(实例)都有一个类。类的主要任务是定义它的实例会用到的方法

多态:多态是实现将不同类型和类的对象进行同样对待的特性——不需要指定对象属于哪个类就能调用方法

封装:对象可以讲它们内部状态隐藏(或封装)起来。在一些语言中,这意味着对象的状态(特性)只对自己的方法可用。在Python中,所有的特性都是公开可用的

继承:一个类可以是一个或者多个类的子类。子类从超类继承所有方法。

接口和内省:程序员可以靠多态调用自己想要的方法。不过如果想哟知道对象到底有什么方法和特性,可以用getattr等函数

面向对象设计

Python基础教程读书笔记(第7章—第8章:更加抽象;异常)_第1张图片

 

第八章:异常

1:什么是异常——Python用异常对象(exception object)来表示异常情况。遇到错误后,会引发异常。如果异常对象并未被处理或捕捉,程序就会用所谓的回溯(Traceback,一种错误信息)终止执行。事实上,每个异常都是一些类的实例,这些实例可以被引发,并且可以用很多种方法进行捕捉,使得程序可以捉住错误并且对其进行处理,而不是让整个程序失败

2:按自己的方式出错——如何引发异常,甚至创建自己的异常类型

1)raise 语句:为了引发异常,可以使用一个类(应该是Exception的子类)或者实例参数调用raise语句。使用类时,程序会自动创建实例。内建的异常有很多,都在exceptions模块中,可以用dir函数列表模块的内容
Python基础教程读书笔记(第7章—第8章:更加抽象;异常)_第2张图片

2)自定义异常类:class SomeCustomException(Exception):pass

3:捕捉异常:异常最有意思的地方就是可以处理它们(通常叫诱捕或捕捉异常)。这个功能使用try/except实现

try:
    x=input('Enter the first number: ')
    y=input('Enter the second number: ')
    print(int(x)/int(y))
except ZeroDivisionError as err :
    print('The second number can\'t be zero!')

注意:如果没有捕捉到异常,它会被“传播”到调用的函数中。如果那里依然没有捕获,这写异常就会“浮”到程序的最顶层。也就是说你可以捕捉到在其他人的函数中所引发的异常

如果捕捉到了异常,但是又想重新引发它(也就是说要传递异常),那么可以调用不带参数的raise(还能捕捉到异常时显式地提高具体异常)。举例来说,一个能“屏蔽”ZeroDivisionError(除0错误)的计算器类。如果整个行为被激活,那么计算器就会打印错误信息,而不是让异常传播。如果在与用户进行交互的过程中使用,就很有用了,但是如果是在程序内部使用,引发异常会更好。

>>> class MuffledCalculator:
    muffled = False
    def calc(self,expr):
        try:
            return eval(expr)
        except ZeroDivisionError:
            if self.muffled:
                print('Division by zero is illegal')
            else:
                raise

            
>>> calculator = MuffledCalculator()
>>> calculator.calc('10/2')
5.0
#示例:分别打开和关闭了屏蔽
>>> calculator.calc('10/0')
Traceback (most recent call last):
  File "<pyshell#154>", line 1, in <module>
    calculator.calc('10/0')
  File "<pyshell#151>", line 5, in calc
    return eval(expr)
  File "<string>", line 1, in <module>
ZeroDivisionError: division by zero
>>> calculator.muffled = True
>>> calculator.calc('10/0')
Division by zero is illegal

注意:如果除零行为发生而屏蔽机制被打开,那么calc方法会(隐式地)返回None。换句话说,如果打开了屏蔽机制,那么就不应该依赖返回值

4:不止一个except子句

try:
    x=input('Enter the first number: ')
    y=input('Enter the second number: ')
    print(x/y)
except ZeroDivisionError :
    print('The second number can\'t be zero!')
except TypeError:
    print('That wasn\'t a number,was it?')

应该注意到,异常处理不会搞乱原来的代码,而增加一堆if语句检查可能的错误情况会让代码相当难读

5:用一个块捕捉两个异常:

try:
    x=input('Enter the first number: ')
    y=input('Enter the second number: ')
    print(x/y)
except (ZeroDivisionError,TypeError,NameError): #注意不能没有括号,因为是元组
    print('......')

6:捕捉对象:
如果希望在except子句中访问异常对象本身,可以使用两个参数(注意,就算要捕捉多个异常,也只需向except子句提供一个参数——一个元组)。

try:
    x=input('Enter the first number: ')
    y=input('Enter the second number: ')
    print(x/y)
except (ZeroDivisionError,TypeError) as err:
    print(err)

7:真正的全捕捉
就算程序处理了好几种类型的异常,那有些异常还是会从眼皮底下溜走。比如上面的除法程序,在提示符下直接回车,不输入任何东西,会引发异常,这个异常逃过了try/except语句的检查——很正常。如果想捕捉所有异常,则在except子句中忽略所有的异常类:

try:
    x=input('Enter the first number: ')
    y=input('Enter the second number: ')
    print(x/y)
except :
    print('Something wrong happend')

注意:这样捕捉所有异常是危险的,因为它会隐藏所有程序员未想到并且未做好准备处理的错误。使用except Exception,e会更好,或者对异常对象e进行一些检查

8:万事大吉
有些情况,一些坏事发生时执行一段代码是很有用的,可以像对条件和循环语句那样,给try/except语句加个else子句:

while True:    
    try:
        x=input('Enter the first number: ')
        y=input('Enter the second number: ')
        print(int(x)/int(y))
    except Exception as e :
print(e)
print('Invalid input,please try again') else: break

9:最后——Finally子句,它可以用来在可能的异常后进行清理。它和try子句联合使用

10:异常和函数
异常和函数能自然地一起工作。如果异常在函数内引发而不被处理,它就会传播至(浮到)函数调用的地方。如果那里也没有处理异常,它就会继续传播,一直到达主程序(全局作用域)。如果还没有异常处理程序,程序就会带着堆栈跟踪中止

11:异常之禅
有时,条件语句可以实现和异常处理同样的功能,但是条件语句可能在自然性和可读性上差些。而从另一方面来看,某些程序中使用if/else实现会比使用try/except要好。

假设有个字典,我们希望打印出存储在特定的键下面的值。如果该键不存在,则说明也不做,代码可能这样:

def describePerson(person):
    print('Description of', person['name'])
    print('Age', person['age'])
    if 'occupation' in person:
        print('Occupation:', persion['occupation'])

代码很直观,但效率不高(此处效率其实微乎其微)。另外一种方案:

def describePerson(person):
    print('Description of', person['name'])
    print('Age', person['age'])
    try:
        print('Occupation: ' + person['occupation'])
    except KeyError:pass

这个程序假定‘occupation’键存在。如果的确存在,取出它的值打印即可——不用额外检查它是否真的存在。如果不存在,则会引发KeyError异常,而被except子句捕捉到
try/except 语句在Python中的表现可以用这样一句话来解释:“请求宽恕易于请求许可”

小结
异常对象——异常情况(如发生错误)可以用异常对象表示。它们可以用几种方法处理,忽略的话,程序就会中止

警告——类似异常,但是(一般来说)仅仅打印错误信息

引发异常——可以使用raise语句引发异常。它接受异常类或异常实例作为参数。还能提供两个参数(异常和错误信息)。如果在except子句中就不要使用参数调用raise,它会“重新引发”该字句捕捉到的异常

自定义异常类——用继承Exception类的方法可以创建自己的异常类

捕捉异常——try/except 子句捕捉异常。如果except子句中部特别指定异常类,那么所有的异常都会被捕捉。异常可以放在元组中以实现多个异常的指定。如果给exceptexcept提供两个参数,第2个参数就会绑定到异常对象上。同样,try/except还可以包含多个except子句

 else子句——如果try块中没有引发异常,else子句就会被执行

finally——如果需要确保某些代码不管是否有异常引发都要执行(如清理代码),可以放在finally子句中

异常和函数——在函数中引发异常时,它就会被传播到函数调用的地方(对于方法也是一样)

 

你可能感兴趣的:(python)