Python基础教程(第3版)》笔记:第7章 再谈抽象

编程入门 行业动态 更新时间:2024-10-06 23:20:21

Python基础教程(第3版)》笔记:第7章 再谈<a href=https://www.elefans.com/category/jswz/34/1765602.html style=抽象"/>

Python基础教程(第3版)》笔记:第7章 再谈抽象

Python基础教程(第3版)》笔记:第7章 再谈抽象

创建自定义对象(尤其是对象类型或类)是一个Python核心概念。下面列出了使用对象的最重要的好处。

  • 多态:可对不同类型的对象执行相同的操作,而这些操作就像“被施了魔法”一样能够
    正常运行。
  • 封装:对外部隐藏有关对象工作原理的细节。
  • 继承:可基于通用类创建出专用类。
7.1.1 多态

术语**多态(polymorphism)**源自希腊语,意思是“有多种形态”。这大致意味着即便你不知道变量指向的是哪种对象,也能够对其执行操作,且操作的行为将随对象所属的类型(类)而异。

7.1.2 多态和方法
7.1.3 封装

**封装(encapsulation)**指的是向外部隐藏不必要的细节。
但封装不同于多态。多态让你无需知道对象所属的类(对象的类型)就能调用其方法,而封装让你无需知道对象的构造就能使用它。听起来还是有点像?下面来看一个使用了多态但没有使用封装的示例。假设你有一个名为OpenObject的类(如何创建类将在本章后面介绍)。

7.2 类

7.2.1 类到底是什么

每个对象都属于特定的类,并被称为该类的实例。


注意: 在python中,约定使用单数并将首字母大写命名类


7.2.2 创建自定义类

class Person:def set_name(self, name):self.name = namedef get_name(self):return self.namedef greet(self):print("Hello, world! I'm {}.".format(self.name))

class语句创建独立的命名空间,用于在其中定义函数。self指向对象本身。

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

从外部访问这些属性。

>>> foo.name 
'Luke Skywalker' 
>>> bar.name = 'Yoda' 
>>> bar.greet() 
Hello, world! I'm Yoda. 
7.2.3 属性、函数和方法

实际上,方法和函数的区别表现在前一节提到的参数self上。方法(更准确地说是关联的方法)将其第一个参数关联到它所属的实例,因此无需提供这个参数。无疑可以将属性关联到一个普通函数,但这样就没有特殊的self参数了。

>>> class Class: def method(self): print('I have a self!') >>> def function(): print("I don't...") >>> instance = Class() 
>>> instance.method() 
I have a self! 
>>> instance.method = function #将属性关联到一个普通函数
>>> instance.method() 
I don't... 
将属性关联到一个普通函数

请注意,有没有参数self并不取决于是否以刚才使用的方式(如instance.method)调用方法。
实际上,完全可以让另一个变量指向同一个方法。

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

虽然最后一个方法调用看起来很像函数调用,但变量birdsong指向的是关联的方法bird.sing,这意味着它也能够访问参数self(即它也被关联到类的实例)。

7.2.4 再谈隐藏

可将属性定义为私有。私有属性不能从对象外部访问,而只能通过存取器方法(如get_name和set_name)来访问。
Python没有为私有属性提供直接的支持,而是要求程序员知道在什么情况下从外部修改属性是安全的。毕竟,你必须在知道如何使用对象之后才能使用它。然而,通过玩点小花招,可获得类似于私有属性的效果。
要让方法或属性成为私有的(不能从外部访问),只需让其名称以两个下划线打头即可。

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

现在从外部不能访问__inaccessible,但在类中(如accessible中)依然可以使用它。

7.2.5 类的命名空间

定义类时:在class语句中定义的代码都是在一个特殊的命名空间(类的命名空间)内执行的,而类的所有成员都可访问这个命名空间。类定义其实就是要执行的代码段,并非所有的Python程序员都知道这一点,但知道这一点很有帮助。例如,在类定义中,并非只能包含def语句。

class MemberCounter: members = 0 def init(self): MemberCounter.members += 1 
>>> m1 = MemberCounter() 
>>> m1.init() 
>>> MemberCounter.members 
1 
>>> m2 = MemberCounter() 
>>> m2.init() 
>>> MemberCounter.members 
2

上述代码在类作用域内定义了一个变量,所有的成员(实例)都可访问它,这里使用它来计算类实例的数量。注意到这里使用了init来初始化所有实例,第9章将把这个初始化过程自动化,也就是将init转换为合适的构造函数
每个实例都可访问这个类作用域内的变量,就像方法一样。

>>> m1.members 
2 
>>> m2.members 
2 

如果你在一个实例中给属性members赋值,结果将如何呢?

>>> m1.members = 'Two' 
>>> m1.members 
'Two' 
>>> m2.members 
2 

新值被写入m1的一个属性中,这个属性遮住了类级变量。这类似于第6章的旁注“遮盖的问题”所讨论的,函数中局部变量和全局变量之间的关系。

7.2.6 指定超类

要指定超类,可在class语句中的类名后加上超类名,并将其用圆括号括起

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类的用途在于可用作其他类(如将’SPAM’从序列中过滤掉的SPAMFilter类)的基类(超类)。

