前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >《Python入门09》揭秘python面向对象的编程~

《Python入门09》揭秘python面向对象的编程~

作者头像
ShuYini
发布2019-08-08 15:43:15
6510
发布2019-08-08 15:43:15
举报

点击上方的“小小白AI”,选择“关注”

重磅知识,第一时间送达

引言

Python和C++、Java是一样的,它是一种面向对象的计算机语言。在前几章,主要介绍了Python内置的主要对象类型(数、字符串、列表、元组和字典),大致了解了众多的内置函数和标准库,还创建了自定义函数。接下来将要介绍如何创建自定义对象。本文主要从以下三个方面进行介绍:对象的特性、什么是类、如何创建类。

一、对象的特性

在面向对象编程中,对象大致意味着一系列数据(属性)以及一套访问和操作这些数据的方法。使用对象而非全局变量和函数的原因有多个,下面列出了使用对象的最重要的好处。

多态:可对不同类型的对象执行相同的操作。

封装:对外部隐藏有关对象工作原理的细节。

继承:可基于通用类创建出专用类。

1、多态和方法

假设你要为一个销售食品的电子商务网站创建在线支付系统,程序将接收来自系统另一部分(或之后设计的类似系统)的购物车。因此你只需计算总价并从信用卡扣除费用即可。但是当到了双十一搞活动的时候,有很多商品的价格需要降价,你可能需要书写新的报价函数,随时的更新价格,但是商品数量那么多,要写这么多函数,那是相当的麻烦的。那么怎么解决这个问题呢?

这个就应用到了多态,你收到一个对象,却根本不知道它是如何实现的,它可能是众多“形态”中的任何一种。你只知道可以询问其价格,但这就够了。至于询问价格的方式,采用固定的方式。

>>> object.get_price()

像这样与对象属性相关联的函数称为方法。你在本书前面见过这样的函数:字符串、列表和字典的方法。多态你其实也见过。

>>> 'abc'.count('a')

>>> [1, 2, 'a'].count('a')

如果有一个变量x,你无需知道它是字符串还是列表就能调用方法count:只要你向这个方法提供一个字符作为参数,它就能正常运行。

下面来做个实验。标准库模块random包含一个名为choice的函数,它从序列中随机选择一个 元素。下面使用这个函数给变量提供一个值。

>>> from random import choice

>>> x = choice(['Hello, world!', [1, 2, 'e', 'e', 4]])

执行这些代码后,x可能包含字符串'Hello,world!',也可能包含列表[1, 2, 'e', 'e', 4]。具体是哪一个,你不知道也不关心。你只关心x包含多少个'e',而不管x是字符串还是列表你都能找到答案。为找到答案,可像前面那样调用count。

>>>x.count('e')

2

从上述结果看,x包含的应该是列表。但关键在于你无需执行相关的检查,只要x有一个名为 count的方法,它将单个字符作为参数并返回一个整数就行。如果有人创建了包含这个方法的对象,你也可以像使用字符串和列表一样使用这种对象。

多态形式多样 每当无需知道对象是什么样的就能对其执行操作时,都是多态在起作用。这不仅仅适用于方法,我们还通过内置运算符和函数大量使用了多态。请看下面的代码:

>>> 1 +2

3

>>>'Fish' + 'license'

'Fishlicense'

上述代码表明,加法运算符(+)既可用于数(这里是整数),也可用于字符串(以及其他类 型的序列)。为证明这一点,假设你要创建一个将两个对象相加的add函数,可像下面这样定义它(这与模块operator中的函数add等价,但效率更低):

def add(x, y):

return x + y

可使用众多不同类型的参数来调用这个函数。

>>> add(1, 2)

3

>>>add('Fish', 'license')

'Fishlicense'

这也许有点傻,但重点在于参数可以是任何支持加法的对象①。如果要编写一个函数,通过打印一条消息来指出对象的长度,可以像下面这样做(它对参数的唯一要求是有长度,可对其执行函数len)。

