对象基本可以看做数据(特性)以及由一系列可以存取,操作这些数据的方法所组成的集合
多态,不同类的对象使用同样的操作
封装,对外部世界隐藏对象的工作细节
继承,以普通的类为基础建立专门的类对象
意味着,就算不知道变量所引用的对象类型是什么,还是能对它进行操作,而它也会根据对象(或类)类型的不同而表现出不同的行为
绑定到对象特性上面的函数成为方法,如count, '+'
标准库random中包含choice函数可以从序列中随机选出元素,给变量赋值
>>> from random import choice
>>> x = choice(['Hello, world!', [1,2.'e','e',4] }
>>> x.count('e') 2
说明列表胜出,关键点在于不需要检测类型,只需要知道x有个叫做count的方法,带有一个字符作为参数,并返回整数值
绑定到对象特性上面的函数称为方法
多态的多种形式
任何不知道对象到底是什么类型,但是又要对对象‘做点什么’的时候都会用到多态
不仅限于方法,很多内建运算符和函数都有多态的性质,如加运算符
对全局作用域中其他区域隐藏多余信息的原则
多态是可以让用户对于不知道是什么类(或对象类型)的对象进行方法调用,而封装是可以不用关心对象是如何构建的而直接进行使用
所有对象都属于某一个类,称为类的实例
当一个对象所属的类是另外一个对象所属类的子集时,前者被称为后者的子类,相反为超类
__metaclass__ = type #确定使用新式类
class Person:
def setName(self, name):
self.name = name
def getName(self):
return self.name
def greet(self):
print "Hello, world! I'm %s." %self.name
Person是类的名字,class语句会在函数定义的地方创建自己的命名空间,self是对于对象自身的引用
>>> foo = Person()
>>> bar = Person()
>>> foo.setName('Luke Skywalker')
>>> bar.setName('Anakin Skywaylker')
>>> 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.
方法(绑定方法)将他们第一个参数绑定到所属的实例上,因此这个参数就可以不必提供,所以可以将特性绑定到一个普通函数上,这样就不会有特殊的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参数并不取决于调用方法的方式,目前使用的是实例调用方法,可以随意使用引用同一个方法的其他变量
>>> class Bird:
song = 'Squaawk!"
def sing(self):
print self.song
>>> bird = Bird()
>>> bird.sing() Squaawk
>>> birdsong = bird.sing
>>> birdsong() Squaawk
私有化
默认情况下,程序可以从外部访问一个对象的特性,再次使用前面讨论过的有关封装的例子:
>>> c.name 'Sir Lancelot'
>>> c.name = 'Sir Gumby'
>>> c.getName() 'Sir Gumby'
有些程序员认为这样做是可以的,但有些(如SmallTalk之父,SmallTalk的对象特性只允许由同一个对象的方法访问)觉得这样破坏了封装的原则
他们认为对象的状态对于外部应该是完全隐藏(不可访问)的,因为
其他程序员可能不知道(可能也不应该知道)你的对象内部的具体操作,例如ClosedObject可能会在其他对象更改自己名字的时候,给一些
管理员发送邮件消息,这应该是setName方法的一部分,但是如果直接用c.name设定名字会发生什么?什么都没发生,Email也没有发出去,为了避免这类事件,应该使用私有特性,这是外部对象无法访问,但getName和setName等访问器能够访问的特性
为了让方法或者特性变为私有(从外部无法访问),只要在它的名字前面加上双下划线即可:
class Secretive:
def __inaccessible(self):
print "Bet you can't see me..."
def accessible(self):
print "The secret message is:"
self.__iaccessible()
现在__inaccessible从外界是无法访问的,而在类内部还能使用(比如从accessible)访问:
>>> s = Secretive()
>>> s.__inaccessible() 报错AttributeError: Secretive instance has no attribute '__inaccessible'
>>> s.accessible()
The secret message is:
Bet you can't see me...
尽管上下划线有些奇怪,但是看起来像是其他语言中的标准的私有方法,真正发生的事情才是不标准的。
类的内部定义中,所有以上下划线开始的名字都被“翻译:成前面加上单下划线和类名的形式
>>> Secretive._Secretive__inaccessible
在了解这些幕后之后,实际上还是能在类外访问这些私有方法,尽管不应该这么做:
>>> s._Secretice__inaccessible()
Bet you can't see me..
简而言之,确保其他人不会访问对象的方法和特性是不可能的,但是这类”名称变化术“就是他们不应该访问这些函数或特性的信号
如果不需要使用这种方法但是又想让其他对象不要访问内部数据,那么可以使用单下划线,
这不过是个习惯,但有实际效果,例如前面有下划线的名字都不会被带星号的imports语句(from module import*)导入
下面两个语句几乎等价
def foo(x): return x*x
foo = lambda x: x*x
两者都创建了返回参数平方的函数,而且都将变量foo绑定到函数上。
变量foo可以在全局(模块)范围进行定义,也可处于局部的函数或方法内,
定义类时,同样的事情也会发生,所有位于class语句中的代码都在特殊的命名空间中执行----类命名空间。
这个命名空间可由类内所有成员访问,并不是所有python程序员都知道类的定义其实就是执行代码块,比如,在类的定义区并不只限使用def语句:
>>> class C:
print 'Class C being defined...'
Class C being defined...
>>>
下面例子,
class MemberCounter:
menbers = 0
def init(self):
MemberCounter.members += 1
>>> m1 = MemberCounter()
>>> m1.init()
>>> MemberCounter.members 1
>>> m2 = MemberCounter()
>>> m2.init()
>>> MemberCounter.memberd 2
上面代码中,在类作用域内定义了一个可供所有成员)实例)访问的变量,用来计算类的成员数量。注意init用来初始化所有实例
类作用域内的变量也可以被所有实例访问:
>>> m1.members 2
>>> m2.members 2
重绑定members特性
>>> m1.members = 'Two'
>>> m1.members 'Two'
>>> m2.members 2
新numbers值被写到了m1的特性中,屏蔽了类范围内的变量
子类可以扩展超类的定义,将其他类名写在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']
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']
SPAMFilter定义的两个要点:
这里用提供新定义的方式重写了Filter的init定义
filter方法的定义是从Filter类中拿过来(继承)的,所以不用重写它的定义
如果想要查看一个类是否是另一个的子类,可以使用内建的issubclass函数
>>> issubclass(SPAMFilter, Filter) True
>>> issubclass(Filter, SPAMFilter) False
如果想要知道已知类的基类(们),可以直接使用它的特殊特性__bases__
>>> SPAMFilter.__bases__
{
>>> Filter.__bases__ {}
同样还能用使用isinstance方法检查一个对象是否是一个类的实例:
>>> s = SPAMFilter()
>>> isinstance(s, SPAMFilter) True
>>> ininstance(s, Filter) True
>>> isinstance(s, str) False
可以看到s是SPAMFilter类的(直接)成员,但他也是Filter类的间接成员,因为SPAMFilter是Filter的子类
另外一种说法就是SPAMFilter类就是Filters类,可以从前一个例子中看到,
isinstance对于类型也起作用,比如字符串类型str
如果只想知道一个对象属于哪个类,可以使用__class__特性:
>>> s.__class__
如果使用__metaclass__=type或从object继承的方式来定义新式类,那么可以使用type(s)查看实例的类
class Calculator:
def calculate(self, expression):
self.value = eva(expression)
class Talker:
def talk(self):
print 'Hi, my value is '. self.value
class TalkingCalculator(Calculator, Talker):
pass
子类(TalkingCalculator)自己不做任何事,它从超类继承所有行为,这样就变成了会说话的计算器
>>> tc = TalkingCalculator()
>>> tc.calculate('1+2*3')
>>> tc.talk() Hi, my value is 7
这种行为称为多重继承,应尽量避免使用除非特别熟悉
如果一个方法从多个超类继承(即有两个具有相同名字的不同方法),必须注意超类的顺序:
先继承的类中的方法会重写后继承的类中的方法(使其不可访问)
如果超类们共享一个超类,那么在查找给定方法或者特性时访问超类的顺序成为MRO(Method Resolution Order,方法判定顺序)
接口的概念与多态有关,在处理多态对象时,只要关心它的接口(或称协议)即可,也就是公开的方法和特性
在python中不用显示地指定对象必须包含哪些方法才能作为参数接受,例如,不用(像在Java中)显示地编写接口,可以在
使用对象的时候假定它可以实现你所要求的行为,如果不能实现的话,程序就会失败。
一般来说只需让对象符合当前的接口(实现当前方法),但是还可以更灵活一些
除了调用方法然后期待一切顺利之外,还可检查所需方法是否已经存在,如果不存在,就需要做其他事情
>>> hashttr(tc, 'talk') True
>>>hasattr(tc, 'fnord') False
上面的代码中,tc有个叫做talk的特性(包含一个方法),但是并没有fnord特性,如果需要话甚至还能检查talk特性是否可调用
>>> callable(getattr(tc, 'talk', None) True
>>> callable(getattr(tc, 'fnord', None) False
这段代码使用了getattr函数,而没有在if语句内使用hasattr函数直接访问特性,getattr函数允许提供默认值(本例中为None),以便在特性不存在时使用,然后对返回的对象使用callable函数
与getattr相对应的函数是setattr,可以用来设置对象的特性
>>> setattr(tc, 'name', 'Mr.Gumby')
>>> tc.name 'Mr.Gumby'
如果要查看对象内所有存储的值,可以使用__dict__特性
如果真的想要找到对象是由什么组成,可以看看inspect模块,
这是为哪些想要编写对象浏览器)以图形方式浏览python对象的程序)以及其他需要类似功能的程序的高级用户准备的