初识面向对象编程(Object Oriented Programming,OOP)

欢迎关注WX公众号:【程序员管小亮】

前言

最近看了好多关于类和对象的代码,看的头晕眼花,不知所云,可能是这块学的不好的原因吧,所以准备好好学一下面向对象。

既作为笔记记录下自己的学习过程,也作为参考留着以后查看。

一、简介

面向对象编程是最有效的软件编写方法之一。在面向对象编程中,你编写表示现实世界中的事物和情景的类,并基于这些类来创建对象。编写类时,你定义一大类对象都有的通用行为。基于类创建对象时,每个对象都自动具备这种通用行为,然后可根据需要赋予每个对象独特的个性。使用面向对象编程可模拟现实情景,其逼真程度达到了令你惊讶的地步。

根据类来创建对象被称为实例化 ,这让你能够使用类的实例。理解面向对象编程有助于你像程序员那样看世界,还可以帮助你真正明白自己编写的代码:不仅是各行代码的作用,还有代码背后更宏大的概念。了解类背后的概念可培养逻辑思维,让你能够通过编写程序来解决遇到的几乎任何问题。

随着面临的挑战日益严峻,类还能让你以及与你合作的其他程序员的生活更轻松。如果你与其他程序员基于同样的逻辑来编写代码,你们就能明白对方所做的工作;你编写的程序将能被众多合作者所理解,每个人都能事半功倍。

  • 类(Class): 用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。
  • 方法:类中定义的函数。
  • 类变量:类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体之外。类变量通常不作为实例变量使用。
  • 数据成员:类变量或者实例变量用于处理类及其实例对象的相关的数据。
  • 方法重写:如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖(override),也称为方法的重写。
  • 局部变量:定义在方法中的变量,只作用于当前实例的类。
  • 实例变量:在类的声明中,属性是用变量来表示的。这种变量就称为实例变量,是在类声明的内部但是在类的其他成员方法之外声明的。
  • 继承:即一个派生类(derived class)继承基类(base class)的字段和方法。继承也允许把一个派生类的对象作为一个基- 类对象对待。例如,有这样一个设计:一个Dog类型的对象派生自Animal类,这是模拟"是一个(is-a)"关系(例图,Dog是一个Animal)。
  • 实例化:创建一个类的实例,类的具体对象。
  • 对象:通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法。

Python中的类提供了面向对象编程的所有基本功能:类的继承机制允许多个基类,派生类可以覆盖基类中的任何方法,方法中可以调用基类中的同名方法。

对象可以包含任意数量和类型的数据。

二、类

类的定义:

'''
class 类名:
	'类的文档字符串'
    类体
'''

#我们创建一个类
class Data:
	pass

声明类

函数的定义:

def functionName(args):
	'函数文档字符串'
	函数体 

在这里插入图片描述

2.1、创建和使用类

使用类几乎可以模拟任何东西。下面来编写一个表示小狗的简单类 Dog——它表示的不是特定的小狗,而是任何小狗。对于大多数宠物狗,我们都知道些什么呢?它们都有名字和年龄;我们还知道,大多数小狗还会蹲下和打滚。由于大多数小狗都具备上述两项信息(名字和年龄)和两种行为(蹲下和打滚),我们的 Dog 类将包含它们。这个类让 python 知道如何表示小狗的对象。编写这个类后,我们将使用它来创建表示特定小狗的实例。

2.1.1、创建 Dog 类

根据 Dog 类创建的每个实例都将存储名字和年龄。我们赋予了每条小狗蹲下(sit())和打滚(roll_over())的能力;

class Dog():
	""" 一次模拟小狗的简单尝试 """
	
	def __init__(self, name, age):
		""" 初始化属性 name 和 age"""
		self.name = name
		self.age = age
		
	def sit(self):
		""" 模拟小狗被命令时蹲下 """
		print(self.name.title() + " is now sitting.")
		
	def roll_over(self):
		""" 模拟小狗被命令时打滚 """
		print(self.name.title() + " rolled over!")