def length_message(x):

print("The length of", repr(x),"is", len(x))

如你所见,这个函数还使用了repr。repr是多态的集大成者之一,可用于任何对象,下面就 来看看:

>>> length_message('Fnord')

The length of 'Fnord' is 5

>>> length_message([1, 2, 3])

The length of[1, 2, 3] is 3

很多函数和运算符都是多态的,你编写的大多数函数也可能如此,即便你不是有意为之。每当你使用多态的函数和运算符时,多态都将发挥作用。事实上,要破坏多态,唯一的办法是使用诸如type、issubclass等函数显式地执行类型检查,但你应尽可能避免以这种方式破坏多态。重要的是,对象按你希望的那样行事,而非它是否是正确的类型(类)。然而,不要使用类型检查的禁令已不像以前那么严格。引入本章后面将讨论的抽象基类和模块abc后,函数issubclass本身也是多态的了。

2、封装

封装(encapsulation)指的是向外部隐藏不必要的细节。这听起来有点像多态(无需知道对象的内部细节就可使用它)。这两个概念很像,因为它们都是抽象的原则。它们都像函数一样,可帮助你处理程序的组成部分,让你无需关心不必要的细节。

但封装不同于多态。多态让你无需知道对象所属的类(对象的类型)就能调用其方法,而封装让你无需知道对象的构造就能使用它。听起来还是有点像?下面来看一个使用了多态但没有使用封装的示例。假设你有一个名为OpenObject的类(如何创建类将在本章后面介绍)。

>>> o = OpenObject() # 对象就是这样创建的

>>> o.set_name('Sir Lancelot')

>>> o.get_name()

'Sir Lancelot'

你(通过像调用函数一样调用类)创建一个对象,并将其关联到变量o,然后就可以使用方法set_name和get_name了(假设OpenObject支持这些方法)。一切都看起来完美无缺。然而,如果 o将其名称存储在全局变量global_name中呢?

>>> global_name

'Sir Lancelot'

这意味着使用OpenObject类的实例(对象)时,你需要考虑global_name的内容。事实上,必须确保无人能修改它。

>>>global_name = 'Sir Gumby'

>>>o.get_name() 'Sir Gumby'

如果尝试创建多个OpenObject对象,将出现问题,因为它们共用同一个变量。

>>> o1 = OpenObject()

>>> o2 = OpenObject()

>>> o1.set_name('Robin Hood')

>>> o2.get_name() 'Robin Hood'

如你所见,设置一个对象的名称时,将自动设置另一个对象的名称。这可不是你想要的结果。

基本上,你希望对象是抽象的:当调用方法时,无需操心其他的事情,如避免干扰全局变量。如何将名称“封装”在对象中呢?没问题,将其作为一个属性即可。属性是归属于对象的变量,就像方法一样。实际上,方法差不多就是与函数相关联的属性(7.2.3节将介绍方法和函数之间的一个重要差别)。如果你使用属性而非全局变量重新编写前面的类,并将其重命名为ClosedObject,就可像下面这样使用它:

>>> c =ClosedObject()

>>>c.set_name('Sir Lancelot')

>>>c.get_name()

'Sir Lancelot'

到目前为止一切顺利,但这并不能证明名称不是存储在全局变量中的。下面再来创建一个对象。

>>> r =ClosedObject()

>>>r.set_name('Sir Robin')

r.get_name()

'Sir Robin'

从中可知正确地设置了新对象的名称(这可能在你的意料之中),但第一个对象现在怎么样了呢?

>>>c.get_name()

'Sir Lancelot'

其名称还在!因为这个对象有自己的状态。对象的状态由其属性(如名称)描述。对象的方法可能修改这些属性,因此对象将一系列函数(方法)组合起来,并赋予它们访问一些变量(属性)的权限,而属性可用于在两次函数调用之间存储值。

3、继承

