12. Python语言的核心编程 · 第十二章 面向对象(中)

Pythin语言的面向对象(中)

  • 1. 类的特殊方法
  • 2. 封装的引入
  • 3. 封装
  • 4. property装饰器
  • 总结小便条

  上一篇文章 11. Pythin语言的面向对象(上) 我们详细的介绍了 什么是 面向对象、面向对象编程思想的 演化过程 以及 面向对象编程的 优缺点

  接下来呢,我们详细的介绍了 什么是 类(class)、类 怎么定义 以及 类 中的 属性 和 方法 的 优先级 和各种 存放特点

  最后呢,我们详细的介绍了 对象的创建流程 以及 我们在自定义类中创建方法函数时自动出现的 形参self 又是怎么个一回事。

  我们用一段 简单的代码 整合一下我们上一篇文章的内容。

  参考实例:

class Person():
	name = '钢铁侠'

	def speak(self):
		print('你好,我是%s'% self.name)

p_1 = Person()
p_2 = Person()
p_3 = Person()

p_1.name = '蜘蛛侠'
p_2.name = '绿巨人'

p_1.speak()				# 你好,我是蜘蛛侠
p_2.speak()				# 你好,我是绿巨人
p_3.speak()				# 你好,我是钢铁侠

  接下来就开始对今天内容的讲解:

  

1. 类的特殊方法

  在开始介绍 类的特殊方法 前,我先引一下 上一篇文章 11. Pythin语言的面向对象(上)第5块内容 类中的属性和方法 最后的总结:
  对于 属性方法 一般情况下 的 定义思路:
  1) 类对象实例对象都可以保存 属性方法
  2) 如果这个 属性方法所以实例 共享 的,则应该将其保存到 类对象 中;
  3) 如果这个 属性方法某个实例所 独有 的,则应该将其保存到 实例对象 中。
  4) 一般情况下属性 保存到 实例对象 中,实例对象.属性 = 'xxx' ;而 方法 需要保存在 类对象 中。

  当时,我们得出的这个 总结 是因为我们发现: 中所有的 属性 和 方法 都是 共享,所有的 实例对象 都能访问到 被实例的 中的所有内容

  属性 一般是不需要被共享的。

  对象 都是对 现实生活中事物抽象

  所以,也才会有最后的一句话:一般情况下属性 保存到 实例对象 中,实例对象.属性 = 'xxx' ;而 方法 需要保存在 类对象 中。

  假如没有这最后一条的限定,那么我们在调用用对象中的方法函数时,最后的结果就如下。 参考实例:

class Person():
	name = '钢铁侠'

	def speak(self):
		print('你好,我是%s'% self.name)

p_1 = Person()
p_2 = Person()

p_1.speak()				# 你好,我是钢铁侠
p_2.speak()				# 你好,我是钢铁侠

  这也相当于是我们间接的把 自定义类写死 了。

  参考实例:

class Person():
	# name = '钢铁侠'

	def speak(self):
		print('你好,我是%s'% self.name)

p_1 = Person()
p_2 = Person()

p_1.speak()				# AttributeError: 'Person' object has no attribute 'name'
p_2.speak()				# AttributeError: 'Person' object has no attribute 'name'

  参考实例:

class Person():
	# name = '钢铁侠'

	def speak(self):
		print('你好,我是%s'% self.name)

p_1 = Person()
p_2 = Person()

p_1.name = '蜘蛛侠'
p_2.name = '绿巨人'

p_1.speak()				# 你好,我是蜘蛛侠
p_2.speak()				# 你好,我是绿巨人

  参考实例:

class Person():
	# name = '钢铁侠'

	def speak(self):
		print('你好,我是%s'% self.name)

p_1 = Person()
p_2 = Person()
p_3 = Person()

p_1.name = '蜘蛛侠'
p_2.name = '绿巨人'

