版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
第八章类与对象Python支持创建自己的对象,Python从设计之初就是一门面向对象语言,它提供了一些语言特性支持面向对象编程。创建对象是Python的核心概念,本章将介绍如何创建对象,以及多态、封装、方法和继承等概念。Python快乐学习班的同学结束函数乐高积木厅的创意学习后,导游带领他们来到对象动物园。在对象动物园,将为同学们呈现各种动物对象,同学们将在这里了解各种动物所属的类别,各种动物所拥有的技能,以及它们的技能继承自哪里等知识点。现在跟随Python快乐学习班的同学一起进入对象动物园观摩吧!第八章类与对象Python支持创建自己的对象,Pytho18.1理解面向对象8.1理解面向对象28.1.1面向对象编程Python是一门面向对象编程语言,对面向对象语言编码的过程就叫做面向对象编程。面向对象编程——ObjectOrientedProgramming,简称OOP,是一种程序设计思想。OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。面向对象的程序设计把计算机程序视为一组对象的集合,每个对象都可以接收其他对象发过来的消息,并处理这些消息,计算机程序的执行就是一系列消息在各个对象之间传递。在Python中,所有数据类型都被视为对象,也可以自定义对象。自定义的对象数据类型就是面向对象中的类(Class)的概念。8.1.1面向对象编程Python是一门面向对象编程语言38.1.2面向对象术语简介类(Class):用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。类变量(或属性):类变量在整个实例化的对象中是公用的。类变量定义在类中且在方法之外。类变量通常不作为实例变量使用。类变量也称作属性。数据成员:类变量或者实例变量用于处理类及其实例对象的相关的数据。方法重写:如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖(override),也称为方法的重写。实例变量:定义在方法中的变量,只作用于当前实例的类。多态(Polymorphism):对不同类的对象使用同样的操作。封装(Encapsulation):对外部世界隐藏对象的工作细节。8.1.2面向对象术语简介类(Class):用来描述具有4继承(Inheritance):即一个派生类(derivedclass)继承基类(baseclass)的字段和方法。继承也允许把一个派生类的对象作为一个基类对象对待。以普通的类为基础建立专门的类对象。实例化(Instance):创建一个类的实例,类的具体对象。方法:类中定义的函数。对象:通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法。Python中的类提供了面向对象编程的所有基本功能:类的继承机制允许多个基类,派生类可以覆盖基类中的任何方法,方法中可以调用基类中的同名方法。对象可以包含任意数量和类型的数据。继承(Inheritance):即一个派生类(derived58.2类的定义与使用8.2类的定义与使用68.2.1类的定义类定义的语法格式如下:classClassName(object):<statement-1>.<statement-N>Python中定义类使用class关键字,class后面紧跟类名。示例如下(my_class.py):classMyClass(object):i=123deff(self):return'helloworld'由代码可以看到,这里定义了一个名为MyClass的类。8.2.1类的定义类定义的语法格式如下:7在Python中,类名一般由以大写字母开头的单词命名,并且若是由多个单词组成的类名,那各个单词的首字母都大写。类名后面紧接着是(object),object表示该类是从哪个类继承的。通常,如果没有合适的继承类,就使用object类,这是所有类最终都会继承的类。类包含属性(相当于函数中的语句)和方法(类中的方法大体可以理解成第7章所学的函数)。在类中定义的方法的形式和函数差不多,但不称为函数,而称为方法。因为方法需要靠类对象去调用,而函数不需要。在Python中,类名一般由以大写字母开头的单词命名,并且若88.2.2类的使用classMyClass(object):i=123deff(self):return'helloworld'
use_class=MyClass()print('调用类的属性:',use_class.i)print('调用类的方法:',use_class.f())类的使用比函数调用多了几个操作,调用需要执行如下操作:use_class=MyClass()这步叫做类的实例化,即创建一个类的实例,此处得到的use_class这个变量称为类的具体对象。8.2.2类的使用classMyClass(objec9再看后面两行的调用:print(f'调用类的属性:{use_class.i}')print(f'调用类的方法:{use_class.f()}')这里第一行后面use_class.i部分的作用是调用类的属性,也就是前面所说的类变量。第二行后面use_class.f()部分的作用是调用类的方法。在类中定义方法的要求:在类中定义方法时,第一个参数必须是
self。除第一个参数之外,类的方法和普通函数没什么区别,如可以用默认参数、可变参数、关键字参数和命名关键字参数等。在类中调用方法的要求:要调用一个方法,在实例变量上直接调用即可。除了self不用传递,其他参数正常传入。类对象支持两种操作,属性引用和实例化。实例化方式上面已经介绍过,属性引用的标准语法格式如下:语法中,obj代表类对象,name代表属性。再看后面两行的调用:108.3深
入
类本节将深入介绍类的相关内容,如类的构造方法和访问权限。8.3深入类本节将深入介绍类的相关内容,如类的构118.3.1类的构造方法对前面的示例做一些改动,改动后代码如下(my_calss_search.py):classMyClass(object):i=123def__init__(self,name):=name
deff(self):return'hello,'+
use_class=MyClass('xiaomeng')print('调用类的属性:',use_class.i)print('调用类的方法:',use_class.f())8.3.1类的构造方法对前面的示例做一些改动,改动后代码12实例化MyClass这个类时,调用了__init__()这个方法。在Python中,__init__()方法是一个特殊的方法,当对象实例化时会被调用,__init__()的意思是初始化,是initialization的简写。这个方法的书写方式是:先两个下划线,后面接着是init,再接着两个下划线。这个方法也叫构造方法。在定义类时,若不显式定义一个__init__()方法,则程序默认调用一个无参的__init__()方法。在Python中,定义类时,若没有定义构造方法(__init__()方法),在类的实例化时,系统会调用默认的构造方法。另外,__init__()方法可以有参数,参数通过__init__()传递到类的实例化操作上。一个类中可用定义多个构造方法,但类实例化时只实例化其中位于最后的一个构造方法,也即后面的构造方法会覆盖前面的构造方法,并且实例化时需要根据最后一个构造方法的形式进行实例化。建议一个类中只定义一个构造函数。实例化MyClass这个类时,调用了__init__()这个138.3.2类的访问权限在类内部,可以有属性和方法,而外部代码可以通过直接调用实例变量的方法来操作数据,这样,就隐藏了内部的复杂逻辑。要让内部属性不被外部访问,可以在属性的名称前加上两个下划线__,在Python中,实例的变量名如果以__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问。在Python中,可以给类增加get_attrs这样的方法来获取类中的私有变量。在Python中,可以再给类增加set_attrs这样的方法来修改类中的私有变量。在Python中,通过定义私有变量和定义对应的set方法,可以帮助我们做参数检查,避免传入无效的参数。类还有私有方法。类的私有方法也是以两个下划线开头,声明该方法为私有方法,且不能在类的外使用。8.3.2类的访问权限在类内部,可以有属性和方法,而外部148.4继
承继承的语法格式如下:classDerivedClassName(BaseClassName):<statement-1>..<statement-N>面向对象的编程带来的主要好处之一是代码的重用,实现这种重用的方法之一是通过继承机制。继承完全可以理解成类之间的类型和子类型关系。在面向对象程序设计中,当我们定义一个class的时候,可以从某个现有的class继承,定义的新的class称为子类(Subclass),而被继承的class称为基类、父类或超类(Baseclass、Superclass)。8.4继承继承的语法格式如下:15继承语法class子类名(基类名):基类名写在括号里,基本类是在类定义的时候,在元组之中指明的。在python中继承中的一些特点:(1)在继承中,基类的构造方法(__init__()方法)不会被自动调用,它需要在其子类的构造方法中专门调用。(2)在调用基类的方法时,需要加上基类的类名前缀,且需要带上self参数变量。区别于在类中调用普通函数时并不需要带上self参数。(3)Python总是首先查找对应类型的方法,如果不能在子类中找到对应的方法,它才到基类中逐个查找。继承有什么好处?最大的好处是子类获得了父类的全部非私有的功能。继承语法class子类名(基类名):基类名写在括号里,基本类16classAnimal(object):defrun(self):print('Animalisrunning...')定义类:classDog(Animal):pass
classCat(Animal):passdog=Dog()dog.run()
cat=Cat()cat.run()classAnimal(object):178.5多重继承Python还支持多重继承。多重继承的语法格式如下:classDerivedClassName(Base1,Base2,Base3):<statement-1>
.<statement-N>可以看到,多重继承就是在定义类时,在类名后面的圆括号中添加多个基类(父类或超类),各个基类之间使用逗号分隔。需要注意圆括号中父类的顺序,若父类中有相同的方法名,在子类使用时未指定,Python会从左到右进行搜索。即若某个方法在子类中没有定义,则子类从左到右查找各个父类中是否包含这个方法。通过类的多重继承,一个子类就可以继承多个父类,并从多个父类中取得所有非私有方法。8.5多重继承Python还支持多重继承。多重继承的语法188.6多
态使用继承,可以重复使用代码。但对于继承中的示例,无论是Dog类还是Cat类,调用父类的run()方法时显示的都是Animalisrunning...。对Dog类和Cat类做如下改进(完整代码见animal_3.py):classDog(Animal):defrun(self):print('Dogisrunning...')
classCat(Animal):defrun(self):print('Catisrunning...')8.6多态使用继承,可以重复使用代码。但对于继承19执行如下语句:dog=Dog()print('实例化Dog类')dog.run()
cat=Cat()print('实例化Cat类')cat.run()执行结果如下:实例化Dog类Dogisrunning...实例化Cat类Catisrunning...执行如下语句:20由执行结果看到,分别得到了Dog和Cat各自的running结果。当子类和父类都存在相同的run()方法时,子类的run()方法会覆盖了父类的run()方法,在代码运行的时候,总是会调用子类的run()方法。我们称这个为:多态。多态这个术语来自希腊语,意思是有多种形式。多态意味着就算不知道变量所引用的对象类型是什么,还是能对对象进行操作,而多态也会根据对象(或类)的不同而表现出不同的行为。例如我们上面定义的Animal类,在类中定义了run方法,Dog和Cat类分别继承Animal类,并且分别定义了自己的run方法,最后Dog和Cat调用run方法时,调用的是自己的定义的run方法。多态的好处就是,当我们需要传入Dog、Cat等对象时,我们只需要接收Animal类型就可以了,因为Dog、Cat等都是Animal类型,然后,按照Animal类型进行操作即可。由执行结果看到,分别得到了Dog和Cat各自的running21多态的意思是:对于一个变量,我们只需要知道它是Animal类型,无需确切地知道它的子类型,就可以放心地调用run()方法,而具体调用的run()方法是作用在Animal、Dog、Cat对象上,由运行时该对象的确切类型决定。多态真正的威力在于:调用方只管调用,不管细节,而当我们新增一种Animal的子类时,只要确保run()方法编写正确,不用管原来的代码是如何调用的。这就是著名的“开闭”原则:对扩展开放:允许新增Animal子类。对修改封闭:不需要修改依赖Animal类型的函数。很多函数和运算符都是多态的——你写的绝大多数程序可能也是,即便你并非有意这样。多态的意思是:对于一个变量,我们只需要知道它是Animal类228.7封
装封装是对全局作用域中其他区域隐藏多余信息的原则。听起来有些像多态——使用对象而不用知道其内部细节,两者概念类似,因为它们都是抽象的原则——它们都会帮忙处理程序组件而不用过多关系细节,就像函数一样。但是封装并不等同于多态。多态可以让用户对于不知道什么类(或对象类型)的对象进行方法调用,而封装是可以不用关心对象是如何构建的而直接进行使用。前面几节的示例基本都有用到封装的思想,如在前面定义的Student类中,每个实例就拥有各自的name和score这些数据。我们可以通过函数来访问这些数据,比如打印学生的成绩,我们如下定义(student.py):8.7封装封装是对全局作用域中其他区域隐藏多余信23classStudent(object):def__init__(self,name,score):=nameself.score=score
std=Student('xiaozhi',90)definfo(std):print(f'学生:{};分数:{std.score}')info(std)既然Student实例本身就拥有这些数据,要访问这些数据,就没有必要从外面的函数去访问,可以直接在Student类的内部定义访问数据的函数,这样,就把“数据”给封装起来了。classStudent(object):24classStudent0(object):def__init__(self,name,score):=nameself.score=score
definfo(self):print(f'学生:{};分数:{self.score}')要定义一个方法,除了第一个参数是self外,其他和普通函数一样。要调用一个方法,只需要在实例变量上直接调用,除了self不用传递,其他参数正常传入,执行如下语句:stu=Student0('xiaomeng',95)封装的另一个好处是可以给Student类增加新的方法。classStudent0(object):258.8获取对象信息当我们调用一个方法时,可能需要传递一个参数,这个参数类型我们是知道的,但是对于接收参数的方法,就不一定知道参数是什么类型了。那我们该怎么来得知参数的类型呢?Python为我们提供了如下几种获取对象类型的方法,具体如下:1.使用type()我们前面已经学习过type函数的使用,基本类型都可以用type()判断。如:>>>type(123)<class'int'>>>>type('abc')<class'str'>>>>type(None)<class'NoneType'>8.8获取对象信息当我们调用一个方法时,可能需要传递一个262.使用isinstance()要明确class的继承关系,使用type()很不方便,通过判断class的数据类型,来确定class的继承关系,这样要方便的多,这个时候可以使用isinstance()函数。比如对于继承关系是如下的形式:object->Animal->Dog即Animal继承object,Dog继承Animal,使用isinstance()就可以告诉我们,一个对象是否是某种类型。例如创建如下2种类型的对象:>>>animal=Animal()>>>dog=Dog()对上面2种类型对象,使用isinstance判断如下:>>>isinstance(dog,Dog)True2.使用isinstance()27根据打印结果看到,dog是Dog类型,这个是没有任何疑问的,因为dog变量指向的就是Dog对象。接下来再判断Animal的类型,使用isinstance判断如下:>>>isinstance(dog,Animal)True根据打印结果看到,dog也是Animal类型。由此我们得知:dog虽然自身是Dog类型,但由于Dog是从Animal继承下来的,所以,dog也还是Animal类型。换句话说,isinstance()判断的是一个对象是否是该类型本身,或者是否是该类型继承类的类型。因此,我们可以确信,dog还是object类型:>>>isinstance(dog,object)True同时也确信,实际类型是Dog类型的dog,同时也是Animal类型:>>>isinstance(dog,Dog)andisinstance(dog,Animal)True根据打印结果看到,dog是Dog类型,这个是没有任何疑问的,283.使用dir()如果要获得一个对象的所有属性和方法,可以使用dir()函数,它返回一个字符串的list如要获得一个str对象的所有属性和方法,使用方式如下:>>>dir('abc')3.使用dir()298.9类的专有方法Python类还可以定义专用方法,专用方法是在特殊情况下或当使用特别语法时由Python替你调用的,而不是像普通的方法那样在代码中直接调用。本节我们讲述几个Python比较常用的专有方法。看到类似__init__这种形如__xxx__的变量或者函数名就要注意,这些在Python中是有特殊用途的。__init__我们已经知道怎么用了,Python的class中有许多这种有特殊用途的函数,可以帮助我们定制类。下面来介绍这种特殊类型的函数定制类的方法。8.9类的专有方法Python类还可以定义专用方法,专用301.__str__()方法classStudent(object):def__init__(self,name):=name
def__str__(self):return'学生名称:%s'%
print(Student('xiaozhi'))输出结果为:<__main__.Studentobjectat0x0000000000D64198>1.__str__()方法31结果输出的是一堆字符串,基本没有人看得懂,这样打印出来的结果没有可用性。怎样才能输出读者能看懂,并且是可用的结果?要解决这个问题,需要定义__str__()方法,通过__str__()方法返回一个易懂的字符串就可以了。重新定义上面的示例(student_2.py):classStudent(object):def__init__(self,name):=name
def__str__(self):returnf'学生名称:{}'
print(Student('xiaozhi'))程序输出结果为:学生名称:xiaozhi结果输出的是一堆字符串,基本没有人看得懂,这样打印出来的结果32如果在交互模式下输入:>>>s=Student('xiaozhi')>>>s<__main__.Studentobjectat0x00000000030EC550>由输出结果可以看到,将实例化的类对象赋给一个变量,输出变量的实例还是和之前一样,是一串基本看不懂的字符。直接显示变量调用的不是__str__()方法,而是__repr__()方法,两者的区别在于__str__()方法返回用户看到的字符串,而__repr__()方法返回程序开发者看到的字符串。也就是说,__repr__()方法是为调试服务的。所以这个问题的解决办法是再定义一个__repr__()方法。如果在交互模式下输入:33通常,__str__()方法和__repr__()方法是一样的,所以有一个巧妙的写法(student_3.py):classStudent(object):def__init__(self,name):=name
def__str__(self):returnf'学生名称:{}'__repr__=__str__在交互模式下执行:>>>s=Student('xiaozhi')>>>s学生名称:xiaozhi通常,__str__()方法和__repr__()方法是一样342.__iter__()方法如果一个类想被用于for...in循环,类似list或tuple那样,就必须实现一个__iter__()方法,该方法返回一个迭代对象,Python的for循环会不断调用该迭代对象的__next__()方法拿(“拿”是否可以更改为“获得”)到循环的下一个值,直到遇到StopIteration错误时退出循环。classFib(object):def__init__(self):self.a,self.b=0,1#初始化两个计数器a,bdef__iter__(self):returnself#实例本身就是迭代对象,故返回自己def__next__(self):self.a,self.b=self.b,self.a+self.b#计算下一个值ifself.a>100000:#退出循环的条件raiseStopIteration();returnself.a#返回下一个值2.__iter__()方法353.__getitem__()方法Fib实例虽然能作用于for循环,看起来和list有点像,但是,把它当成list来使用还是不行,要表现得像list那样按照下标取出元素,需要实现__getitem__()方法,如下实现:classFib(object):def__getitem__(self,n):a,b=1,1forxinrange(n):a,b=b,a+breturna3.__getitem__()方法364.__getattr__()方法前面示例讲述过,正常情况下,当调用类的方法或属性时,如果不存在,就会报错。要避免这个错误,除了可以加上一个score属性外,Python还有另一个机制,那就是写一个__getattr__()方法,动态返回一个属性。如下:classStudent(object):
def__init__(self):='xiaozhi'
def__getattr__(self,attr):ifattr=='score':return954.__getattr__()方法37当调用不存在的属性(如score)时,Python解释器会调用__getattr__(self,'score')尝试获得属性,这样就有机会返回score的值。在交互模式下输入:>>>stu=Student()>>>xiaozhi>>>stu.score95由输出结果可以看到,通过调用__getattr__()方法,可以正确输出不存在的属性的值。注意,程序只有在没有找到属性的情况下才调用__getattr__(),若已有属性(如name),则不会在__getattr__()方法中查找。此外,所有__getattr__()方法的调用都有返回值,都会返回None(如stu.abc),定义的__getattr__()默认返回None。当调用不存在的属性(如score)时,Python解释器会调385.__call__()方法一个对象实例可以有自己的属性和方法,当调用实例的方法时,采用的方式是使用instance.method()来调用。任何类,只需要定义一个__call__()方法,就可以直接对实例进行调用。如下示例:classStudent(object):def__init__(self,name):=name
def__call__(self):print(f'名称:{}')__call__()还可以定义参数。5.__call__()方法39在交互模式下输入:>>>stu=Student('xiaomeng')>>>stu()名称:xiaomeng由输出结果可以看到,通过定义__call__()方法,可以直接对实例进行调用并得到结果。__call__()方法还可以定义参数。对实例进行直接调用就像对一个函数进行调用一样,完全可以把对象看成函数,把函数看成对象,因为这两者本来就没有根本区别。如果把对象看成函数,函数本身就可以在运行期间动态创建出来,因为类的实例都是运行期间创建出来的。这样一来,就模糊了对象和函数的界限。怎么判断一个变量是对象还是函数呢?很多时候,判断一个对象是否能被调用,可以使用callable()函数。在交互模式下输入:40比如max()函数和上面定义的带有__call__()方法的Student类实例,示例如下:>>>callable(Student('xiaozhi'))True>>>callable(max)True>>>callable([1,2,3])False>>>callable(None)False>>>callable('a')False由输出结果可以看到,通过callable()函数可以判断一个对象是否为“可调用”对象。比如max()函数和上面定义的带有__call__()方法的418.10活学活用——出行建议假如你今天想外出一趟,但不清楚今天的天气是否适宜出行。现在需要设计一个帮你提供建议的程序,程序要求输入出行的时间,然后根据出行时间,结合能见度、温度和当前空气湿度给出出行建议,以及比较适合使用的交通工具,需要考虑需求变更的可能。需求分析:使用本章所学的封装、继承、多态,比较容易实现。在父类中封装查看能见度、查看温度和查看湿度的方法,子类继承父类。若有需要,子类可以覆盖父类的方法,做自己的实现。子类也可以自定义方法。定义天气查找类,类中定义3个方法,一个方法根据传入的input_daytime值返回对应的能见度;一个方法根据传入的input_daytime值返回对应的温度;最后一个方法根据传入的input_daytime值返回对应的湿度。具体代码请参考书本。8.10活学活用——出行建议假如你今天想外出一趟,但不清428.11技巧点拨在init()方法中初始化对象的全部属性是一个好习惯,可以帮助用户更好地管理类中的属性和对属性值的更改。在程序运行的任何时刻为对象添加属性都是合法的,不过应当避免让对象拥有相同的类型却有不同的属性组。继承会给调试带来新挑战,因为当你调用对象的方法时,可能无法知道调用的是哪一个方法。一旦无法确认程序的运行流程,最简单的解决办法是在适当位置添加一个输出语句,如在相关方法的开头或方法调用开始处等。8.11技巧点拨在init()方法中初始化对象的全部属性438.12问题探讨(1)有办法从外部访问以双下画线开头的实例变量吗?(2)方法与函数有什么区别?(3)使用类的好处?8.12问题探讨(1)有办法从外部访问以双下画线开头的实448.13章节回顾(1)回顾什么是类,类如何使用。(2)回顾构造方法的定义,使用构造方法的好处。(3)回顾类的访问权限有哪些,这些访问权限都怎么使用。(4)回顾继承、多重继承的定义,它们都是怎样实现的。(5)回顾多态的定义与实现。(6)回顾封装的定义与实现。(7)回顾类的专有方法,各自如何使用。8.13章节回顾(1)回顾什么是类,类如何使用。458.14实战演练8.14实战演练46第八章类与对象Python支持创建自己的对象,Python从设计之初就是一门面向对象语言,它提供了一些语言特性支持面向对象编程。创建对象是Python的核心概念,本章将介绍如何创建对象,以及多态、封装、方法和继承等概念。Python快乐学习班的同学结束函数乐高积木厅的创意学习后,导游带领他们来到对象动物园。在对象动物园,将为同学们呈现各种动物对象,同学们将在这里了解各种动物所属的类别,各种动物所拥有的技能,以及它们的技能继承自哪里等知识点。现在跟随Python快乐学习班的同学一起进入对象动物园观摩吧!第八章类与对象Python支持创建自己的对象,Pytho478.1理解面向对象8.1理解面向对象488.1.1面向对象编程Python是一门面向对象编程语言,对面向对象语言编码的过程就叫做面向对象编程。面向对象编程——ObjectOrientedProgramming,简称OOP,是一种程序设计思想。OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。面向对象的程序设计把计算机程序视为一组对象的集合,每个对象都可以接收其他对象发过来的消息,并处理这些消息,计算机程序的执行就是一系列消息在各个对象之间传递。在Python中,所有数据类型都被视为对象,也可以自定义对象。自定义的对象数据类型就是面向对象中的类(Class)的概念。8.1.1面向对象编程Python是一门面向对象编程语言498.1.2面向对象术语简介类(Class):用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。类变量(或属性):类变量在整个实例化的对象中是公用的。类变量定义在类中且在方法之外。类变量通常不作为实例变量使用。类变量也称作属性。数据成员:类变量或者实例变量用于处理类及其实例对象的相关的数据。方法重写:如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖(override),也称为方法的重写。实例变量:定义在方法中的变量,只作用于当前实例的类。多态(Polymorphism):对不同类的对象使用同样的操作。封装(Encapsulation):对外部世界隐藏对象的工作细节。8.1.2面向对象术语简介类(Class):用来描述具有50继承(Inheritance):即一个派生类(derivedclass)继承基类(baseclass)的字段和方法。继承也允许把一个派生类的对象作为一个基类对象对待。以普通的类为基础建立专门的类对象。实例化(Instance):创建一个类的实例,类的具体对象。方法:类中定义的函数。对象:通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法。Python中的类提供了面向对象编程的所有基本功能:类的继承机制允许多个基类,派生类可以覆盖基类中的任何方法,方法中可以调用基类中的同名方法。对象可以包含任意数量和类型的数据。继承(Inheritance):即一个派生类(derived518.2类的定义与使用8.2类的定义与使用528.2.1类的定义类定义的语法格式如下:classClassName(object):<statement-1>.<statement-N>Python中定义类使用class关键字,class后面紧跟类名。示例如下(my_class.py):classMyClass(object):i=123deff(self):return'helloworld'由代码可以看到,这里定义了一个名为MyClass的类。8.2.1类的定义类定义的语法格式如下:53在Python中,类名一般由以大写字母开头的单词命名,并且若是由多个单词组成的类名,那各个单词的首字母都大写。类名后面紧接着是(object),object表示该类是从哪个类继承的。通常,如果没有合适的继承类,就使用object类,这是所有类最终都会继承的类。类包含属性(相当于函数中的语句)和方法(类中的方法大体可以理解成第7章所学的函数)。在类中定义的方法的形式和函数差不多,但不称为函数,而称为方法。因为方法需要靠类对象去调用,而函数不需要。在Python中,类名一般由以大写字母开头的单词命名,并且若548.2.2类的使用classMyClass(object):i=123deff(self):return'helloworld'
use_class=MyClass()print('调用类的属性:',use_class.i)print('调用类的方法:',use_class.f())类的使用比函数调用多了几个操作,调用需要执行如下操作:use_class=MyClass()这步叫做类的实例化,即创建一个类的实例,此处得到的use_class这个变量称为类的具体对象。8.2.2类的使用classMyClass(objec55再看后面两行的调用:print(f'调用类的属性:{use_class.i}')print(f'调用类的方法:{use_class.f()}')这里第一行后面use_class.i部分的作用是调用类的属性,也就是前面所说的类变量。第二行后面use_class.f()部分的作用是调用类的方法。在类中定义方法的要求:在类中定义方法时,第一个参数必须是
self。除第一个参数之外,类的方法和普通函数没什么区别,如可以用默认参数、可变参数、关键字参数和命名关键字参数等。在类中调用方法的要求:要调用一个方法,在实例变量上直接调用即可。除了self不用传递,其他参数正常传入。类对象支持两种操作,属性引用和实例化。实例化方式上面已经介绍过,属性引用的标准语法格式如下:语法中,obj代表类对象,name代表属性。再看后面两行的调用:568.3深
入
类本节将深入介绍类的相关内容,如类的构造方法和访问权限。8.3深入类本节将深入介绍类的相关内容,如类的构578.3.1类的构造方法对前面的示例做一些改动,改动后代码如下(my_calss_search.py):classMyClass(object):i=123def__init__(self,name):=name
deff(self):return'hello,'+
use_class=MyClass('xiaomeng')print('调用类的属性:',use_class.i)print('调用类的方法:',use_class.f())8.3.1类的构造方法对前面的示例做一些改动,改动后代码58实例化MyClass这个类时,调用了__init__()这个方法。在Python中,__init__()方法是一个特殊的方法,当对象实例化时会被调用,__init__()的意思是初始化,是initialization的简写。这个方法的书写方式是:先两个下划线,后面接着是init,再接着两个下划线。这个方法也叫构造方法。在定义类时,若不显式定义一个__init__()方法,则程序默认调用一个无参的__init__()方法。在Python中,定义类时,若没有定义构造方法(__init__()方法),在类的实例化时,系统会调用默认的构造方法。另外,__init__()方法可以有参数,参数通过__init__()传递到类的实例化操作上。一个类中可用定义多个构造方法,但类实例化时只实例化其中位于最后的一个构造方法,也即后面的构造方法会覆盖前面的构造方法,并且实例化时需要根据最后一个构造方法的形式进行实例化。建议一个类中只定义一个构造函数。实例化MyClass这个类时,调用了__init__()这个598.3.2类的访问权限在类内部,可以有属性和方法,而外部代码可以通过直接调用实例变量的方法来操作数据,这样,就隐藏了内部的复杂逻辑。要让内部属性不被外部访问,可以在属性的名称前加上两个下划线__,在Python中,实例的变量名如果以__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问。在Python中,可以给类增加get_attrs这样的方法来获取类中的私有变量。在Python中,可以再给类增加set_attrs这样的方法来修改类中的私有变量。在Python中,通过定义私有变量和定义对应的set方法,可以帮助我们做参数检查,避免传入无效的参数。类还有私有方法。类的私有方法也是以两个下划线开头,声明该方法为私有方法,且不能在类的外使用。8.3.2类的访问权限在类内部,可以有属性和方法,而外部608.4继
承继承的语法格式如下:classDerivedClassName(BaseClassName):<statement-1>..<statement-N>面向对象的编程带来的主要好处之一是代码的重用,实现这种重用的方法之一是通过继承机制。继承完全可以理解成类之间的类型和子类型关系。在面向对象程序设计中,当我们定义一个class的时候,可以从某个现有的class继承,定义的新的class称为子类(Subclass),而被继承的class称为基类、父类或超类(Baseclass、Superclass)。8.4继承继承的语法格式如下:61继承语法class子类名(基类名):基类名写在括号里,基本类是在类定义的时候,在元组之中指明的。在python中继承中的一些特点:(1)在继承中,基类的构造方法(__init__()方法)不会被自动调用,它需要在其子类的构造方法中专门调用。(2)在调用基类的方法时,需要加上基类的类名前缀,且需要带上self参数变量。区别于在类中调用普通函数时并不需要带上self参数。(3)Python总是首先查找对应类型的方法,如果不能在子类中找到对应的方法,它才到基类中逐个查找。继承有什么好处?最大的好处是子类获得了父类的全部非私有的功能。继承语法class子类名(基类名):基类名写在括号里,基本类62classAnimal(object):defrun(self):print('Animalisrunning...')定义类:classDog(Animal):pass
classCat(Animal):passdog=Dog()dog.run()
cat=Cat()cat.run()classAnimal(object):638.5多重继承Python还支持多重继承。多重继承的语法格式如下:classDerivedClassName(Base1,Base2,Base3):<statement-1>
.<statement-N>可以看到,多重继承就是在定义类时,在类名后面的圆括号中添加多个基类(父类或超类),各个基类之间使用逗号分隔。需要注意圆括号中父类的顺序,若父类中有相同的方法名,在子类使用时未指定,Python会从左到右进行搜索。即若某个方法在子类中没有定义,则子类从左到右查找各个父类中是否包含这个方法。通过类的多重继承,一个子类就可以继承多个父类,并从多个父类中取得所有非私有方法。8.5多重继承Python还支持多重继承。多重继承的语法648.6多
态使用继承,可以重复使用代码。但对于继承中的示例,无论是Dog类还是Cat类,调用父类的run()方法时显示的都是Animalisrunning...。对Dog类和Cat类做如下改进(完整代码见animal_3.py):classDog(Animal):defrun(self):print('Dogisrunning...')
classCat(Animal):defrun(self):print('Catisrunning...')8.6多态使用继承,可以重复使用代码。但对于继承65执行如下语句:dog=Dog()print('实例化Dog类')dog.run()
cat=Cat()print('实例化Cat类')cat.run()执行结果如下:实例化Dog类Dogisrunning...实例化Cat类Catisrunning...执行如下语句:66由执行结果看到,分别得到了Dog和Cat各自的running结果。当子类和父类都存在相同的run()方法时,子类的run()方法会覆盖了父类的run()方法,在代码运行的时候,总是会调用子类的run()方法。我们称这个为:多态。多态这个术语来自希腊语,意思是有多种形式。多态意味着就算不知道变量所引用的对象类型是什么,还是能对对象进行操作,而多态也会根据对象(或类)的不同而表现出不同的行为。例如我们上面定义的Animal类,在类中定义了run方法,Dog和Cat类分别继承Animal类,并且分别定义了自己的run方法,最后Dog和Cat调用run方法时,调用的是自己的定义的run方法。多态的好处就是,当我们需要传入Dog、Cat等对象时,我们只需要接收Animal类型就可以了,因为Dog、Cat等都是Animal类型,然后,按照Animal类型进行操作即可。由执行结果看到,分别得到了Dog和Cat各自的running67多态的意思是:对于一个变量,我们只需要知道它是Animal类型,无需确切地知道它的子类型,就可以放心地调用run()方法,而具体调用的run()方法是作用在Animal、Dog、Cat对象上,由运行时该对象的确切类型决定。多态真正的威力在于:调用方只管调用,不管细节,而当我们新增一种Animal的子类时,只要确保run()方法编写正确,不用管原来的代码是如何调用的。这就是著名的“开闭”原则:对扩展开放:允许新增Animal子类。对修改封闭:不需要修改依赖Animal类型的函数。很多函数和运算符都是多态的——你写的绝大多数程序可能也是,即便你并非有意这样。多态的意思是:对于一个变量,我们只需要知道它是Animal类688.7封
装封装是对全局作用域中其他区域隐藏多余信息的原则。听起来有些像多态——使用对象而不用知道其内部细节,两者概念类似,因为它们都是抽象的原则——它们都会帮忙处理程序组件而不用过多关系细节,就像函数一样。但是封装并不等同于多态。多态可以让用户对于不知道什么类(或对象类型)的对象进行方法调用,而封装是可以不用关心对象是如何构建的而直接进行使用。前面几节的示例基本都有用到封装的思想,如在前面定义的Student类中,每个实例就拥有各自的name和score这些数据。我们可以通过函数来访问这些数据,比如打印学生的成绩,我们如下定义(student.py):8.7封装封装是对全局作用域中其他区域隐藏多余信69classStudent(object):def__init__(self,name,score):=nameself.score=score
std=Student('xiaozhi',90)definfo(std):print(f'学生:{};分数:{std.score}')info(std)既然Student实例本身就拥有这些数据,要访问这些数据,就没有必要从外面的函数去访问,可以直接在Student类的内部定义访问数据的函数,这样,就把“数据”给封装起来了。classStudent(object):70classStudent0(object):def__init__(self,name,score):=nameself.score=score
definfo(self):print(f'学生:{};分数:{self.score}')要定义一个方法,除了第一个参数是self外,其他和普通函数一样。要调用一个方法,只需要在实例变量上直接调用,除了self不用传递,其他参数正常传入,执行如下语句:stu=Student0('xiaomeng',95)封装的另一个好处是可以给Student类增加新的方法。classStudent0(object):718.8获取对象信息当我们调用一个方法时,可能需要传递一个参数,这个参数类型我们是知道的,但是对于接收参数的方法,就不一定知道参数是什么类型了。那我们该怎么来得知参数的类型呢?Python为我们提供了如下几种获取对象类型的方法,具体如下:1.使用type()我们前面已经学习过type函数的使用,基本类型都可以用type()判断。如:>>>type(123)<class'int'>>>>type('abc')<class'str'>>>>type(None)<class'NoneType'>8.8获取对象信息当我们调用一个方法时,可能需要传递一个722.使用isinstance()要明确class的继承关系,使用type()很不方便,通过判断class的数据类型,来确定class的继承关系,这样要方便的多,这个时候可以使用isinstance()函数。比如对于继承关系是如下的形式:object->Animal->Dog即Animal继承object,Dog继承Animal,使用isinstance()就可以告诉我们,一个对象是否是某种类型。例如创建如下2种类型的对象:>>>animal=Animal()>>>dog=Dog()对上面2种类型对象,使用isinstance判断如下:>>>isinstance(dog,Dog)True2.使用isinstance()73根据打印结果看到,dog是Dog类型,这个是没有任何疑问的,因为dog变量指向的就是Dog对象。接下来再判断Animal的类型,使用isinstance判断如下:>>>isinstance(dog,Animal)True根据打印结果看到,dog也是Animal类型。由此我们得知:dog虽然自身是Dog类型,但由于Dog是从Animal继承下来的,所以,dog也还是Animal类型。换句话说,isinstance()判断的是一个对象是否是该类型本身,或者是否是该类型继承类的类型。因此,我们可以确信,dog还是object类型:>>>isinstance(dog,object)True同时也确信,实际类型是Dog类型的dog,同时也是Animal类型:>>>isinstance(dog,Dog)andisinstance(dog,Animal)True根据打印结果看到,dog是Dog类型,这个是没有任何疑问的,743.使用dir()如果要获得一个对象的所有属性和方法,可以使用dir()函数,它返回一个字符串的list如要获得一个str对象的所有属性和方法,使用方式如下:>>>dir('abc')3.使用dir()758.9类的专有方法Python类还可以定义专用方法,专用方法是在特殊情况下或当使用特别语法时由Python替你调用的,而不是像普通的方法那样在代码中直接调用。本节我们讲述几个Python比较常用的专有方法。看到类似__init__这种形如__xxx__的变量或者函数名就要注意,这些在Python中是有特殊用途的。__init__我们已经知道怎么用了,Python的class中有许多这种有特殊用途的函数,可以帮助我们定制类。下面来介绍这种特殊类型的函数定制类的方法。8.9类的专有方法Python类还可以定义专用方法,专用761.__str__()方法classStudent(object):def__init__(self,name):=name
def__str__(self):return'学生名称:%s'%
print(Student('xiaozhi'))输出结果为:<__main__.Studentobjectat0x0000000000D64198>1.__str__()方法77结果输出的是一堆字符串,基本没有人看得懂,这样打印出来的结果没有可用性。怎样才能输出读者能看懂,并且是可用的结果?要解决这个问题,需要定义__str__()方法,通过__str__()方法返回一个易懂的字符串就可以了。重新定义上面的示例(student_2.py):classStudent(object):def__init__(self,name):=name
def__str__(self):returnf'学生名称:{}'
print(Student('xiaozhi'))程序输出结果为:学生名称:xiaozhi结果输出的是一堆字符串,基本没有人看得懂,这样打印出来的结果78如果在交互模式下输入:>>>s=Student('xiaozhi')>>>s<__main__.Studentobjectat0x00000000030EC550>由输出结果可以看到,将实例化的类对象赋给一个变量,输出变量的实例还是和之前一样,是一串基本看不懂的字符。直接显示变量调用的不是__str__()方法,而是__repr__()方法,两者的区别在于__str__()方法返回用户看到的字符串,而__repr__()方法返回程序开发者看到的字符串。也就是说,__repr__()方法是为调试服务的。所以这个问题的解决办法是再定义一个__repr__()方法。如果在交互模式下输入:79通常,__str__()方法和__repr__()方法是一样的,所以有一个巧妙的写法(student_3.py):classStudent(object):def__init__(self,name):=name
def__str__(self):returnf'学生名称:{}'__repr__=__str__在交互模式下执行:>>>s=Student('xiaozhi')>>>s学生名称:xiaozhi通常,__str__()方法和__repr__()方法是一样802.__iter__()方法如果一个类想被用于for...in循环,类似list或tuple那样,就必须实现一个__iter__()方法,该方法返回一个迭代对象,Python的for循环会不断调用该迭代对象的__next__()方法拿(“拿”是否可以更改为“获得”)到循环的下一个值,直到遇到StopIteration错误时退出循环。classFib(object):def__init__(self):self.a,self.b=0,1#初始化两个计数器a,bdef__iter__(self):returnself#实例本身就是迭代对象,故返回自己def__next__(self):self.a,self.b=self.b,self.a+self.b#计算下一个值ifself.a>100000:#退出循环的条件raiseStopIteration();returnself.a#返回下一个值2.__iter__()方法813.__getitem__()方法Fib实例虽然能作用于for循环,看起来和list有点像,但是,把它当成list来使用还是不行,要表现得像list那样按照下标取出元素,需要实现__getitem__()方法,如下实现:
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 国画故宫课件教学课件
- 2024年保卫服务合同
- (完整版)特种设备应急预案
- 2024年建筑工地木工班组劳务承包合同
- 2024年度生态补偿机制实施合同
- 2024年应急运输响应合同
- 激励学生课件教学课件
- 2024年度教育设备采购与维护合同
- 2024年度欧洲汽车制造与销售合同
- 2024年大宗商品物流合同
- 抗癌必修课胰腺癌
- 充电桩采购安装投标方案(技术方案)
- 《带状疱疹》课件
- 法律资料特种设备法律法规与事故案例培训
- 成立分公司计划书
- 浙江省绍兴市诸暨市2023-2024学年七年级数学上学期期末试卷
- 营销的12个方法论
- 化肥农药减量增效问卷调查表
- 浙江省宁波市四校联考2023-2024学年九年级上学期12月月考数学试题
- 社会服务项目结果评估报告
- GB/T 18603-2023天然气计量系统技术要求
评论
0/150
提交评论