继承是另一种偷懒的方式(这里是褒义)。程序员总是想避免多次输入同样的代码。本书前 面通过创建函数来达成这个目标,但现在要解决一个更微妙的问题。如果你已经有了一个类,并 要创建一个与之很像的类(可能只是新增了几个方法),该如何办呢?创建这个新类时,你不想 复制旧类的代码,将其粘贴到新类中。

例如,你可能已经有了一个名为Shape的类,它知道如何将自己绘制到屏幕上。现在你想创 建一个名为Rectangle的类,但它不仅知道如何将自己绘制到屏幕上,而且还知道如何计算其面 积。你不想重新编写方法draw,因为Shape已经有一个这样的方法,且效果很好。那么该如何办 呢?让Rectangle继承Shape的方法,使得对Rectangle对象调用方法draw时,将自动调用Shape类 的这个方法。

二、什么是类?

类的定义是一种对象。每个对象都属于特定的类,并被称为该类的实例。 例如,如果你在窗外看到一只鸟,这只鸟就是“鸟类”的一个实例。鸟类是一个非常通用(抽 象)的类,它有多个子类:你看到的那只鸟可能属于子类“云雀”。你可将“鸟类”视为由所有 鸟组成的集合,而“云雀”是其一个子集。一个类的对象为另一个类的对象的子集时,前者就是后者的子类。因此“云雀”为“鸟类”的子类,而“鸟类”为“云雀”的超类。

通过这样的陈述,子类和超类就很容易理解。但在面向对象编程中,子类关系意味深长,因 为类是由其支持的方法定义的。类的所有实例都有该类的所有方法,因此子类的所有实例都有超 类的所有方法。有鉴于此,要定义子类,只需定义多出来的方法(还可能重写一些既有的方法)。

例如,Bird类可能提供方法fly,而Penguin类(Bird的一个子类)可能新增方法eat_fish。 创建Penguin类时,你还可能想重写超类的方法,即方法fly。鉴于企鹅不能飞,因此在Penguin 的实例中,方法fly应什么都不做或引发异常。

三、如何创建类

下面是一个简单的示例: __metaclass__ = type # 如果你使用的是Python 2,请包含这行代码

class Person:

def set_name(self, name):

self.name = name

def get_name(self):

return self.name

def greet(self):

print("Hello, world! I'm {}.".format(self.name))

这个示例包含三个方法定义,它们类似于函数定义,但位于class语句内。Person当然是类的名称。class语句创建独立的命名空间,用于在其中定义函数。一切看起来都挺 好,但你可能想知道参数self是什么。它指向对象本身。那么是哪个对象呢?下面通过创建两个 实例来说明这一点。

>>> foo = Person()

>>> bar = Person()

>>> foo.set_name('Luke Skywalker')

>>> bar.set_name('Anakin Skywalker')

>>> foo.greet() Hello, world! I'm Luke Skywalker.

>>> bar.greet()

Hello, world! I'm Anakin Skywalker.

这个示例可能有点简单,但澄清了self是什么。对foo调用set_name和greet时,foo都会作为 第一个参数自动传递给它们。我将这个参数命名为self,这非常贴切。实际上,可以随便给这个 参数命名,但鉴于它总是指向对象本身,因此习惯上将其命名为self。

显然,self很有用,甚至必不可少。如果没有它,所有的方法都无法访问对象本身——要操 作的属性所属的对象。与以前一样,也可以从外部访问这些属性。

>>> foo.name 'Luke Skywalker'

>>> bar.name = 'Yoda'

>>> bar.greet()

Hello, world! I'm Yoda

四、总结

本节主要介绍了,python的多态和方法、继承、封装三大特性,然后介绍了什么是python的类,最后介绍了python如创建类。下次,小小白将介绍如何关于python类的一些主要特性。Always remember you should be coding~~~

参考文献:

[1]Beginning Python From Novice to Professional (Third Edition),Magnus Lie

Hetland.

希望上述内容能够帮助到正在学习的你~

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-09-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 AINLPer 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档