Python面向对象程序设计OOP入门教程【类,实例,继承,重载等】


Posted in Python onJanuary 05, 2019

本文实例讲述了Python面向对象程序设计OOP。分享给大家供大家参考,具体如下:

类是Python所提供的最有用的的工具之一。合理使用时,类可以大量减少开发的时间。类也在流行的Python工具中使用,例如,tkinter GUI API。

为何使用类

与面向对象的Java一样,类是对现实世界的一种抽象。

从更具体的程序设计观点来看,类是Python的程序组成单元,就像函数和模块一样:类是封装逻辑和数据的另一种方式。实际上,类也定义新的命名空间,在很大程度上就像模块。但是类有三个重要的独到之处,使其在建立对象时更为有用。

1.多重实例

类基本上就是产生对象的工厂。每次调用一个类,就会产生一个有独立命名空间的新对象。每个又类产生的对象都能读取类的属性,并获得自己的命名空间来储存数据,这些数据对于每个对象来说都不同。

2.通过继承进行定制

类也支持OOP的继承的概念。我们可以在类的外部重新定义其属性从而扩充这个类。

3.运算符重载

通过提供特定的协议方法,类可以定义对象来响应在内置类型上的几种运算。例如,通过类创建的对象可以进行切片,级联和索引等运算。Python提供了一些可以由类使用的钩子,从而能够中断并实现任何的内置类型运算。

Python的类与Java类似,所以关于继承、属性和方法以及类和实例的基础知识在这里不再赘述。直接介绍语法内容。

类产生多个实例对象

【OOP模型中的两种对象:类对象和实例对象】

类对象提供默认行为,是实例对象的工厂。实例对象是程序处理的实际对象:各自都有独立的命名空间,但是继承(可自动存取)创建该实例的类中的变量名。类对象来自于语句,而实例来自于调用。每次调用一个类,就会得到这个类的新的实例。

【类对象提供默认行为】

执行class语句,就会得到类对象。以下是Python类的主要特性:
1.class语句创建类对象并将其赋值给变量名。就像函数def语句,Python class语句也是可执行语句,执行时,会产生新的类对象,并将其赋值给class头部的变量名。此外,就像def应用,class语句一般是在其所在文件导入时执行的。

2.class语句内的赋值语句会创建类的属性。class语句内的顶层的赋值语句会产生类对象中的属性。从技术角度讲,class语句的作用域会变成类对象的属性的命名空间,就像模块的全局作用域一样。执行class语句后,类的属性可由变量名点号运算获取object.name

3.类属性提供对象的状态和行为。类对象的属性记录状态信息和行为,可由这个类创建的所有实例共享。位于类中的函数def语句会生成方法,方法将会处理实例。

【实例对象是具体元素】

当调用类对象时,我们得到了实例对象。以下是类的实例内含的重点概要。

1.像函数那样调用类对象会创建新的实例对象。实例代表了程序领域中的具体元素。

2.每个实例对象继承类的属性并获得了自己的命名空间。由类所创建的实例对象是新的命名空间。一开始是空的,但是会继承创建该实例的类对象内的属性。

3.在方法内对self属性做赋值运算会产生每个实例自己的属性。在类方法函数内,第一个参数(self)会引用正处理的实例对象。对self的属性做赋值运算,会创建或修改实例内的数据,而不是类的数据。

第一个例子

首先定义一个名为FirstClass的类,通过交互模式运行Python class 语句。

>>> class FirstClass:
 def setdata(self,value):
 self.data = value
 def display(self):
 print(self.data)

这里是在交互模式下工作,但一般来说,这种语句应该是当其所在的模块文件导入时运行的。就像通过def建立的函数,这个类在Python抵达并执行语句前是不会存在的。

就像所有的复合语句一样,class开头一行会列出类的名称,后面再接一个或多个内嵌并且缩进的语句的主体。