根据约定,在 Python 中,首字母大写的名称指的是类。这个类定义中的括号是空的,因为我们要从空白创建这个类。

类中的函数称为方法 ;你学到的有关函数的一切都适用于方法,就目前而言,唯一重要的差别是调用方法的方式。方法 __init__() 是一个特殊的方法,每当你根据 Dog 类创建新实例时, Python 都会自动运行它。在这个方法的名称中,开头和末尾各有两个下划线,这是一种约定,旨在避免 Python 默认方法与普通方法发生名称冲突。

我们将方法 __init__() 定义成了包含三个形参selfnameage 。在这个方法的定义中,形参 self 必不可少,还必须位于其他形参的前面。为何必须在方法定义中包含形参 self 呢?因为 Python 调用这个 __init__() 方法来创建 Dog 实例时,将自动传入实参 self每个与类相关联的方法调用都自动传递实参 self ,它是一个指向实例本身的引用,让实例能够访问类中的属性和方法。我们创建 Dog 实例时, Python 将调用 Dog 类的方法 __init__() 。我们将通过实参向 Dog() 传递名字和年龄; self 会自动传递,因此我们不需要传递它。每当我们根据 Dog 类创建实例时,都只需给最后两个形参( nameage )提供值。

self.nameself.age 是两个定义的变量,都有前缀 selfself 为前缀的变量都可供类中的所有方法使用,我们还可以通过类的任何实例来访问这些变量self.name = name 获取存储在形参 name 中的值,并将其存储到变量 name 中,然后该变量被关联到当前创建的实例。 self.age = age 的作用与此类似。像这样可通过实例访问的变量称为属性

Dog 类还定义了另外两个方法: sit()roll_over() 。由于这些方法不需要额外的信息,如名字或年龄,因此它们只有一个形参 self 。我们后面将创建的实例能够访问这些方法,换句话说,它们都会蹲下和打滚。当前, sit()roll_over() 所做的有限,它们只是打印一条消息,指出小狗正蹲下或打滚。但可以扩展这些方法以模拟实际情况:如果这个类包含在一个计算机游戏中,这些方法将包含创建小狗蹲下和打滚动画效果的代码。如果这个类是用于控制机器狗的,这些方法将引导机器狗做出蹲下和打滚的动作。

一:我们定义的类的属性到底存到哪里了?有两种方式查看:

dir(类名):查出的是一个名字列表

类名.__dict__:查出的是一个字典,key为属性名,value为属性值

二:特殊的类属性

类名.__name__# 类的名字(字符串)
类名.__doc__# 类的文档字符串
类名.__base__# 类的第一个父类(在讲继承时会讲)
类名.__bases__# 类所有父类构成的元组(在讲继承时会讲)
类名.__dict__# 类的字典属性
类名.__module__# 类定义所在的模块
类名.__class__# 实例对应的类(仅新式类中)
2.1.2、根据类创建实例

可将类视为有关如何创建实例的说明。 Dog 类是一系列说明,让 Python 知道如何创建表示特定小狗的实例。

下面来创建一个表示特定小狗的实例:

my_dog = Dog('willie', 6)
	
print("My dog's name is " + my_dog.name.title() + ".")
print("My dog is " + str(my_dog.age) + " years old.")

这里使用的是前一个示例中编写的 Dog 类。我们让 Python 创建一条名字为 ‘willie’ 、年龄为 6 的小狗。遇到这行代码时, Python 使用实参 'willie'6 调用 Dog 类中的方法 __init__() 。方法 __init__() 创建一个表示特定小狗的示例,并使用我们提供的值来设置属性 nameage 。方法 __init__() 并未显式地包含 return 语句,但 Python 自动返回一个表示这条小狗的实例。我们将这个实例存储在变量 my_dog 中。

在这里,命名约定很有用:我们通常可以认为首字母大写的名称(如 Dog )指的是类,而小写的名称(如 my_dog )指的是根据类创建的实例

  1. 访问属性

要访问实例的属性,可使用句点表示法。我们编写了如下代码来访问 my_dog 的属性 name 的值:

my_dog.name