p_1.speak()				# 你好,我是蜘蛛侠
p_2.speak()				# 你好,我是绿巨人
p_3.speak()				# AttributeError: 'Person' object has no attribute 'name'

  通过观察,我们发现了一些问题:
    1) 对于我们 Person 来说,name属性 是必须的;
    2) name属性 是必须不同的。

  此时,我们该怎么做?

  我们希望:在创建对象时,必须 设置创建 相对属性,如果不设置,实例对象无法创建

  属性 的创建,应该是自动完成的;而不应该由我们 **一个一个 手动**的添加的。

  基于案例:我们希望 。。。

  此时,有一个新问题来了:我们怎么知道,我们什么时候需要 创建对象 呢?

  到此呢,正式的开始我们今天的第一块内容:在 中,我们可以定义一些 特殊方法,这种 特殊方法 也称为 魔术方法
  特殊方法 都是形如 __xxx__() 这种形式的;
  特殊方法 的特点就是 不需要 我们自己调用,特殊方法 会在 特定时候 自动调用

  接下来,我们通过一系列的 参考实例 逐步的 讲解推衍 什么是 中的 特殊方法 ,或者说是 魔术方法

  接下来呢我们就以一个 非常非常特殊特殊方法 __init__() 这个 特殊方法(构造方法) 为例,开始 我们的 表演

  参考实例:

class Person():
	def __init__(self):
		pass

	def speak(self):
		print('你好,我是%s'% self.name)

p_1 = Person()

p_1.__init__()				# 

  先不管你是 什么方法,只要是个 方法,我们的第一个反应应该是什么?是不是 可以调用 啊。可是,虽然最后的结果 没报错,但是最后为什么没任何的 输出结果 呀?

  我们把 pass这个 站位语句 换成一个 输出语句print('Hello') 来看看。

  参考实例:

class Person():
	def __init__(self):
		print('Hello')

	def speak(self):
		print('你好,我是%s'% self.name)

p_1 = Person()				# Hello

p_1.__init__()				# Hello

  咦,我明明只是调用了一次,为什么最后 控制台 会返回 两个值为什么会这样呢?

  此时,回看我们在前面说过的一句话:特殊方法 的特点就是 不需要 我们自己调用,特殊方法 会在 特定时候 自动调用。所以,就是不用再特意再去调用一次 特殊方法;这么去做,没什么意义。

  此时,我想各位读者朋友心里会有一个疑问了,你们一定会问:小编小编。你让我定义了一个方法,可是你又不让我去调;那我定义它干嘛呀?那我们先来看下下面的 参考实例

  参考实例:

class Person():
	def __init__(self):
		print('init方法执行了。')

	def speak(self):
		print('你好,我是%s'% self.name)

p_1 = Person()				# init方法执行了。

  此时,读者朋友你们是不是发现:小编我没有调用 方法def __init__(self),但是感觉又好像调用了 方法def __init__(self)。那此时,我来问一下大家:是谁 调用了 方法def __init__(self)
  是谁?对象吗?不是的。其实是 Python语言的解析器 在调用的。特殊方法 会在 特定时候 自动调用。所以,就是不用再特意再去调用一次 特殊方法;这么去做,没什么意义。

  特殊方法 太多太多了,读者朋友你只要打开 PyCharm 编辑器,新建一个 .py文件,打开新建好的 .py文件创建一个类class Person():,在这个你自定义的类里输入 def __,你就会发现出现很多很多的 特殊方法。这么多的 特殊方法 很多小编我之计也还没捯饬清楚,所以也无法在这里为大家意义的介绍,在这里呢直接能简单的罗列一些常用的 特殊方法 以及一个 Python 的特殊方法详解 的链接给大家提供参考。

类的专有方法 功能作用
__init__ 构造函数,在生成对象时调用
__del__ 析构函数,释放对象时使用
__repr__ 打印,转换
__setitem__ 按照索引赋值
__getitem__ 按照索引获取值
__len__ 获得长度
__cmp__ 比较运算
__call__ 函数调用
__add__ 加运算
__sub__ 减运算
__mul__ 乘运算
__truediv__ 除运算
__mod__ 求余运算
__pow__ 乘方

  不过大家对 特殊方法 还是有很多不明白,那也是太正常的了。对于 特殊方法 的学习,更合适的学习方法是在 不断的尝试与实践中。 而不是简简单单的看几篇文章就能全部的解释通透的,学习 编程 呢也是一样的道理。

  不过呢,对于 特殊方法 这一类的方法我们又该怎么学习呢?我只需要掌握两个 通俗的规律 就可以很快速的学习了:
    1) 特殊方法什么时候调用;
    2) 特殊方法有什么作用。

  介绍完了 学习方法,现在我们以研究学习 __init__() 这个特殊方法(构造方法) 为例:
我们将原有的 实例对象p_1 = Person(),现在再添加3个,分别是:p_2 = Person()p_3 = Person()p_4 = Person();然后呢,我们将它们全部都执行一遍:

  参考实例:

