对象魔法
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秒内读完并理解。对于其余的方法,尽可能将其篇幅控制在一页或一屏内。