句点表示法在 Python 中很常用,这种语法演示了 Python 如何获悉属性的值。在这里, Python 先找到实例 my_dog ,再查找与这个实例相关联的属性 name 。在 Dog 类中引用这个属性时,使用的是 self.name 。在后面的代码中,我们使用同样的方法来获取属性 age 的值。在前面的第 1 条 print 语句中, my_dog.name.title()my_dog 的属性 name 的值 'willie' 改为首字母大写的;在第 2 条 print 语句中, str(my_dog.age)my_dog 的属性 age 的值 6 转换为字符串。

输出是有关 my_dog 的摘要:

My dog's name is Willie.
My dog is 6 years old.
  1. 调用方法

根据 Dog 类创建实例后,就可以使用句点表示法来调用 Dog 类中定义的任何方法。下面来让小狗蹲下和打滚:

my_dog = Dog('willie', 6)
my_dog.sit()
my_dog.roll_over()

要调用方法,可指定实例的名称(这里是 my_dog )和要调用的方法,并用句点分隔它们。遇到代码 my_dog.sit() 时, Python 在类 Dog 中查找方法 sit() 并运行其代码。Python 以同样的方式解读代码 my_dog.roll_over()

Willie 按我们的命令做了:

Willie is now sitting.
Willie rolled over!

这种语法很有用。如果给属性和方法指定了合适的描述性名称,如 nameagesit()roll_over() ,即便是从未见过的代码块,我们也能够轻松地推断出它是做什么的。

  1. 创建多个实例

可按需求根据类创建任意数量的实例。下面再创建一个名为 your_dog 的实例:

my_dog = Dog('willie', 6)
your_dog = Dog('lucy', 3)
print("My dog's name is " + my_dog.name.title() + ".")
print("My dog is " + str(my_dog.age) + " years old.")
my_dog.sit()
print("\nYour dog's name is " + your_dog.name.title() + ".")
print("Your dog is " + str(your_dog.age) + " years old.")
your_dog.sit()

在这个实例中,我们创建了两条小狗,它们分别名为 Willie 和 Lucy 。每条小狗都是一个独立的实例,有自己的一组属性,能够执行相同的操作:

My dog's name is Willie.
My dog is 6 years old.
Willie is now sitting.
Your dog's name is Lucy.
Your dog is 3 years old.
Lucy is now sitting.

就算我们给第二条小狗指定同样的名字和年龄, Python 依然会根据 Dog 类创建另一个实例。你可按需求根据一个类创建任意数量的实例,条件是将每个实例都存储在不同的变量中,或占用列表或字典的不同位置。

2.2、使用类和实例

你可以使用类来模拟现实世界中的很多情景。类编写好后,你的大部分时间都将花在使用根据类创建的实例上。你需要执行的一个重要任务是修改实例的属性。你可以直接修改实例的属性,也可以编写方法以特定的方式进行修改。

2.2.1、Car 类

下面来编写一个表示汽车的类,它存储了有关汽车的信息,还有一个汇总这些信息的方法:

class Car():
	""" 一次模拟汽车的简单尝试 """
	
	def __init__(self, make, model, year):
		""" 初始化描述汽车的属性 """
		self.make = make
		self.model = model
		self.year = year
		
	def get_descriptive_name(self):
		""" 返回整洁的描述性信息 """
		long_name = str(self.year) + ' ' + self.make + ' ' + self.model
		return long_name.title()

my_new_car = Car('audi', 'a4', 2016)
print(my_new_car.get_descriptive_name())

我们定义了方法 __init__() 。与前面的 Dog 类中一样,这个方法的第一个形参为 self ;我们还在这个方法中包含了另外三个形参: makemodelyear 。方法 __init__() 接受这些形参的值,并将它们存储在根据这个类创建的实例的属性中。创建新的 Car 实例时,我们需要指定其制造商、型号和生产年份。

我们还定义了一个名为 get_descriptive_name() 的方法,它使用属性 yearmakemodel 创建一个对汽车进行描述的字符串,让我们无需分别打印每个属性的值。为在这个方法中访问属性的值,我们使用了 self.makeself.modelself.year