class Person():
	def __init__(self):
		print('init方法执行了。')

	def speak(self):
		print('你好,我是%s'% self.name)

p_1 = Person()				# init方法执行了。
p_2 = Person()				# init方法执行了。
p_3 = Person()				# init方法执行了。
p_4 = Person()				# init方法执行了。

  在上面的 参考实例 中,我们创建了 4个 实例对象;运行程序后,在控制台里返回了 4个 相同的值 init方法执行了。

  通过最后的结果我们发现:我每创建一个 实例对象,就等于是执行一次__init__方法。我们完全可以这么理解。

  这里呢,我们来说一下 实例化对象p1 = Person() 整个的一个创建流程:
    1. 创建一个变量 (p1,p2,p3…);
    2. 在内存中创建一个新的对象;
    3.__init__(self) 方法执行了。

  此时,又一个 程序运行优先级 问题出现了:

  之前说过:类对象实例对象都可以保存 属性方法。此时,我们除了定义一些 特殊方法代码块 外是不是还可以定义一些 属性

  先来一个 参考实例:

class Person():
	a = 10
	b = 20
	
	print('Person类中代码块的代码。')

	def __init__(self):
		print('init方法执行了。')

	def speak(self):
		print('你好,我是%s'% self.name)

p_1 = Person()
p_2 = Person()
p_3 = Person()
p_4 = Person()

  那么,我们问:在我们的 自定义类Person() 里,是我们添加的 代码块 先执行 呢?还是我们在 自定义类 里的定义的 __init__(self):特殊方法 先执行 呢?谁先执行呢?

  如果没明白我问的问题,那我换个表达问题的方式:在我们的 自定义类Person() 里,print('Person类中代码块的代码。')def __init__(self): 这两个谁 先执行def __init__(self): 吗?

  在这里呢,小编我多一句嘴:在我们 学习编程 或者 参与项目开发 的时候,遇到问题正常的,当我们遇到 不懂、不明白、不理解 的问题时候,我们可以 不懂、不明白、不理解,我们需要的是 及时询问、及时上网进行搜索、及时进行打印测试,不可以 猜!!! 在我们 学习编程 或者 参与项目开发 的时候 最最最最最忌讳 的就是 。我们可以有几个 预期值,但 就是 不自信、不确定风险 的代名词;而且 抗拒猜 也是我们 自我认知 成长的一个过程。

  现在我们一起来打印一下 参考实例 最后输出的

class Person():
	
	print('Person类中代码块的代码。')

	def __init__(self):
		print('init方法执行了。')

	def speak(self):
		print('你好,我是%s'% self.name)

p_1 = Person()
p_2 = Person()
p_3 = Person()
p_4 = Person()

# Person类中代码块的代码。
# init方法执行了。
# init方法执行了。
# init方法执行了。
# init方法执行了。

  看到这个输出结果,我想有的读者朋友一定会说:咦,为什么 同样次数的 实例调用,为什么 特殊方法 def __init__(self): 执行了 4次,而代码块 print('Person类中代码块的代码。') 却只执行了 1次?而且,为什么 最先执行 的是 代码块 print('Person类中代码块的代码。') ?这个是不是和它 摆放的位置 有关系啊?

  别的问题我们先放在一边,我们先 重新调整 一下 代码块 print('Person类中代码块的代码。') 的位置,先解开 摆放位置 是否会影响 代码块 print('Person类中代码块的代码。') 执行的次数 这个问题。

  参考实例:

class Person():
	
	def __init__(self):
		print('init方法执行了。')

	print('Person类中代码块的代码。')

	def speak(self):
		print('你好,我是%s'% self.name)

p_1 = Person()
p_2 = Person()
p_3 = Person()
p_4 = Person()

