Python基础教程(第三版)(七)再谈抽象

菜鸡的学习笔记。

7.1 对象魔法

  • 多态:可对不同类型的对象执行相同的操作,但是操作将随对象所属的类型而异;
  • 封装:对外隐藏对象内部工作原理的细节;
  • 继承:可基于通用类创建出专用类。
    按作者的意思,多态最难懂,但也最有趣,就先讲多态。

## 7.1.1 多态 假如要实现查询物品价格的函数,但是有人用元组来存放(‘item’,price)数据,有人用字典来存放{‘item’: price}数据,还有人用别的来对象存放数据,那么在查询价格时,就要先判断对象的类型,再根据索引或者键值去获得价格。现在假设每种对象都有一种能查询价格的方法,那么就不用再去判断对象类型,只用调用方法即可,例如Object.get_price(),这就是多态(也有一定程度上封装)。

7.1.2 多态和方法

有很多函数都用了多态,例如+函数,repr()函数,等等。
关于鸭子类型。就是只要你会走得像鸭子,叫的像鸭子,就可以把你当成鸭子来处理,只要你有某种方法,就可以用这种方法去实现某种功能,而不管你是什么(自己的理解)。


##7.1.3 封装 主要理解封装和多态的区别。封装和多态很像,都是**抽象**。但是二者的区别是**多态让你无需知道对象所属的类就能调用其方法,而封装就让你无需知道对象的构造就能使用它。** 书中的🌰: 比如有两个相同类的对象,都有get_name(),set_name()的方法,但是都关联到了一个全局变量name,这样更改其中任何一个,另一个就会变;这就需要封装,把name**封装**到对象内部,让每个对象都有一个自己的name,这样就会互不影响。
##7.1.4 继承 让通用类衍生出专用类,并且不会丢失其中的必要方法。
#7.2 类 ##7.2.1 类到底是什么 类就是类型,对象是类的实例化。人是个类,你就是个对象。 **超类**和**子类**:如麻雀是鸟的子类,鸟是麻雀的超类。 子类可以增加新的方法,也可以重写超类的方法,比如鸟并不都吃肉,但是老鹰会吃肉,可以给子类老鹰新加方法eat_meat();又如鸟有方法fly(),但是子类企鹅不会飞,就应该重写fly(),让fly()起不到什么作用。
##7.2.2 创建自定义类 和JAVA类似。别忘了**self**,创建对象似乎不需要new。
##7.2.3 属性、函数和方法 主要就是理解[self](https://www.cnblogs.com/jessonluo/p/4717140.html)
##7.2.4 再谈隐藏 ``` >>>c.name 'Sam' >>>c.name = 'Tom' >>>c.get_name() 'Tom' ``` 上面的代码不通过set_name()方法直接修改了变量。有的程序员声称这样违背了封装原则,他们认为应该对外完全隐藏对象的状态。为什么他们会如此极端?直接访问name不就好了,为什么非要用set_name,get_name呢? > 关键是其他程序猿可能不知道(也不应该知道)对象内部发生的情况。例如,ClosedObject可能在对象修改其名称时向管理员发送电子邮件。这种功能可能包含在方法set_name中。但如果直接设置c.name,结果将如何呢?什么都不会发生——根本不会发送电子邮件。为避免这类问题,可将属性定义为私有。私有属性不能从对象外部访问,而只能通过存取器方法(如get_name和set_name)来访问。

可以用两个下划线让方法或者属性变为私有。
这种方法是变相的变为私有,只是对其名称做了转换,在开头加个下划线和类名即可访问,但是不推荐。

>>>s = Secretive()
>>>s.__inaccessible()  # 会报错
s._Secretive__inaccessible()  #不会报错

## 7.2.5 类的命名空间 额外的知识——下面两条语句等价: ``` def foo(x): return x * x foo = lambda x: x * x ``` 进入正题: ``` class MemberCounter: members = 0 def init(self): MemberCounter.members += 1 m1 = MemberCounter() m2 = MemberCounter() m1.init() print(MemberCounter.members) m2.init() print(MemberCounter.members) ``` 结果:![](https://upload-images.jianshu.io/upload_images/17108100-1209ca3658ab1395.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) >上述代码在类作用域定义了一个变量,所有的成员(实例)都可访问它。

自己体会一下,不难理解,但要注意,定义init()时,写成members +=1 会报错,必须要加上类名。
有个特殊情况:

m1.members = 'Two'
print(m1.members)
print(m2.members)

结果:
相当于m1写入了一个新属性,覆盖了类级变量


## 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):
def init(self):
self.blocked = ['SPAM']

f = Filter()
f.init()
print(f.filter([1, 2, 3]))

s = SPAMFilter()
s.init()
print(s.filter(['SPAM', 1, 2, 3]))