在代码最后两行处,我们根据 Car 类创建了一个实例,并将其存储到变量 my_new_car 中。接下来,我们调用方法get_descriptive_name() ,指出我们拥有的是一辆什么样的汽车:

2016 Audi A4

为让这个类更有趣,下面给它添加一个随时间变化的属性,它存储汽车的总里程。

2.2.2、给属性指定默认值

类中的每个属性都必须有初始值,哪怕这个值是 0 或空字符串。在有些情况下,如设置默认值时,在方法 __init__() 内指定这种初始值是可行的;如果你对某个属性这样做了,就无需包含为它提供初始值的形参。

下面来添加一个名为 odometer_reading 的属性,其初始值总是为 0 。我们还添加了一个名为 read_odometer() 的方法,用于读取汽车的里程表:

class Car():
	def __init__(self, make, model, year):
		""" 初始化描述汽车的属性 """
		self.make = make
		self.model = model
		self.year = year
		self.odometer_reading = 0
		
	def get_descriptive_name(self):
		""" 返回整洁的描述性信息 """
		long_name = str(self.year) + ' ' + self.make + ' ' + self.model
		return long_name.title()
		
	def read_odometer(self):
		""" 打印一条指出汽车里程的消息 """
		print("This car has " + str(self.odometer_reading) + " miles on it.")
		
my_new_car = Car('audi', 'a4', 2016)
print(my_new_car.get_descriptive_name())
my_new_car.read_odometer()

现在,当 Python 调用方法 __init__() 来创建新实例时,将像前一个示例一样以属性的方式存储制造商、型号和生产年份。接下来, Python 将创建一个名为 odometer_reading 的属性,并将其初始值设置为 0 。我们还定义了一个名为read_odometer() 的方法,它让你能够轻松地获悉汽车的里程。

一开始汽车的里程为 0 :

2016 Audi A4
This car has 0 miles on it.

出售时里程表读数为 0 的汽车并不多,因此我们需要一个修改该属性的值的途径。

2.2.3、修改属性的值

可以以三种不同的方式修改属性的值:直接通过实例进行修改;通过方法进行设置;通过方法进行递增(增加特定的值)。下面依次介绍这些方法。

  1. 直接修改属性的值

要修改属性的值,最简单的方式是通过实例直接访问它。下面的代码直接将里程表读数设置为 23 :

class Car():
	def __init__(self, make, model, year):
		""" 初始化描述汽车的属性 """
		self.make = make
		self.model = model
		self.year = year
		self.odometer_reading = 0
		
	def get_descriptive_name(self):
		""" 返回整洁的描述性信息 """
		long_name = str(self.year) + ' ' + self.make + ' ' + self.model
		return long_name.title()
		
	def read_odometer(self):
		""" 打印一条指出汽车里程的消息 """
		print("This car has " + str(self.odometer_reading) + " miles on it.")
		
my_new_car = Car('audi', 'a4', 2016)
print(my_new_car.get_descriptive_name())

my_new_car.odometer_reading = 23
my_new_car.read_odometer()

在倒数第二行处,我们使用句点表示法来直接访问并设置汽车的属性 odometer_reading 。这行代码让 Python 在实例 my_new_car 中找到属性 odometer_reading ,并将该属性的值设置为 23 :

2016 Audi A4
This car has 23 miles on it.

有时候需要像这样直接访问属性,但其他时候需要编写对属性进行更新的方法。

  1. 通过方法修改属性的值

如果有替你更新属性的方法,将大有裨益。这样,你就无需直接访问属性,而可将值传递给一个方法,由它在内部进行更新。

下面的示例演示了一个名为 update_odometer() 的方法:

class Car():
	def __init__(self, make, model, year):
		""" 初始化描述汽车的属性 """
		self.make = make
		self.model = model
		self.year = year
		self.odometer_reading = 0
		
	def get_descriptive_name(self):
		""" 返回整洁的描述性信息 """
		long_name = str(self.year) + ' ' + self.make + ' ' + self.model
		return long_name.title()
		
	def read_odometer(self):
		""" 打印一条指出汽车里程的消息 """
		print("This car has " + str(self.odometer_reading) + " miles on it.")
	
	def update_odometer(self, mileage):
		""" 将里程表读数设置为指定的值 """
		self.odometer_reading = mileage
		