就像之前学过的,def其实是赋值运算。在这里把函数对象赋值给变量名setdata,而display位于class语句范围内,因此会产生附加在类上的属性:FirstClass.setdataFirstClass.display。事实上,在类嵌套的代码块中顶层的赋值的任何变量名,都会变成类的属性。

位于类中的函数通常称为方法。方法时普通def,支持先去学过的函数的所有内容。在方法函数中,调用时,第一个参数(self)自动接收隐含的实例对象:调用的主体。下面建立这个类两个实例:

>>> x = FirstClass()
>>> y = FirstClass()

以此方式调用类时,【注意后面有小括号】,会产生实例对象。确切的讲,此时有三个对象:两个实例和一个类。

这两个实例一开始是空的,但是它们被连接到创建它们的类。如果对实例以及类对象内的属性名称进行点号运算,Python会通过继承搜索从类取得变量名。

>>> x.setdata('King Arthur')
>>> y.setdata(3.14159)

x或y本身都没有setdata属性,为了寻找这个属性,Python会顺着实例到类的连接搜索。而这就是所谓的Python的继承:继承是在属性点号运算时发生的,而且只与查找连接对象内的变量名有关。

在FirstClass的setdata函数中,传入的值会赋给self.data。在方法中,self(按惯例,这是最左侧参数的名称)会自动引用正在处理的实例(x或y),所以赋值语句会把值储存在实例的命名空间,而不是类的命名空间。

因为类会产生多个实例,方法必须经过self参数才能获取正在处理的实例。当调用类的display方法来打印self.data时,会发现每个实例的值都不同。另外,变量名display在x和y之内都相同,因为它是来自于类的:

>>> x.display()
King Arthur
>>> y.display()
3.14159

注意:在每个实例内的data成员储存了不同对象类型(字符串和浮点数)。就像Python中的其他事物,实例属性并没有声明。首次赋值后,实例就会存在,就像简单的变量。事实上,如果在调用setdata之前,就对某一实例调用display,则会触发未定义变量名的错误:data属性以setdata方法赋值前,是不会在内存中存在的。

我们可以在类的内部或外部修改实例属性。在类内时,通过方法对self进行赋值运算,而在类外时,则可以通过对实例对象进行赋值运算:

>>> x.data = 'New value'
>>> x.display()
New value

虽然比较少见,通过在类方法函数外对变量名进行赋值运算,我们甚至可以在实例命名空间内产生全新的属性:

>>> x.anothername = 'spam'

这里会增加一个名为anothername的新属性,实例对象x的任何类方法都可以使用它。

不过,类通常是通过self参数进行赋值运算从而建立实例的所有属性的。

类通过继承进行定制

除了作为工厂来生成多个实例对象之外,类也可以引入新组件(子类)来进行修改,而不对现有组件进行原地的修改。由类产生的实例对象会继承该类的属性。

在Python中,实例从类中继承,而类继承于超类。以下是属性继承机制的核心观点:

1.超类列在了类开头的括号中。含有继承的类称为子类,而子类所继承的类就是其超类。

2.类从其超类中继承属性。就像实例继承其类中所定义的属性名一样,类也会继承其超类中定义的所有属性名称。当读取属性时,如果它不存在于子类中,Python会自动搜索这个属性。

3.每个object.attribute都会开启新的独立搜索。

4.逻辑的修改是通过创建子类,而不是修改超类。在树中层次较低的子类中重新定义超类的变量名,子类就可以取代并定制所继承的行为。

第二个例子

下个例子建立在上一个例子的基础之上。首先,定义一个新的类SecondClass,继承FirstClass所有变量名,并提供自己的一个变量名。

>>> class secondClass(FirstClass):
 def display(self):
 print('Current value = " %s "'%self.data)

SecondClass定义display方法以不同格式打印。定义一个和FirstClass中的属性同名的属性,SecondClass有效地取代其超类内的display属性。因为继承搜索会从实例向上进行,之后到子类,然后到超类,直到所找到的属性名称首次出现为止。