>>> s = SPAMFilter() 
>>> s.init() 
>>> s.filter(['SPAM', 'SPAM', 'SPAM', 'SPAM', 'eggs', 'bacon', 'SPAM']) 
['eggs', 'bacon'] 

请注意SPAMFilter类的定义中有两个要点。

  • 以提供新定义的方式重写了Filter类中方法init的定义。
  • 直接从Filter类继承了方法filter的定义,因此无需重新编写其定义。
    第二点说明了继承很有用的原因:可以创建大量不同的过滤器类,它们都从Filter类派生而来,并且都使用已编写好的方法filter。这就是懒惰的好处。
7.2.7 深入探讨继承

要确定一个类是否是另一个类的子类,可使用内置方法issubclass。

>>> issubclass(SPAMFilter, Filter)
True 
>>> issubclass(Filter, SPAMFilter)
False

如果你有一个类,并想知道它的基类,可访问其特殊属性__bases__。

>>> SPAMFilter.__bases__ 
(<class __main__.Filter at 0x171e40>,) 
>>> Filter.__bases__ 
(<class 'object'>,) 

同样,要确定对象是否是特定类的实例,可使用isinstance。

>>> s = SPAMFilter() 
>>> isinstance(s, SPAMFilter) 
True 
>>> isinstance(s, Filter) 
True 
>>> isinstance(s, str) 
False 

注意 使用isinstance通常不是良好的做法,依赖多态在任何情况下都是更好的选择。一个重要的例外情况是使用抽象基类和模块abc时。


如你所见,s是SPAMFilter类的(直接)实例,但它也是Filter类的间接实例,因为SPAMFilter是Filter的子类。换而言之,所有SPAMFilter对象都是Filter对象。从前一个示例可知,isinstance也可用于类型,如字符串类型(str)。
如果你要获悉对象属于哪个类,可使用属性__class__。



>>> s.__class__ 
<class __main__.SPAMFilter at 0x1707c0> 
7.2.8 多个超类

在前一节,你肯定注意到了一个有点奇怪的细节:复数形式的__bases__。前面说过,你可使用它来获悉类的基类,而基类可能有多个。为说明如何继承多个类,下面来创建几个类

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

子类TalkingCalculator本身无所作为,其所有的行为都是从超类那里继承的。关键是通过从Calculator那里继承calculate,并从Talker那里继承talk,它成了会说话的计算器。

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

这被称为多重继承,是一个功能强大的工具。然而,除非万不得已,否则应避免使用多重继承,因为在有些情况下,它可能带来意外的“并发症”。
使用多重继承时,有一点务必注意:如果多个超类以不同的方式实现了同一个方法(即有多个同名方法),必须在class语句中小心排列这些超类,因为位于前面的类的方法将覆盖位于后面的类的方法。因此,在前面的示例中,如果Calculator类包含方法talk,那么这个方法将覆盖Talker类的方法talk(导致它不可访问)。如果像下面这样反转超类的排列顺序:
class TalkingCalculator(Talker, Calculator): pass
将导致Talker的方法talk是可以访问的。多个超类的超类相同时,查找特定方法或属性时访问超类的顺序称为方法解析顺序(MRO),它使用的算法非常复杂。所幸其效果很好,你可能根本无需担心

7.2.9 接口和内省

接口这一概念与多态相关。处理多态对象时,你只关心其接口(协议)——对外暴露的方法和属性。

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

提供一些指南。

  • 将相关的东西放在一起。如果一个函数操作一个全局变量,最好将它们作为一个类的属性和方法。
  • 不要让对象之间过于亲密。方法应只关心其所属实例的属性,对于其他实例的状态,让它们自己去管理就好了。
  • 慎用继承,尤其是多重继承。继承有时很有用,但在有些情况下可能带来不必要的复杂性。要正确地使用多重继承很难,要排除其中的bug更难。
  • 保持简单。让方法短小紧凑。一般而言,应确保大多数方法都能在30秒内读完并理解。对于其余的方法,尽可能将其篇幅控制在一页或一屏内。
    确定需要哪些类以及这些类应包含哪些方法时,尝试像下面这样做。
    (1) 将有关问题的描述(程序需要做什么)记录下来,并给所有的名词、动词和形容词加上标记。
    (2) 在名词中找出可能的类。
    (3) 在动词中找出可能的方法。
    (4) 在形容词中找出可能的属性。
    (5) 将找出的方法和属性分配给各个类。
    有了面向对象模型的草图后,还需考虑类和对象之间的关系(如继承或协作)以及它们的职责。为进一步改进模型,可像下面这样做。
    (1) 记录(或设想)一系列用例,即使用程序的场景,并尽力确保这些用例涵盖了所有的功能。
    (2) 透彻而仔细地考虑每个场景,确保模型包含了所需的一切。如果有遗漏,就加上;如果有不太对的地方,就修改。不断地重复这个过程,直到对模型满意为止。

更多推荐

Python基础教程(第3版)》笔记:第7章 再谈抽象

本文发布于:2024-02-13 07:24:08,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1757692.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:抽象   再谈   基础教程   笔记   Python

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!