# Person类中代码块的代码。
# init方法执行了。
# init方法执行了。
# init方法执行了。
# init方法执行了。

  请各位读者朋友 仔细看 控制台 最后打印出来的结果。我们可以确定:摆放位置 不会影响 代码块 print('Person类中代码块的代码。') 执行的 顺序 和 次数。那又是什么影响到了 代码块 print('Person类中代码块的代码。') 执行的 顺序 和 次数

  这里呢,我们再来 完善 一下 实例化对象p1 = Person() 整个的一个 创建执行 流程:
    1. 创建一个变量 (p1,p2,p3…);
    2. 在内存中创建一个新的对象;
    3. 先执行 类 中的代码块,只执行一次
    4. 执行 特殊方法 __init__(self)

  说完了 实例化对象 整个的一个 创建执行 流程,我们重新回到:特殊方法什么时候调用 以及 这特殊方法有什么作用。这个问题。那我们现在先来看看 __init__方法 什么时候调用__init__方法在 实例对象 创建以后 执行。知道了 __init__方法 什么时候调用,我们再来讨论一下 __init__方法 有什么作用

  我们先删掉 自定义类 中的 代码块print('Person类中代码块的代码。'),然后我们来打印一下特殊方法__init__(self) 里的 self,一起来看下最后的结果。

  参考实例:

class Person():
	
	def __init__(self):
		self.name = '葫芦娃'
		print(self)

	def speak(self):
		print('你好,我是%s'% self.name)

p_1 = Person()				# <__main__.Person object at 0x000001E47DDF8400>
p_2 = Person()				# <__main__.Person object at 0x000001E47DE0E430>
p_3 = Person()				# <__main__.Person object at 0x000001E47DE98490>
p_4 = Person()				# <__main__.Person object at 0x000001E47DE98670>

  先解释一下,在上面的 参考实例 中,我们所进行的操作具体的作用是什么:首先 代码 self.name = '葫芦娃' 的作用是 通过 self 向新创建的对象中初始化属性

  对上面的 参考实例 做些微的调整:

class Person():
	
	def __init__(self):
		# 通过 self 向新创建的对象中初始化属性。
		self.name = '葫芦娃'

	def speak(self):
		print('你好,我是%s'% self.name)

p_1 = Person()			

p_1.speak()				# 你好,我是葫芦娃

  看完上面修改后的 参考实例 以及 参考实例最后的 输出结果,我想,此时一定会有读者朋友大声的质问:实例化对象p_1 中没有name属性,为什么最后却会输出 你好,我是葫芦娃 这个结果?

  OK,我们现在就来解释一下:虽然在 实例化对象p_1 中没有name属性,但是我们是不是在 自定义类 中的 特殊方法__init__(self) 里的添加了一个 self.name属性;这就相当于在 当前的 实例化对象 中添加了一个 name属性。此时,我们再回看上面 未修改前的 参考实例,现在我们就知道最后输出的 四个对象 究竟是什么了吧。

  那我们再把最后的打印结果 p_1.speak() 改为 p_2.speak(),此时我们再来看看,参考实例 最后又会输出什么结果呢:

class Person():
	
	def __init__(self):
		# 通过 self 向新创建的对象中初始化属性。
		self.name = '葫芦娃'

	def speak(self):
		print('你好,我是%s'% self.name)

p_1 = Person()	
p_2 = Person()		

p_2.speak()				# 你好,我是葫芦娃

  也是 你好,我是葫芦娃。虽然最后输出的结果没什么毛病,但读者朋友们,你们有没有发现:我们测试了那么多的代码,小编我也写了那么多的内容,可是现在这最后的 输出结果的 效果 似乎又回到了我们开篇的那个 参考实例

  我们前面说过:对于 自定义类Person 来说 name属性 是必须要有的;但是每个 name属性 又都是不同的。而且,刚开始的时候,我们是以 手动 的方式保持了程序 最后的输出结果不同

  现在我们的程序 最后的输出结果 却是又无法由 代码的使用者 自定义 的;换个 表达方式 就是:我们把 特殊方法def __init__(self): 里的内容写死了。此时这个问题,我们该怎么解决呢?我们是不是可以把 属性self.name =形参 的形式传递进去。那我们要怎么定义这个 形参 呢?

  参考实例: 我们先在 特殊方法def __init__(self): 里添加一个 形参 尝试着看下最后的输出结果。

class Person():
	
	def __init__(self, name):
		self.name = name

	def speak(self):
		print('你好,我是%s'% self.name)

p_1 = Person()		

p_1.speak()				# TypeError: __init__() missing 1 required positional argument: 'name'

  程序执行后报了这么一个错:TypeError: __init__() missing 1 required positional argument: 'name'属性错误: 特殊方法__init__ 缺少一个 位置参数: 'name'。前面,我们为了保证 最后的输出结果不同 是以 手动 的方式修改程序里的 name属性 的。如果我们在调用程序的时候,不小心 在最后没有设置 name属性,最后是不是报 AttributeError: 'Person' object has no attribute 'name' 这个错误;而现在在我们使用了 特殊方法__init__ 后是 实例对象p_1 在没有设置 name属性 时,直接就给我们报了 属性错误

  那这个 属性错误 背后的意义有什么呢?这个 属性错误 背后的意义就是:在你 实例对象 的时候必须要创建 name属性 这么一个参数。强制性的需要你加上一个 name属性

  即然 Python编译器 强制性的需要我们加上一个 name属性,那我们就先加上一个 name属性 先看看最后的结果