my_new_car = Car('audi', 'a4', 2016)
print(my_new_car.get_descriptive_name())

my_new_car.update_odometer(23)
my_new_car.read_odometer()

Car 类所做的唯一修改是添加了方法 update_odometer() 。这个方法接受一个里程值,并将其存储到self.odometer_reading 中。在最后的两行,我们调用了 update_odometer() ,并向它提供了实参 23 (该实参对应于方法定义中的形参 mileage )。它将里程表读数设置为 23 ;而方法 read_odometer() 打印该读数:

2016 Audi A4
This car has 23 miles on it.

可对方法 update_odometer() 进行扩展,使其在修改里程表读数时做些额外的工作。下面来添加一些逻辑,禁止任何人将里程表读数往回调:

def update_odometer(self, mileage):
	"""
	将里程表读数设置为指定的值
	禁止将里程表读数往回调
	"""
	if mileage >= self.odometer_reading:
		self.odometer_reading = mileage
	else:
		print("You can't roll back an odometer!")

现在, update_odometer() 在修改属性前检查指定的读数是否合理。如果新指定的里程( mileage )大于或等于原来的里程( self.odometer_reading ),就将里程表读数改为新指定的里程;否则就发出警告,指出不能将里程表往回拨。

  1. 通过方法对属性的值进行递增

有时候需要将属性值递增特定的量,而不是将其设置为全新的值。假设我们购买了一辆二手车,且从购买到登记期间增加了 100 英里的里程,下面的方法让我们能够传递这个增量,并相应地增加里程表读数:

class Car():
	def __init__(self, make, model, year):
		""" 初始化描述汽车的属性 """
		self.make = make
		self.model = model
		self.year = year
		self.odometer_reading = 0
		
	def get_descriptive_name(self):
		""" 返回整洁的描述性信息 """
		long_name = str(self.year) + ' ' + self.make + ' ' + self.model
		return long_name.title()
		
	def read_odometer(self):
		""" 打印一条指出汽车里程的消息 """
		print("This car has " + str(self.odometer_reading) + " miles on it.")
	
	def update_odometer(self, mileage):
		"""
		将里程表读数设置为指定的值
		禁止将里程表读数往回调
		"""
		if mileage >= self.odometer_reading:
			self.odometer_reading = mileage
		else:
			print("You can't roll back an odometer!")
		
	def increment_odometer(self, miles):
		""" 将里程表读数增加指定的量 """
		self.odometer_reading += miles
		
my_used_car = Car('subaru', 'outback', 2013)
print(my_used_car.get_descriptive_name())
my_used_car.update_odometer(23500)
my_used_car.read_odometer()
my_used_car.increment_odometer(100)
my_used_car.read_odometer()

新增的方法 increment_odometer() 接受一个单位为英里的数字,并将其加入到 self.odometer_reading 中。我们创建了一辆二手车—— my_used_car

我们调用方法 update_odometer() 并传入 23500 ,将这辆二手车的里程表读数设置为 23500 。我们调用 increment_odometer() 并传入 100 ,以增加从购买到登记期间行驶的 100 英里:

2013 Subaru Outback
This car has 23500 miles on it.
This car has 23600 miles on it.

你可以轻松地修改这个方法,以禁止增量为负值,从而防止有人利用它来回拨里程表。

注意:你可以使用类似于上面的方法来控制用户修改属性值(如里程表读数)的方式,但能够访问程序的人都可以通过直接访问属性来将里程表修改为任何值。要确保安全,除了进行类似于前面的基本检查外,还需特别注意细节。

三、面向过程与面向对象

3.1、面向过程

面向过程的程序设计的核心是过程,过程即解决问题的步骤。面向过程的设计就好比精心设计好一条流水线,考虑周全什么时候应该处理什么东西。

