打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
第七章:更加抽象
前几章介绍了Python主要的内建对象类型(数字、字符串、列表、元组和字典),以及内建函数和标准库的用法,还有自定义函数的方式。本章主要介绍创建自己的对象,以及多态、封装、方法、特性、超类以及继承的概念。
一、对象的魔力
面向对象程序设计中的术语对象(object)基本上可以看做数据(特性)以及由一系列可以存取、操作这些数据的方法所组成的集合。对象的优点包括以下方面:
多态(polymorphism):意味着可以对不同类的对象使用同样的操作,它们会像被施了魔法一样的工作。
封装(encapsulation):对外部世界隐藏对象的工作细节。
继承(inheritance):以普通的类为基础建立专门的类对象。
1、多态
术语多态来自希腊语,意思是“有多种形式”。多态意味着就算不知道变量所引用的对象类型是什么,还是能对它进行操作,而它也会根据对象(或类)类型不同而表现出不同的行为。
1)多态和方法
绑定到对象特性上面的函数称为方法(method):
如果要知道字符e在变量x中出现多少次,而不管x是字符串还是列表,可以使用count函数
这里只需要知道x有个叫做count的方法,带有一个字符作为参数,并且返回整数值就够了。
2)多态的多种形式
repr函数是多态特性的代表之一--可以对任何东西使用
很多函数和运算符都是多态的--你写的绝大多数程序可能都是,即便你并非有意这样。只要使用多态函数和运算符,多态就“消除“了。事实上,唯一能够毁掉多态的就是使用函数显示地检查类型,比如type、isinstance以及issubclass函数等。如果可能,应该尽量避免使用这些毁掉多态的方式。
注:这里所讨论的多态的形式是Python式编程的核心,也是被称为”鸭子类型“的东西。
2、封装
封装是对全局作用域中其它区域隐藏多余信息的原则。封装和多态都是抽象的原则,它们都会帮助处理程序组件而不用过多关心多余细节,就像函数做得一样。
但是封装并不等同于多态。多态可以让用户对于不知道什么类(或对象类型)的对象进行方法调用,而封装是可以不用关心对象是如何构建的而直接进行使用。
3、继承
继承是另一个行为,程序员不想把同一段代码写好几次。如果有了一个类,而又想建立一个非常类似的,新的类可能只是添加几个方法,在编写新类时,又不想把旧累的代码全部复制过去,可以用继承的方法,程序会自动从旧类调用方法。
二、类和类型
1、类
类是一种对象,所有的对象都属于某一个类,称为类的实例(instance)。
比如,百灵鸟类是鸟类的子类,而鸟类则是百灵鸟类的超类。
2、创建自己的类
这个例子包含3个方法定义,除了它们是写在class语句里面外,一切都像函数定义。Person当然是类的名字,class语句会在函数定义的地方创建自己的命名空间。下面看看示例:
例子一目了然,应该能说明self的用处了。在调用foo的setName和greet函数时,foo自动将自己作为第一个参数传入函数中——因此形象地命名为self。对于这个变量,每个人可能都会有自己的叫法,但是因为它总是对象自身,所以习惯上总是叫做self。
和之前一样,特性是可以在外部访问的
提示:如果知道foo是Person的实例的话,那么可以把foo.greet()看作Person.greet(foo)方便的简写。
3、特性、函数和方法
方法(更专业一点可以称为绑定方法)将它们的第一个参数绑定到所属的实例上,因此这个参数可以不必提供。所以可以将特性绑定到一个普通函数上。
注意:self函数并不取决于调用方法的方式,目前使用的是实例调用方法,可以随意使用引用同一个方法的其他变量:
尽管最后一个方法调用看起来与函数调用十分相似,但是变量birdsong引用绑定方法bird.sing上,也就意味着这还是对self参数的访问(也就是说,它仍旧绑定到类的相同实例上)。
为了让方法或者特性变为私有(从外部无法访问),只要在它的名字前面加上双下划线即可:
现在__inaccessible从外界是无法访问的,而在类内部还能使用(比如从accessible)访问:
类的所有定义中,所有以双下划线开始的名字都被“翻译”成前面加上单下划线和类名的形式。
实际上还是能在类外访问这些私有方法
如果不需要使用这种方法但是又想让其他对象不要访问内部数据,那么可以使用单下划线。这不过是个习惯,但的确有实际效果。
4、类的命名空间
下面的两个语句(几乎)等价:
两者都创建了返回参数平方的函数,而且都将变量foo绑定到函数上。变量foo可以在全局(模块)范围进行定义,也可处于局部的函数或方法内。定义类时,同样的事情也会发生,所有位于class语句中的代码都在特殊的命名空间中执行——类命名空间(class namespace)。这个命名空间可由类内所有成员访问。并不是所有Python程序员都知道类的定义其实就是执行代码块,这一点非常有用,比如在类的定义区并不只限于使用del语句:
再看另一个:
上面的代码中,在类作用域内定义了一个可供所有成员(实例)访问的变量,用来计算类的成员数量。注意init用来初始化所有实例
就像方法一样,类作用域内的变量也可以被所有实例访问:
如果在实例中重绑定members特性会怎样?
新members值被写到了m1的特性中,屏蔽了类范围内的变量。这跟函数内的局部和全局变量的行为十分类似。
5、指定超类
子类可以扩展为超类。将其他类名写在class语句后的圆括号内可以指定超类:
SPAMFilter是Filter的子类,子类中定义的init重写Filter超类中的init方法。
Filter是个用于过滤序列的通用类,事实上它不能过滤任何东西:
Filter类的用处在于它可以用作其他类的基类(超类),比如SPAMFilter类,可以将序列中的“SPAM”过滤出去。
注意SPAMFilter定义的两个要点
这里用提供新定义的方式重写了Filter的init定义
filter方法的定义是从Filter类中拿过来(继承)的,所以不用重写它的定义。
第二个要点揭示了继承的用处:可以写一大堆不同的过滤类,全都从Filter继承,每一个都可以使用已经实现的filter方法。
6、调查继承
如果想要查看一个类是否是另一个的子类,可以使用内建的issubclass函数:
如果想要知道已知类的基类,可以直接使用它的特殊特性__bases__:
同样,还能用使用isinstance方法检查一个对象是否是一个类的实例:
注意:使用isinstance并不是个好习惯,使用多态会更好一些。
可以看到,s是SPAMFilter类的(直接)成员,但是它也是Filter类的间接成员,因为SPAMFilter是Filter的子类。另外一种说法就是SPAMFilter类就是Filter类。
如果只想知道一个对象属于哪个类,可以使用__class__特性:
注:如果使用__metaclass__=type或从object继承的方式来定义新式类,那么可以使用type(s)查看实例的类。
7、多个超类
子类(TalkingCalculator)自己不做任何事,它从自己的超类继承所有的行为。它从Calculator类那里继承calculate方法,从Talker类那里继承talk方法,这样它就成了会说话的计算器。
这种行为成为多重继承(multiple inheritance),是个非常有用的工具。但除非读者特别熟悉多重继承,否则应该尽量避免使用,因为有些时候会出现不可预见的麻烦。
8、接口和内省
“接口”的概念与多态有关。一般来说只需要让对象符合当前的接口(换句话说就是实现当前方法),但是还可以更灵活一些。
检查talk特性是否可调用
注:callable函数在Python3.0中已不再可用。可以使用hasattr(x,'__call__')来代替callable(x)。
这段代码使用了getattr函数,而没有在if语句内使用hasattr函数直接访问特性,getattr函数允许提供默认值,以便在特性不存在时使用,然后对返回的对象使用callable函数。
与getattr相对应的函数是setattr,可以用来设置对象的特性。
三、一些关于面向对象设计的思考
1、将属于一类的对象放在一起,如果一个函数操纵一个全局变量,那么两者最好都在类内作为特性和方法出现。
2、不要让对象过于亲密。方法应该只关心自己实例的特性。让其它实例管理自己的状态。
3、要小心继承,尤其是多重继承。继承的机制有时很有用,但也会在某些情况下让事情变得过于复杂。多重继承难以正确使用,更难以调试。
4、简单就好。让你的方法小巧。一般来说,多数方法都应该在30秒内被读完,尽量将代码行数控制在一页或者一屏之内。
当考虑需要什么类型以及类要有什么方法时,应该尝试下面的方法:
1、写下问题的描述(程序要做什么?),把所有名词、动词和形容词加下划线。
2、对于所有名词,用作可能的类。
3、对于所有动词,用作可能的方法。
4、对于所有形容词,用作可能的特性。
5、把所有方法和特性分配到类。
以上是面向对象模型的草图,还可以考虑类和对象之间的关系(比如继承或协作)以及他们的作用,可以用以下步骤精炼模型:
1、写下一系列的使用实例--也就是程序应用时的场景,试着包括所有的功能。
2、一步步考虑每个使用实例,保证模型包括所有需要的东西。如果有些遗漏的话就添加进来。如果某处不太正确则改正,直到满意为止
四、本章小结
对象:对象包括特性和方法。特性只是作为对象的一部分的变量,方法是存储在对象内的函数。方法和其它函数的区别在于方法总是将对象作为自己的第一个参数,这个参数一般称为self。
类:类代表对象的集合(或一类对象),每个对象(实例)都有一个类。类的主要任务是定义它的实例会使用到的方法。
多态:多态是实现将不同类型和类的对象进行同样对待的特性--不需要知道对象属于哪个类就能调用方法。
封装:对象可以将它们的内部状态隐藏(或封装)起来。在一些语言中,这意味着对象的状态(特性)只对自己的方法可用。
继承:一个类可以使一个或者多个子类的子类。子类从超类继承所有方法。可以使用多个超类,这个特性可以用来组成功能的正交部分。
接口和内省:一般来说,对于对象不用探讨过深。程序员可以靠多态调用自己需要的方法。不过如果想要知道对象到底有什么方法和特性,有些函数可以帮助完成这项工作。
本章的新函数
函数 描述
callable(object) 确定对象是否可调用
getattr(object,name[,default]) 确定特性的值,可选择提供默认值
hasattr(object,name) 确定对象是否有给定的特性
isinstance(object,class) 确定对象是否是类的实例
issubclass(A,B) 确定A是否为B的子类
random.choice(sequence) 从非空序列中随机选择元素
setattr(object,name,value) 设定对象的给定特性为value
type(object) 返回对象的类型
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
Python3学习笔记(三)
自考本科java学习第二章
Java 面试参考指南( 一 )
学习Java语言-接口和继承-继承
ES6的Class
一篇文章带你搞懂Python中的类
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服