class Person():
	
	def __init__(self, name):
		self.name = name

	def speak(self):
		print('你好,我是%s'% self.name)

p_1 = Person('葫芦娃')		
print(p_1.name)				# 葫芦娃

  哎哟,竟然是 葫芦娃 。虽然最后的 输出结果 不是我们最期望的答案;不过呢,最后也是输出了一个 结果很好 的答案。

  现在呢,我们再来创建三个 实例对象,本别是 p_2p_3p_4

  参考实例:

class Person():
	
	def __init__(self, name):
		self.name = name

	def speak(self):
		print('你好,我是%s'% self.name)

p_1 = Person('葫芦娃')		
print(p_1.name)				# 葫芦娃
p_2 = Person('钢铁侠')		
print(p_2.name)				# 钢铁侠
p_3 = Person('绿巨人')		
print(p_3.name)				# 绿巨人
p_4 = Person('美国队长')		
print(p_4.name)				# 美国队长

  此时,我们再让 控制台 说出那句话:

  参考实例:

class Person():
	
	def __init__(self, name):
		self.name = name

	def speak(self):
		print('你好,我是%s'% self.name)

p_1 = Person('葫芦娃')		
p_1.speak()					# 你好,我是葫芦娃
p_2 = Person('钢铁侠')		
p_2.speak()					# 你好,我是钢铁侠
p_3 = Person('绿巨人')		
p_3.speak()					# 你好,我是绿巨人
p_4 = Person('美国队长')		
p_4.speak()					# 你好,我是美国队长

  现在,我们是不是只需要在 创建 实例对象 时添加上 自定义类 里相对应的 属性值,然后再直接的调用相对应的 方法 就可以使用了。不需要在 创建好 实例对象 后再向 实例对象 里 创建 属性 以及 相对应的属性值,最后再 调用方法 输出结果。

  这么使用 特殊方法减少代码量,又 确保 了代码的 安全性,而且还 减少 了在 后期维护 和 修改 时出现 误操作 的可能性。

  

2. 封装的引入

  

3. 封装

• 出现封装的原因:我们需要一种方式来增强数据的安全性
• 1. 属性不能随意修改
• 2. 属性不能改为任意的值
• 封装是面向对象的三大特性之一
• 封装是指隐藏对象中一些不希望被外部所访问到的属性或方法
• 我们也可以提供给一个getter()和setter()方法是外部可以访问到属性
• getter() 获取对象中指定的属性
• setter() 用来设置对象指定的属性
• 使用封装,确实增加了类的定义的复杂程度,但是它也确保了数据的安全
• 1. 隐藏属性名,使调用这无法随意的修改对象中的属性
• 2. 增加了getter()和setter()方法,很好控制属性是否是只读的
• 3. 使用setter()设置属性,可以在呢及数据的验证
• 4. 使用getter()方法获取属性,使用setter()方法设置属性可以在读取属性和修改属性的同时做一些其他的处理
• 可以为对象的属性使用双下划线开头 __xxx。双下划线开头的属性,是对象的隐藏属性,隐藏属性只能在类的内部访问,无法通过对象访问
• 其实隐藏属性只不过是Python自动为属性改了一个名字 --> _类名__属性名 例如 __name -> _Person__name
• 这种方式实际上依然可以在外部访问,所以这种方式我们一般不用。一般我们会将一些私有属性以_开头
• 一般情况下,使用_开头的属性都是私有属性,没有特殊情况下不要修改私有属性

  

4. property装饰器

• 我们可以使用@property装饰器来创建只读属性,@property装饰器会将方法转换为相同名称的只读属性,可以与所定义的属性配合使用,这样可以防止属性被修改

  
  
  

总结小便条

本篇文章主要讲了以下几点内容:

  

  本章回顾暂时就到这了,如果还有点晕,那就把文章里所有引用的案例代码再敲几遍吧。拜拜~

你可能感兴趣的:(Python,python)