优点:

  • 流程化使得编程任务明确,在开发之前基本考虑了实现方式和最终结果,具体步骤清楚,极大的降低了写程序的复杂度,便于节点分析。

  • 效率高,面向过程强调代码的短小精悍,善于结合数据结构来开发高效率的程序。

缺点:

  • 需要深入的思考,耗费精力,代码重用性低,扩展能力差,后期维护难度比较大。

应用场景:一旦完成基本很少改变的场景,著名的例子有 Linux 內核,git,以及 Apache HTTP Server 等。

3.2、面向对象

面向对象的程序设计的核心是对象。面向对象的设计就好比下棋,整个游戏分成三个对象:棋子双方,棋盘系统,规则系统,然后赋予每个对象一些属性和行为。

优点:

  • 结构清晰,程序是模块化和结构化,更加符合人类的思维方式;

  • 易扩展,代码重用率高,可继承,可覆盖,可以设计出低耦合的系统;

  • 易维护,系统低耦合的特点有利于减少程序的后期维护工作量。

缺点:

  • 开销大,当要修改对象内部时,对象的属性不允许外部直接存取,所以要增加许多没有其他意义、只负责读或写的行为。这会为编程工作增加负担,增加运行开销,并且使程序显得臃肿。

  • 性能低,由于面向更高的逻辑抽象层,使得面向对象在实现的时候,不得不做出性能上面的牺牲,计算时间和空间存储大小都开销很大。

应用场景:需求经常变化的软件,一般需求的变化都集中在用户层,互联网应用,企业内部软件,游戏等都是面向对象的程序设计大显身手的好地方。

3.3、面向过程和面向对象的区别

直观上个理解面向过程应该就是一个偏向于实现过程,另一个面向对象更偏向于实现对象。

面向对象的意义是为了解决面向过程编程出现的不灵活,重复代码多,耦合性高等大型软件编程出现的问题,简单来说就是为了解决问题,所以提出这么一种思想来指导编程,提出这种思想的人认为将代码世界与现实世界做映射可以解决以上说的问题。

四、面向对象的三大特性

4.1、继承

继承指的是一种创建新类的方式,在python 中,新建的类可以继承一个或多个父类,父类又可称为基类或超类,新建的类称为派生类或子类。

python 中类的继承分为:单继承和多继承。

class ParentClass1: #定义父类
    pass

class ParentClass2: #定义父类
    pass

class SubClass1(ParentClass1): #单继承,基类是ParentClass1,派生类是SubClass
    pass

class SubClass2(ParentClass1,ParentClass2): #python支持多继承,用逗号分隔开多个继承的类
    pass

class SubClass3(): #如果没有指定基类,python的类会默认继承object类,object是所有python类的基类
    pass

查看继承:

print(SubClass1.__bases__) #__base__只查看从左到右继承的第一个子类,__bases__则是查看所有继承的父类
# (<class '__main__.ParentClass1'>,)
print(SubClass2.__bases__)
# (<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)
print(SubClass3.__bases__)
# (<class 'object'>,)

Python的类可以继承多个类,Java和C#中则只能继承一个类

4.2、多态

多态指的是一类事物有多种形态

比如,动物有多种形态:人,狗,猪

import abc

class Animal(metaclass=abc.ABCMeta): #同一类事物:动物
    @abc.abstractmethod
    def talk(self):
        pass

class People(Animal): #动物的形态之一:人
    def talk(self):
        print('say hello')

class Dog(Animal): #动物的形态之二:狗
    def talk(self):
        print('say wangwang')

class Pig(Animal): #动物的形态之三:猪
    def talk(self):
        print('say aoao')

鸭子类型:

Python崇尚鸭子类型,即‘如果看起来像、叫声像而且走起路来像鸭子,那么它就是鸭子’。python程序员通常根据这种行为来编写程序。例如,如果想编写现有对象的自定义版本,可以继承该对象;也可以创建一个外观和行为像,但与它无任何关系的全新对象,后者通常用于保存程序组件的松耦合度。

#二者都像鸭子,二者看起来都像文件,因而就可以当文件一样去用
class TxtFile:
    def read(self):
        pass
    def write(self):
        pass