有时候,我们把这种在树中较低处发生的重新定义的、取代属性的动作称为【重载】。结果就是,SecondClass改变了display的行为,把FirstClass特定化了。另外,SecondClass(以及其任何实例)依然会继承FirstClass的setdata方法:

>>> z = SecondClass()
>>> z.setdata(42)
>>> z.display()
Current value = " 42 "

这里有一个和OOP相关的很重要的事情要留意:SecondClass引入的专有化完全是在FirstClass外部完成的。也就是说,不影响当前存在的或未来的FirstClass对象,就像上一个例子中的x:

>>> x.display()
New value

类是模块内的属性

类的名称没有什么神奇之处。当class语句执行时,这只是赋值给对象的变量,而对象可以用任何普通表达式引用。

例如,如果FirstClass是写在模块文件内,而不是在交互模式下输入的,就可将其导入,在类开头的那行可以正常地使用它的名称。

from modulename import FirstClass
class SecondClass(FirstClass):
 def display(self):...

或者,其等效写法:

import modulename
class SecondClass(module.FirstClass):
 def display():...

像其他事物一样,类名称总是存在于模块中。每个模块可以任意混合任意数量的变量、函数以及类。文件food.py示例如下:

#food.py
var = 1
def func():
 ...
class spam:
 ...
class ham:
 ...
class eggs:
 ...

如果模块和类碰巧有相同的名称,也是如此。文件person.py,写法如下:

class person:
 ...

需要像往常一样通过模块获取类:

import person
x = person.person()

person.person()指的是person模块内的person类。只写person只会取得模块,而不是类,除非使用from语句。

from person import person
x = person()

Python的通用惯例之处,类名应该以一个大写字母开头,以使得他们更为清晰:

import person
x = person.Person()

类可以截获Python运算符:运算符重载

运算符重载就是让类写成的对象,可以截获并响应用在内置类型上的运算:加法、切片、打印和点号运算等。

因为运算符重载,可以让我们自己的对象行为就像内置对象那样,这可促进对象接口更为一致并更易于学习,而且可让类对象由预期的内置类型接口的代码处理。以下是重载运算符主要概念的概要:

1.以双下划线命名的方法(__X__)是特殊钩子。Python运算符重载的实现是提供特殊命名的方法来拦截运算。Python语言替每种运算和特殊命名的方法之间,定义了固定不变的映射关系。

2.当实例出现在内置运算时,这类方法会自动调用。例如,如果实例对象继承了__add__方法,当对象出现在+表达式内时,该方法就会调用。该方法的返回值变成相应表达式的结果。

3.类可覆盖多数内置类型运算。有几十种特殊运算符重载的方法的名称,几乎可以截获并实现内置类型的所有运算。它不仅包括了表达式,而且像打印和对象建立这类基本运算也包括在内。

4.运算符覆盖方法没有默认值,而且也不需要。如果类没有定义或继承运算符重载方法,就是说相应的运算在类实例中并不支持。例如,如果没有__add__,+表达式就会引发异常。

5.运算符可让类与Python的对象模型相集成。

【不过,要注意的是,运算符重载是可选的功能,一般的应用程序开发并不需要,除非真的有特殊的需求需要模仿内置类型接口。】

第三个例子

这一次,要定义SecondClass的子类,实现三个特殊名称的属性,让Python自动进行调用:

1.当新的实例构造时,会调用__init__(self是新的ThirdClass对象)
2.当ThirdClass实例出现在+表达式中时,则会调用__add__。
3.当打印一个对象的时候(从技术上讲,当通过str内置函数或者其Python内部的等价形式来将其转换为打印字符串的时候),运行__str__

新的子类也定义了一个常规命名的方法,叫做mul,它在原处修改该实例的对象。如下是一个新的子类:

