对象魔法

1.多态

多态指的是能够同样地对待不同类型和类的对象,既无需知道对象属于哪个类就可调用其方法。

2.封装

对象可能隐藏(封装)其内部状态。在有些语言中,这意味着对象的状态(属性)只能通过其方法来访问。在Python中,所有的属性都是公有的,但直接访问对象的状态时程序员应谨慎行事,因为这可能在不经意间导致状态不一致。

3.继承

一个类可以是一个或多个类的子类,在这种情况下,子类将继承超类的所有方法。你可以指定多个超类,通过这样做可组合正交(独立且不相关)的功能。为此,一种常见的做法是使用一个核心超类以及一个或多个混合超类。

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

例如,你在窗外看到一只鸟,这只鸟就是“鸟类”的一个实例。鸟类是一个非常通用的(抽象)的类,它有多个子类:你看到的那只鸟可能属于子类“云雀”。你可以将“鸟类”视为由所有鸟组成的集合,而“云雀”是其一个资料集,一个类的对象为另一个类的对象的子集时,前者就是后者的子类,因此“云雀”为“鸟类”的子集,而“鸟类”为“云雀”的超类。

1.创建自定义类

 1 class Student(object):
 2 
 3     # 构造方法(构造器、构造子 - constructor)
 4     # 调用该方法的时候不是直接使用方法的名字而是使用类的名字
 5     def __init__(self, name='无名氏', age=20):# 属性
 6         # 给对象绑定属性
 7         self._name = name
 8         self._age = age
 9 
10     # 我们定义一个方法就代表对象可以接收这个消息
11     # 对象的方法的第一个参数都是统一写成self
12     # 它代表了接收消息的对象 - 对象.消息(参数)
13     def study(self, course):# 方法,学生的行为
14         print('%s正在学习%s' % (self._name, course))

2.属性,函数和方法

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

3.隐藏

默认情况下,可以从外部访问对象的属性。

有些程序员认为这没问题,但有些程序员认为这违反了封装原则。他们认为应该对外部完全隐藏对象的状态(即不能从外部访问它们)。为何他们的立场如此极端?由每个对象管理自己的属性还不够吗?为何要向外部隐藏属性?毕竟,如果能直接访问CloseObject(对象c所属的类)的属性name,就不需要创建方法setName了。

关键是其他程序员可能不知道(也不应知道)对象内部发生的情况。为避免这类问题,可以将属性定义为私有。私有属性不能从对象外部访问,而只能通过存取器方法(如set_name和get_name)来访问。

Python没有为私有属性提供直接的支持,而是要求程序员知道在什么情况下从外部修改属性是安全的。毕竟,你必须在知道如何使用对象之后才能使用它。然而,通过玩点小花招,可以获得类似于私有属性的效果。

要让方法或属性成为私有的(不能从外部访问),只需要让其名称以两个下划线打头即可。

虽然以两个下划线打头有点怪异,但这样的方法类似于其他语言中的标准私有方法,然而,幕后的处理手法并不标准:在类定义中,对所有以两个下划线打头的名称都进行转换,即在开头加上一个下划线和类名。

只要知道这种幕后处理手法,就能从类外访问私有方法,然而不应这样做。

总之,你无法禁止别人访问对象的私有方法和属性,但这种名称修改方式发出了强烈的信号,让他们不要这样做。

如果你不希望名称被修改,有想发出不要从外部修改属性或方法的新号,可用一个下划线打头。这虽然只是一种约定,但也有些作用。例如,from module import *不会导入以一个下划线打头的名称。

4.类的命名空间

下面的两条语句大致等价:

 def foo(x): return x * x

 foo = lambda x: x * x 

它们都创建一个返回参数平方的函数,并将这个函数关联到变量foo。可以在全局(模块)作用域内定义名称foo,也可以在函数或方法内定义。定义类时情况亦如此:在class语句中定义的代码都是在一个特殊的命名空间(类的命名空间)内执行的,而类的所有成员都可访问这个命名空间。类定义其实就是要执行的代码段,并非所有的Python程序员都知道这一点,但知道这一点很有帮助。例如,在类定义中,并非只能包含def语句。

5.指定超类

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

 

 1 class Filter(object):
 2     
 3     def __init__(self):
 4         self.blocked = []
 5     
 6     def filter(self,sequence):
 7         return [x for x in sequence if x not in self.blocked]
 8     
 9 
10 class SPAMFilter(Filter): # SPAMFilter是Fliter的子类
11     
12     def __init__(self): # 重写超类Filter的方法init
13         self.blocked = ['SPAM']

 

Filter是一个过滤序列的通用类。实际上,它不会过滤掉任何东西。

Filter类的用途在于可用作其他类(如将'SPAM'从序列中过滤掉的SPAMFilter类)的基类(超类)。

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

  ·以提供新定义的方式重写了Fliter类中方法init的定义。

  ·直接从Filter类继承了方法filter的定义,因此无需重新编写其定义。

第二点说明了继承很有用的原因:可以创建打量不同的过滤器类,它们都从Filter类派生而来,并且都使用已编写好的方法filter。这就是懒惰的好处。

6.继承

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

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

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

7.接口和内省

一般而言,你无需过于深入地研究对象,而只依赖于多态来调用所需的方法。然而,如果要确定对象包含哪些方法或属性,有一些函数可供你来完成这种工作。

8.抽象基类

使用模块 abc可创建抽象基类。抽象基类用于指定子类必须提供哪些功能,却不实现这些功能。

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

将相关的东西放在一起。如果一个函数操作一个全局变量,最好将它们作为一个类的属性和方法。

不要让对象之间过于亲密。方法应只关系其所属实例的属性,对于其他实力的状态,让它们自己去管理就好了。

慎用继承,尤其是多重继承。继承有时很有用,但在有些情况下可能带来不必要的复杂性。要正确地使用多重继承很难,要排除其中的bug更难。

保持简单。让方法短小紧凑。一般而言,应确保大多数方法都能在30秒内读完并理解。对于其余的方法,尽可能将其篇幅控制在一页或一屏内。