结果:
![](https://upload-images.jianshu.io/upload_images/17108100-02123c5d85b0c81a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
- 对于超类中的函数可以重写,如例子中的init;
- 可以从超类中继承方法,如filter,不需要再次定义,提高了效率。

<hr>
##7.2.7 深入讨论继承

print(issubclass(SPAMFilter, Filter))
print(SPAMFilter.bases)
print(isinstance(s, SPAMFilter))
print(isinstance(s, Filter))

结果:![](https://upload-images.jianshu.io/upload_images/17108100-8342d6b0efbb112c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
- 判断一个类是否是另一个类的子类,可以用方法issubclass;
- 想要知道某个类的基类,访问它的特殊属性__bases__;
- 想要知道某个对象是否是某个类的实例,用方法isinstance;如果一个对象是某个特定的类的实例,那么也是该类的超类的实例。
## 7.2.8 多个超类
即多重继承,在圆括号中增加要继承的类即可。
**注意**:如果继承的类中有方法重名,那么圆括号中前面的类的方法会覆盖后面的类的方法;
                &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;慎用超类,容易带来Bug。
<hr>
## 7.2.9 接口和内省
暂时没有理解什么是接口、内省。
等以后结合java的接口理解一下。
掌握两种方法
hasattr(tc, 'talk')
callable(getattr(tc, 'talk', None))
- 第一种检查某个对象是否有某个属性,返回True或者False;
- 第二种getattr,可以在属性不存在时设置返回的值。

<hr>
## 7.2.10 抽象基类
**使用模块abc可以创建抽象基类。抽象基类用于指定子类必须提供哪些功能,却不实现这些功能。**
书中的这段话理解的还不透彻,先记录下来:
>然而,有比手工检查各个方法更好的选择。在历史上的大部分时间内,Python几乎都只依赖于鸭子类型,即假设所有对象都能完成其工作,同时偶尔使用hasattr来检查所需的方法是否存在。很多其他语言(如JAVA和Go)都采用显式指定接口的理念,而有些第三方模块提供了这种理念的各种实现。最终,Python通过引入模块abc提供了官方解决方案。这个模块为所谓的抽象基类提供了支持。一般而言,抽象类是不能(至少是不应该)实例化的类,其职责是定义子类应实现的一组抽象方法。

class Talker(ABC):
@abstractmethod
def talk(self):
pass

上面的类是抽象基类ABC派生出的抽象子类,是不能够实例化的,它的作用只是为了规定其子类必须有talk方法。
**实例化 Talker类会报错**:
![](https://upload-images.jianshu.io/upload_images/17108100-67eb0a38380cf835.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
如果定义这么一个子类:

class Knigget(Talker):
pass

由于没有重写方法talk,这个子类也是抽象类,也是无法实例化的。
如果重写了方法talk:

class Knigget(Talker):
def talk(self):
print("yeah!")

现在就可以实例化。从另一个角度说,如果一个对象确实是Talker对象,那么它一定有方法talk。

k = Knigget()
k.talk()
print(isinstance(k, Talker))

结果:![](https://upload-images.jianshu.io/upload_images/17108100-3b829fb86539c9fa.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
下图中Herring虽然有talk方法,但是isinstance判False。用Talker派生可以解决问题。如果是引用的类,无法自己定义,可以用register。但是**regiser会把没有talk方法的类也弄成Talker的子类**。
书上说,只要实现了方法talk,即使不是Talker的子类,依然能够通过类型检查。(这句话不明白是啥意思,在register之前isinstance的结果明明是False)

class Herring:
def talk(self):
print("em...")

h = Herring()
h.talk()
print(isinstance(h, Talker))
Talker.register(Herring)
print(isinstance(h, Talker))

结果:![](https://upload-images.jianshu.io/upload_images/17108100-904d5fdb333e9a6a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
<hr>
#7.3 关于面向对象设计的一些思考
> - 将相关的东西放在一起。如果一个函数操作一个全局变量,最好将它们作为一个类的属性和方法;
>- 不要让对象之间过于亲密。方法应只关心其所属实例的属性,对于其他实例的状态,让它们自己去管理就好了;
>- 慎用继承,尤其是多态继承。继承有时很有用,但在有些情况下可能带来不必要的复杂性。要正确地使用多重继承很难,要排除其中的Bug更难。
>- 保持简单,让方法短小紧凑。一般而言,应确保大多数方法都能在30秒内读完并理解。对于其余的方法,尽可能将其篇幅控制在一页或一屏内。

>确定哪些类以及这些类应该包含哪些方法时,尝试这样做:
>1. 将有关问题的描述(程序需要做什么)记录下来,并给所有的名词、动词、形容词加上标记;
>2. 在名词中找出可能的类;
>3. 在动词中找出可能的方法;
>4. 在形容词中找出可能的属性;
>5. 将找出的方法和属性分配给个各类。

<hr>
[Python基础教程(第三版)(八)异常](https://www.cnblogs.com/xmusxy/p/10914372.html)
posted @ 2019-05-23 20:37  深夜饿狗  阅读(264)  评论(0编辑  收藏  举报