>>> class ThirdClass(SecondClass):
 def __init__(self,value):
 self.data = value
 def __add__(self,other):
 return ThirdClass(self.data+other)
 def __str__(self):
 return '[ThirdClass:%s]'%self.data
 def mul(self,other):
 self.data*=other
>>>
>>> a = ThirdClass('abc')
>>> a.display()
Current value = " abc "
>>> print(a)
[ThirdClass:abc]
>>> b = a + 'xyz'
>>> b.display()
Current value = " abcxyz "
>>> a.mul(3)
>>> print(a)
[ThirdClass:abcabcabc]

ThirdClass是一个SecondClass对象,所以其实例会继承SecondClass的display方法。但是,ThirdClass生成的调用现在会传递一个参数(例如,‘abc'),这是传给__init__构造函数内的参数value的,并将其值赋给self.data。直接效果就是,ThirdClass计划在构建时自动设置data属性,而不是在构建之后请求setdata调用。

此外,ThirdClass对象现在可以出现在+表达式和print调用中。对于+,Python把左侧的实例对象传给__add__中的self参数,而把右边的值传给other。__add__返回的内容成为+表达式的结果。对于print,Python把要打印的对象传递给__str__中的self;该方法返回的字符串看作是对象的打印字符串。使用__str__,我们可以用一个常规的print来显示该类的对象,而不是调用特殊的display方法。

__init__、__add__和__str__这样的特殊命名的方法会由子类和实例继承,就像这个类中赋值的其他变量名。Python通常会自动调用,但偶尔也能由程序代码调用。

【注意:只有在实现本质为数学的对象时,才会用到许多运算符重载方法。例如,向量或矩阵类可以重载加法运算符,但员工类可能就不用。就较简单的类而言,可能根本不会用到重载】

【几乎每个实例的类都会出现一个重载方法是:__init__构造函数。虽然Python不会对实例的属性进行声明,但通常也可以通过找到类的__init__方法的代码,而了解实例有哪些属性。】

注意:Python中没有Java中的方法重载,即方法名相同,但参数和参数类型不同,比如__init__函数只能有一个,取最后一个赋给__init__的函数对象。

>>> class Test:
 def __init__():
 pass
 def __init__(self,name,age,sex):
 self.name = name
 self.age = age
 self.sex = sex
>>> a = Test()
Traceback (most recent call last):
 File "<pyshell#54>", line 1, in <module>
 a = Test()
TypeError: __init__() missing 3 required positional arguments: 'name', 'age', and 'sex'

把两个__init__换了位置之后就没有报错了,因为__init__函数已经更改成了没有参数的:

>>> class Test:
 def __init__(self,name,age,sex):
 self.name = name
 self.age = age
 self.sex = sex
 def __init__(self):
 pass
>>> a = Test()

世界上最简单的Python类

实际上,我们建立的类可以什么东西都没有,下列语句建立一个类,其内完全没有附加的属性:

>>> class rec:pass

因为没有写任何方法,所以我们需要无操作的pass语句。以交互模式执行此语句,建立这个类后,就可以完全在最初的class语句外,通过赋值变量名给这个类增加属性:

>>> rec.name = 'Bob'
>>> rec.age = 40

通过赋值语句创建这些属性后,就可以用一般的语法将它们取出。这样用时,类差不多就像C的struct,我们也可以用字典的键做类似的事情,但是需要额外的字符。

>>> print(rec.name)
Bob

现在建立两个该类的实例:

>>> x = rec()
>>> y = rec()
>>> x.name,y.name
('Bob', 'Bob')

这些实例本身没有属性,它们只是从类对象那里取出name属性。不过,如果把一个属性赋值给一个实例,就会在该对象内创建(或修改)该属性,而不会因属性的引用而启动继承搜索,因为属性赋值运算只会影响属性赋值所在的对象。在这里,x得到自己的name,但y依然继承附加在他的类上的name:

>>> x.name = 'Gavin'
>>> rec.name,x.name,y.name
('Bob', 'Gavin', 'Bob')

事实上,命名空间对象的属性通常都是以字典的形式实现的。例如,__dict__属性是针对大多数基于类的对象的命名空间字典。如下,名称和__X__内部名称集合所出现的顺序可能随着版本的不同而有所不同:

>>> rec.__dict__.keys()
dict_keys(['__weakref__', 'name', '__module__', '__doc__', 'age', '__dict__'])
>>> list(x.__dict__.keys())
['name']
>>> list(y.__dict__.keys())
[]

在这里,类的字典显示出我们进行赋值了的name和age属性,x有自己的name,而y依然是空的。不过,每个实例都连接至其类以便于继承,如果你想查看的话,这个连接叫做__class__:

>>> x.__class__
<class '__main__.rec'>

类也有一个__bases__属性,它是其超类的元祖:

>>> rec.__bases__
(<class 'object'>,)

这两个属性时Python在内存中类树常量的表示方式。

即使是方法也可以完全独立地在任意类对象的外部创建。例如,下列在任意类之外定义了一个简单函数,并带有一个参数:

>>> def upperName(self):
return self.name.upper()

这里与类完全没有什么关系——这是一个简单函数,在此时就能予以调用,只要我们传入一个带有name属性的对象:

>>> upperName(x)
'GAVIN'

不过,如果我们把这个简单函数赋值成类的属性,就会变成方法,可以由任何实例调用:

>>> rec.method = upperName
>>> x.method()
'GAVIN'
>>> y.method()
'BOB'
>>> rec.method(x)
'GAVIN'

类与字典的关系

看如下字典的示例:

>>> rec = {}
>>> rec['name'] = 'mel'
>>> rec['age'] = 45
>>> rec['job'] = 'trainer/writer'
>>>
>>> print(rec['name'])
mel

这段代码模拟了像其他语言中记录这样的工具,这里也可以用类做同样的事情:

>>> class rec:pass
>>> rec.name = 'mel'
>>> rec.age = 45
>>> rec.job = 'trainer/writer'
>>>
>>> print(rec.age)
45

这段代码的语法比其字典等价形式要少很多。它使用了一个空的class语句来产生一个空的命名空间。

这是有效的,但是,对于我们将需要的每一条不同的记录,都需要一条新的class语句。更通俗的讲,我们可以产生一个空类的实例来表示每条不同的记录:

>>> class rec:pass
>>> pers1 = rec()
>>> pers1.name='mel'
>>> pers1.job = 'trainer'
>>> pers1.age = 40
>>>
>>> pers2 = rec()
>>> pers2.name = 'vls'
>>> pers2.job = 'developer'
>>>
>>> pers1.name,pers2.name
('mel', 'vls')

这里,我们通过对属性赋值来填充记录,实际上,同一个类的实例甚至不一定必须有相同的一组属性名称,在这个示例中,pers1有唯一的age属性。每一个实例都有一个不同的属性字典。

最后,我们可以编写一个更完整的类来实现记录及处理:

>>> class Person:
 def __init__(self,name,job):
 self.name = name
 self.job = job
 def info(self):
 return (self.name,self.job)
>>> rec1 = Person('mel','trainer')
>>> rec2 = Person('vls','developer')
>>>
>>> rec1.job,rec2.info()
('trainer', ('vls', 'developer'))

希望本文所述对大家Python程序设计有所帮助。

Python 相关文章推荐
wxPython窗口中文乱码解决方法
Oct 11 Python
Python制作数据导入导出工具
Jul 31 Python
简单谈谈python中的lambda表达式
Jan 19 Python
解决python中使用PYQT时中文乱码问题
Jun 17 Python
python2和python3实现在图片上加汉字的方法
Aug 22 Python
Python enumerate内置库用法解析
Feb 24 Python
django配置app中的静态文件步骤
Mar 27 Python
PyInstaller运行原理及常用操作详解
Jun 13 Python
Python调用OpenCV实现图像平滑代码实例
Jun 19 Python
降低python版本的操作方法
Sep 11 Python
python如何利用Mitmproxy抓包
Oct 10 Python
MoviePy简介及Python视频剪辑自动化
Dec 18 Python
Python3爬虫全国地址信息
Jan 05 #Python
Python图像处理之图像的读取、显示与保存操作【测试可用】
Jan 04 #Python
Python图像处理之图像的缩放、旋转与翻转实现方法示例
Jan 04 #Python
Python图像处理实现两幅图像合成一幅图像的方法【测试可用】
Jan 04 #Python
Python小游戏之300行代码实现俄罗斯方块
Jan 04 #Python
django主动抛出403异常的方法详解
Jan 04 #Python
pyspark操作MongoDB的方法步骤
Jan 04 #Python
You might like
smarty+adodb+部分自定义类的php开发模式
2006/12/31 PHP
PHP和.net中des加解密的实现方法
2013/02/27 PHP
php中Session的生成机制、回收机制和存储机制探究
2014/08/19 PHP
php限制ip地址范围的方法
2015/03/31 PHP
php简单计算权重的方法示例【适合抽奖类应用】
2019/06/10 PHP
解决laravel5.4下的group by报错的问题
2019/10/16 PHP
PHP多进程简单实例小结
2019/11/09 PHP
用 javascript 实现的点击复制代码
2007/03/24 Javascript
Javascript日期对象的dateAdd与dateDiff方法
2008/11/18 Javascript
JS事件在IE与FF中的区别详细解析
2013/11/20 Javascript
node.js中的querystring.parse方法使用说明
2014/12/10 Javascript
JS动态修改表格cellPadding和cellSpacing的方法
2015/03/31 Javascript
Angularjs中UI Router的使用方法
2016/05/14 Javascript
js上下视差滚动简单实现代码
2017/03/07 Javascript
微信小程序block的使用教程
2018/04/01 Javascript
Angular8路由守卫原理和使用方法
2019/08/29 Javascript
在vue中实现禁止屏幕滚动,禁止屏幕滑动
2020/07/22 Javascript
vue中touch和click共存的解决方式
2020/07/28 Javascript
[01:00:04]DOTA2上海特级锦标赛B组小组赛#1 Alliance VS Spirit第二局
2016/02/26 DOTA
[30:00]完美世界DOTA2联赛PWL S2 Rebirth vs LBZS 第二场 11.28
2020/12/01 DOTA
Python中使用PIL库实现图片高斯模糊实例
2015/02/08 Python
python中函数默认值使用注意点详解
2016/06/01 Python
Python爬虫之pandas基本安装与使用方法示例
2018/08/08 Python
在Pandas中DataFrame数据合并,连接(concat,merge,join)的实例
2019/01/29 Python
python中yield的用法详解——最简单,最清晰的解释
2019/04/04 Python
python tkinter图形界面代码统计工具
2019/09/18 Python
Python图像处理库PIL的ImageGrab模块介绍详解
2020/02/26 Python
快速了解Python开发环境Spyder
2020/06/29 Python
CSS3 icon font完全指南(CSS3 font 会取代icon图标)
2013/01/06 HTML / CSS
HTML5 标准将把互联网视频扔回到黑暗时代
2010/02/10 HTML / CSS
HTML5打开手机扫码功能及优缺点
2017/11/27 HTML / CSS
解析html5 canvas实现背景鼠标连线动态效果代码
2019/06/17 HTML / CSS
构造器Constructor是否可被override?
2013/08/06 面试题
活动简报范文
2015/07/22 职场文书
读鲁迅先生的经典名言
2019/08/20 职场文书
2019年世界儿童日宣传标语
2019/11/22 职场文书