class DiskFile:
    def read(self):
        pass
    def write(self):
        pass

4.3、封装

封装指的是隐藏对象的属性和实现细节,仅对外提供公共访问方式。

封装原则:

  1. 将不需要对外提供的内容都隐藏起来;

  2. 把属性都隐藏,提供公共方法对其访问。

4.3.1、私有变量和私有方法

在python中用双下划线开头的方式将属性隐藏起来(设置成私有的)

#其实这仅仅这是一种变形操作
#类中所有双下划线开头的名称如__x都会自动变形成:_类名__x的形式:

class A:
    __N=0 #类的数据属性就应该是共享的,但是语法上是可以把类的数据属性设置成私有的如__N,会变形为_A__N
    def __init__(self):
        self.__X=10 #变形为self._A__X
    def __foo(self): #变形为_A__foo
        print('from A')
    def bar(self):
        self.__foo() #只有在类内部才可以通过__foo的形式访问到.

#A._A__N是可以访问到的,即这种操作并不是严格意义上的限制外部访问,仅仅只是一种语法意义上的变形

这种自动变形的特点:

  1. 类中定义的__x只能在内部使用,如self.__x,引用的就是变形的结果。

  2. 这种变形其实正是针对外部的变形,在外部是无法通过__x这个名字访问到的。

  3. 在子类定义的__x不会覆盖在父类定义的__x,因为子类中变形成了:_子类名__x,而父类中变形成了:_父类名__x,即双下滑线开头的属性在继承给子类时,子类是无法覆盖的。

这种变形需要注意的问题是:

  1. 这种机制也并没有真正意义上限制我们从外部直接访问属性,知道了类名和属性名就可以拼出名字:_类名__属性,然后就可以访问了,如a._A__N

  2. 变形的过程只在类的内部生效,在定义后的赋值操作,不会变形
    在这里插入图片描述

  3. 在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的

#正常情况
class A:
    def fa(self):
        print('from A')
    def test(self):
        self.fa()

class B(A):
     def fa(self):
         print('from B')
         
>>> b=B()
>>> b.test()
from B

#把fa定义成私有的,即__fa
class A:
    def __fa(self):
        print('from A')
    def test(self):
        self.__fa()

class B(A):
     def __fa(self):
         print('from B')
         
>>> b=B()
>>> b.test()
from A
4.3.2、封装与扩展性

封装在于明确区分内外,使得类实现者可以修改封装内的东西而不影响外部调用者的代码;而外部使用用者只知道一个接口(函数),只要接口(函数)名、参数不变,使用者的代码永远无需改变。这就提供一个良好的合作基础——或者说,只要接口这个基础约定不变,则代码改变不足为虑。

#类的设计者
class Room:
    def __init__(self,name,owner,width,length,high):
        self.name=name
        self.owner=owner
        self.__width=width
        self.__length=length
        self.__high=high
    def tell_area(self): #对外提供的接口,隐藏了内部的实现细节,此时我们想求的是面积
        return self.__width * self.__length

#使用者
>>> r1=Room('卧室','egon',20,20,20)
>>> r1.tell_area() #使用者调用接口tell_area
400

#类的设计者,轻松的扩展了功能,而类的使用者完全不需要改变自己的代码
class Room:
    def __init__(self,name,owner,width,length,high):
        self.name=name
        self.owner=owner
        self.__width=width
        self.__length=length
        self.__high=high
    def tell_area(self):
    #对外提供的接口,隐藏内部实现,此时我们想求的是体积,内部逻辑变了,只需求修该下列一行就可以很简答的实现,
    #而且外部调用感知不到,仍然使用该方法,但是功能已经变了
        return self.__width * self.__length * self.__high

#对于仍然在使用tell_area接口的人来说,根本无需改动自己的代码,就可以用上新功能
>>> r1=Room('卧室','egon',20,20,20)
>>> r1.tell_area()
8000

python课程推荐。
在这里插入图片描述

参考文章

  • 8
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我是管小亮

一口吃掉你的打赏,嗝~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值