week5 day1-3 面向对象编程

week5 day1-3 面向对象编程

    • 一. 面向对象编程思想
    • 二. 类和对象
    • 三. 属性查找与绑定方法
      • 3.1 获取数据属性
      • 3.2 获取功能属性
    • 四. 类的三大特性之一——封装
      • 4.1 隐藏属性
      • 4.2 类的装饰器property
      • 4.3 绑定方法与非绑定方法
    • 五. 类的三大特性之二——继承
      • 5.1 继承基本概念
      • 5.2 在继承背景下的属性查找
      • 5.2.1 单继承
      • 5.2.2 多继承
      • 5.3 继承应用
      • 5.3.1 指名道姓要(与继承无关)
      • 5.3.2 super() ‘找爸爸要’
    • 5.4 组合(重要)
    • 六. 类的三大特性之三——多态

一. 面向对象编程思想

前情回顾:我们之前所学的书写代码的方法叫做面向过程编程。面向过程编程是一种很常用的书写编程的思想,也非常实用。将复杂的问题拆解出来一步一步解决。

而现在我们将介绍一种新的编程思想——面向对象编程。

面向过程编程的核心是过程,是将问题按步骤拆解完逐步实现的过程。而面向对象编程的核心是对象,对象就是一个盛放数据和功能的容器基于该思想写程序就是在创造一个个容器

编程思想 优点 缺点
面向过程编程 将复杂的问题简单化/流水化 可扩展性差
面向对象编程 可扩展性强 把简单的问题复杂化

二. 类和对象

我们个人【对象】既具有数据属性(身高体重年龄),又具有功能属性(吃喝拉撒)。而我们每个人都属于人类这个范围,在人类这个范围中我们共有的数据属性是两条腿,两只胳膊,共有的功能属性是吃喝拉撒。这就是我们的共同点,把我们的共同点找出来都扔在一个大的容器里面,这就构成了【类】。

对象是具体的一个个个体,而类是把个体都具体有的属性取出来扔到一个大的容器里面。这个大的容器叫做类,而一个个个体就是把属性具体化的类。

即类包含对象共有的数据属性和功能属性,对象是把数据属性具体化的类。产生对象的过程就是类的实例化的过程。

# 基础语法
class 类名:
	(类独有的数据属性)
	def __init__(self,传入的参数1,传入的参数2...):
		self.对象独有数据属性1=传入的参数1
		self.对象独有数据属性2=传入的参数2
		...
	def 数据的独有功能属性(self,可以传参):
		功能属性的函数体代码
	@类的装饰器
	def 功能属性:
		...
# 例子
# 在定义类的时候发生的事情:
# 	1.立刻运行类体代码
# 	2.将运行过程中产生的名字丢到类的名称空间里面
#(类在定义时产生名称空间,函数和模块在调用时产生名称空间)
class People:
	school='oldboy'
	def __init__(self,name,age,gender):
		self.name=name
		self.age=age
		self.gender=gender
	def tell_info(self):
		print('{}{}{}'.format(self.name,self.age,self.gender))
		
# 在调用类时发生的事情:
# 	1.先自动创造一个空对象
# 	2.自动触发类体代码中__init__的运行,将空对象当作第一个参数传入
# 	3.返回一个初始化好的对象 stu1
stu1=People('wth',20,'male')
print(stu1.__dict__)-----># {'name': 'egon', 'age': 18, 'gender': 'male'}

我们会发现:
调用类产生对象就是了一个具有对象都有属性的字典
调用类产生对象就是了一个具有对象都有属性的字典
调用类产生对象就是了一个具有对象都有属性的字典
调用类产生对象就是了一个具有对象都有属性的字典
调用类产生对象就是了一个具有对象都有属性的字典

三. 属性查找与绑定方法

在上一个标题中我们讨论了类和对象的关系。类中包含了相关对象中所有的共有属性,既有数据属性,又有功能属性。而对象调用类,调用类会发生三件事,①先自动创建一个空对象,②触发类体代码中的__init__方法,将空对象当作第一个参数传入,同时将其他参数传入__init__方法,③返回一个初始化完的对象。而无论是类还是对象,都需要可以方便地拿到数据属性和功能属性。接下来我们将具体分析如何通过类和对象拿到数据属性和功能属性。

class Student:
	school='oldboy' # 类的属性
	def __init__(self,name,age,gender):
		self.name=name # 对象独有的属性
		self.age=age # 对象独有的属性
		self.gender=gender # 对象独有的属性
	def tell_info(self):
		print('{}{}{}'.format(self.name,self.age,self.gender))
		
obj1=Student('wth',20,'male') # 背后完成:Student.__init__(obj1,'wth',20,'male')
obj2=Student('wth1',20,'female')

我们之前在学模块的时候学过.的用法。在导入语句中,.是路径分隔符,.前必须是文件夹;在调用语句中,.前是模块名,.后是模块中的功能,.起到了调用功能的作用。同样,当我们创建了对象之后,也需要拿到对象独有的数据属性和功能属性。其获取属性的方法与模块(模块其实就是比类更大的容器)一模一样。下面我们分数据属性和功能属性来详细说明。

3.1 获取数据属性

# 使用方法:
对象.对象/类 数据属性的名字
类.类数据属性的名字

对象获取数据属性的查找顺序:先从定义对象的类里面找,再从类(字典)的名称空间里面获取数据。
类获取数据属性的查找顺序:从类的名称空间中找。

类中的数据属性是共享给对象(id相同)使用的。如果需求是修改所有对象的某项数据,直接修改类中对象数据的值即可;如果只是想修改单个对象的属性,该对象的属性存在则修改,不存在则添加。

# 代码演示:
class Student:
	count=0
	school='oldboy'
	student_list={
     }
	def __init__(self,name,age,gender):
		Student.cout+=1
		self.name=name
		self.age=age
		self.gender=gender
		student_list[Student.count]=self
	def tell_info(self):
		print('{}-{}-{}'.format(self,name,self.age,self.gender))
stu1=Student('wth',20,'male')
print(Student.count)-----># 1
stu2=Student('wth1',20,'female')
print(Student.count)-----># 2
for k,v in Student.student_list.items():
	print(k,v.name)-----># 1 wth
						 # 2 wth1	

3.2 获取功能属性

# 使用方法:
对象.类的内置方法(给除了self以外的参数传参)
类.类的内置方法(给包括self在内的所有参数传参) # 类对于内部方法的调用与普通函数调用无异

类中的功能属性类自己可以使用,如果类来调用,该怎么传参就怎么传参,和调用普通函数一样。
但类中的函数是给对象用的,对象来调用就是一个绑定方法,绑定方法的特点就是会将调用内置方法的对象(字典)当作第一个参数传入。

# 代码演示:
class Student:
	count=0
	school='oldboy'
	student_list={
     }
	def __init__(self,name,age,gender):
		Student.cout+=1
		self.name=name
		self.age=age
		self.gender=gender
		student_list[Student.count]=self
	def tell_info(self):
		print('{}-{}-{}'.format(self,name,self.age,self.gender))
stu1=Student('wth',20,'male')
stu2=Student('wth1',20,'female')
# 类来调用
Student.tell_info(stu1)
# 对象调用
stu1.tell_info() # 参数自动传入调用它的对象

四. 类的三大特性之一——封装

关于类的封装的特性其实我们一直在创造类的过程中一直在使用,我们把对象中相似或相同的属性都提取出来放到一个大容器里面,而这个大容器被我们封装成了类。而我们把具象化的属性封装在一个对象里面。我们原来想传递数据需要把所有的数据都传递给别人,需要传多个值:现在我们只需要将封装了这些具体数据的对象扔给别人,需要传一个值,而其他属性都可以通过这个值拿到。

4.1 隐藏属性

是不是我们可以获得对象里面所有的值呢?是的。但其实有些数据我们不想别人拿到或者我们不想别人随意修改那些数据,我们就需要一种方法把数据藏起来。这也就是python提供给我们的隐藏属性的方法。我们可以把属性隐藏起来,不让外部直接访问修改,我们单独这些数据的删改查开辟接口来进行进一步的操作。

我们只需要在想隐藏起来的属性前面加上__即将该属性隐藏起来了。那么这个隐藏过程是如何发生的?以及我们可以如何使用隐藏属性这个方法呢?

# 代码演示:
class Student:
	def __init__(self,name):
		self.__name=name
	# 开辟查询接口
	def get_name(self):
		print(self.__name)
stu1=Student('wth')
print(stu1.__name)-----># 直接报错,说没有这个属性
# 我们创造的学生名字这个数据属性究竟去了哪里?查询一下这个对象的字典
print(stu1.__dict__)----->{
     ...,'_Student__name':'wth',...} # python完成了改名操作

通过上面的代码我们可以得知隐藏属性这个方法并没有真的把我们的数据隐藏起来,而是给它改了名,让我们没法直接访问到。我们没法直接访问,但是可以通过其他接口拿到名字。总结一下隐藏属性这个方法的特点:

  1. 并没有真的藏起来,只是变形了
  2. 该变形只在类定义阶段,扫描语法的时候执行,此后__开头的属性都不会变形
  3. 该隐藏对内不对外stu1.get_name()----->'wth'

那么为什么要隐藏属性呢?

  1. 隐藏数据是为了严格控制外部访问者对数据的修改和删除
  2. 隐藏功能属性是为了隔离复杂度 eg.开电脑的时候我们只需要点亮开机键等待电脑开机,其他的计算机会帮我们处理(其实隐藏属性也是为了提高封装程度)

下面是回忆过去时间:
我们之前学习数据类型的时候,定义一个列表,字典的时候list1=list('string'),这个状态是不是很像我们把类实例化的操作,list就是类的名字,把一个参数传进去,创建一个空对象,将空对象和参数传入内置的__init__函数,返回一个初始化后的对象,赋值给list1,然后我们可以通过对象加.调用类的内置方法处理数据。其实,python中一切皆对象,只不过我们可以拿来就用的是内置好的类,而我们想要完成某种特定的需求所建立的类是自定义的类。

4.2 类的装饰器property

我们能不能让类中的函数属性像数据属性一样调用呢?

答案是肯定的。python在类中提供了装饰器方便我们进行操作。

# 代码演示:
class People:
	def __init__(self,name,height,weight):
		self.name=name
		self.height=height
		self.weight=weight
	# 写一个测BMI的功能
	@property
	def BMI(self):
		print('{}的BMI是{}'.format(self.name,self.weight/(self.height**2)))
peo1=People('wth',178,75)
# 不加装饰器的调用方法
peo1.BMI()
# 添加了装饰器property之后的调用方式
peo1.BMI # 实现了将功能属性像数据属性一样调用的效果

property是不需要传参的功能属性添加装饰器的方式,直接将功能属性的使用方法转换成了数据属性的使用方法。那么对于有参功能,我们应该如何添加装饰器呢?有两种方法,我们分别来进行示范。

# 代码演示:添加装饰器方式一
class People:
	def __init__(self,name):
		self.__name=name
	# 针对同一属性的不同操作
	# 无参功能(查看)添加装饰器
	@property
	def name(self):
		print(self.__name)
	# 有参功能(修改)添加装饰器
	@name.setter
	def name(self,new_name):
		if str(new_name) is not str:
			print('修改的名字必须是字符格式!')
		self.__name=new_name
		print('名字已修改为{}'.format(self.__name))
	# 无参功能(删除)添加装饰器
	@name.deleter
	def name(self):
		print('姓名属性不可修改!') # 也可以在此处抛出异常
peo1=People('wth')
peo1.name='WTH'
peo1.name-----># 'WTH'
del peo1.name-----># '姓名属性不可修改!'
# 代码演示:添加装饰器方法二
class People:
    def __init__(self,name):
        self.__name=name

    def get_name(self):
        return self.__name

    def set_name(self,name):
        if type(name) is not str:
            raise Exception('名字必须是字符串类型')
        self.__name=name

    def del_name(self):
        raise Exception('不允许删除!')

    name=property(get_name,set_name,del_name)

obj=People('wth')
print(obj.name)
obj.name='WTH'
del obj.name

4.3 绑定方法与非绑定方法

绑定方法我们在介绍类和对象关系的时候提到过,类的功能属性只存在于类的名称空间中,对象想要调用类中的功能属性时就是把一个链接放在对象的名称空间中,这个链接可以直接指向类中功能的内存地址,类中的功能是相当于绑定方法拿给对象用的。绑定方法给谁就是由谁来用,谁来调用就把自己当作第一个参数传入。

那么什么是非绑定方法呢?不与任何对象绑定,意味着谁都可以来调用,但无论谁来调用,都是一个普通函数,没有自动传参的效果。

# 代码演示:
class People:
	def __init__(self,name):
		self.name=name
	# 但凡在类中定义一个函数,默认就是绑定给对象,应该由对象来调用,对象来调用会把自己当成第一个参数传入
	def tell(self):
		print(self.name)
	# 类中定义的函数被@classmethod装饰过,就绑定给类,应该由类来调用,类来调用就会把自己当成第一个参数自动传入
	@classmethod
	def f1(cls):
		print(cls)
	# 类中定义的函数被@staticmethod装饰过,谁也不绑定,谁都可以来调,按照普通函数传参
	@staticmethod
	def func():
		print('this is from func')
peo1=People('wth')
peo1.tell()
People.f1()
peo1.func()
People.func()

五. 类的三大特性之二——继承

5.1 继承基本概念

既然对象之间相似属性可以提取出来放在类里面,那么我们可不可以把类和类的相似数据属性和功能属性拿出来,放在父类里面呢?

# 代码演示:提取前
class Student:
	def __init__(self,name,age,gender):
		self.name=name
		self.age=age
		self.gender=gender
	def tell_info(self):
		print('{}{}{}'.format(self.name,age,gender))
	def choose_course(self):
		print('{}正在选课'.format(self.name))
class Teacher:
	def __init__(self,name,age,gender):
		self.name=name
		self.age=age
		self.gender=gender
	def tell_info(self):
		print('{}{}{}'.format(self.name,age,gender))
	def grade(self):
		print('{}正在评分'.format(self.name))
# 代码演示:提取后
class People:
	def __init__(self,name,age,gender):
		self.name=name
		self.age=age
		self.gender=gender
	def tell_info(self):
		print('{}{}{}'.format(self.name,age,gender))
class Student(People):
	def choose_course(self):
		print('{}正在选课'.format(self.name))
class Teacher(People):
	def grade(self):
		print('{}正在评分'.format(self.name))

在上面的例子中,把Student类和Teacher类共有的属性提取出来,创建了一个新类,这个新类也叫Student和Teacher的父类/超类/基类,而Student和Teacher称为子类。继承的优点就是,子类可以遗传父类所有的属性and代码简介。缺点是强耦合。可以通过代码print(类.__bases__)来查看这个类的所有父类。

新式类:在python3中,但凡是继承了object的子类以及该类的子子孙孙类都是新式类。(python3的所有类都是新式类)

经典类:一般python2中的类都是经典类。

既然是涉及到继承,那么对象调用的属性是如何查找的呢?见例

# 例一
class Bar:
	def f1(self):
		print('from Bar.f1')
	def f2(self):
		print('from Bar.f2')
		self.f1()
class Foo(Bar):
	def f1(self):
		print('from Foo.f1')
obj=Foo()
obj.f2()-----># from Bar.f2
			  # from Foo.f1

obj是Foo的对象,当它想要调用f2时,先从自己的名称空间找,但发现没有找到f2,就去定义自己的类里面寻找,同样也没有。再向自己类的父类里面寻找,找到了,这时候可以调用。但需要注意一点!对象的功能属性调用的时候,把自己当作第一个参数传入,因此调用f2的时候f2内的代码是这样的:

def f2(obj):
	print('from Bar.f2')
	obj.f1()

结果一目了然。再考虑下面这种情况:

# 例二:
class Bar:
	def __f1(self):
		print('from Bat.f1')
	def __f2(self):
		print('from Bar.f2')
		self.f1()
class Foo(Bar):
	def __f1(self):
		print('from Foo.f1')
obj=Foo()
obj.f2()-----># from Bar.f2
			  # from Bar.f1

这里为什么会出现不同的结果呢?因为隐藏属性在类的定义阶段会被改名,所以Bar的两个功能属性变成了下面的样子:

class Bar:
	def _Bar__f1(self):
		print('from Bar.f1')
	def _Bar__f2(obj):
		print('from Bar.f2')
		obj._Bar__f1()

而obj本身是没有_Bar__f1()这个名字的属性的,所以从定义自己的类里面找,还是没有,再去父类里面找,找到了,只在父类里面有相同名字的属性,成功调用!

5.2 在继承背景下的属性查找

5.2.1 单继承

单继承是不分新式类还是经典类,相当于一根棍,只需要沿着棍向上寻找就可以了。

5.2.2 多继承

需要分新式类和经典类。然后参照发起属性查找类的mro列表来搜索属性。
week5 day1-3 面向对象编程_第1张图片
week5 day1-3 面向对象编程_第2张图片

5.3 继承应用

考虑下面这种场景:

class Student:
	def __init__(self,name,age,gender):
		self.name=name
		self.age=age
		self.gender=gender
	def choose_course(self):
		print('{}正在选课'.format(self.name))
class Teacher:
	def __inti__(self,name,age,gender,level)
		self.name=name
		self.age=age
		self.gender=gender
		self.level=level
	def grade(self):
		print('{}正在打分'.format(self.name))

我们会发现其中有很多冗余代码,我们第一时间应该想到是可以把它们都提出来,但是我们又发现,其中Student部分的__init__部分完全重复,但是Teacher部分的__init__大部分代码重复,只有一个数据属性是需要自己定义的,我们可以把两者都重复的提取出来,放到父类里面,但是不重复的属性怎么办呢?提出来的属性又可以如何定义呢?介绍两种继承的方法。

5.3.1 指名道姓要(与继承无关)

修改后的代码如下:

class People:
	def __init__(self,name,age,gender):
		self.name=name
		self.age=age
		self.gender=gender
class Student:
	def choose_course(self):
		print('{}正在选课'.format(self.name))
class Teacher:
	def __init__(self,name,age,gender,level)
		People.__init__(self,name,age,gender) # 类调类的功能属性,相当于调普通函数,正常传参
		self.level=level
	def grade(self):
		print('{}正在打分'.format(self.name))

5.3.2 super() ‘找爸爸要’

在介绍super()方法之前,先介绍一下继承实现的原理。继承的查找顺序是遵循发起查找的mro列表的次序进心搜索的。拿个例子介绍一下mro列表:

class A:
	...
class M:
	...
class B(A):
	...
class C(M):
	...
class D:
	...
class E(B,C,D):
	...
print(E.mro())----->
# [, , , , , , ]

修改后代码如下:

class People:
	def __init__(self,name,age,gender):
		self.name=name
		self.age=age
		self.gender=gender
class Student:
	def choose_course(self):
		print('{}正在选课'.format(self.name))
class Teacher(People):
	def __init__(self,name,age,gender,level)
		super().__init__(name,age,gender) # super()会返回一个特殊的对象,该对象会参考发起属性查找的那一个类的mro列表,依次去当前类的父类中查找属性
		self.level=level
	def grade(self):
		print('{}正在打分'.format(self.name))

super()会返回一个特殊的对象,该对象会参考发起属性查找的那一个类的mro列表,依次去当前类的父类中查找属性。
super()会返回一个特殊的对象,该对象会参考发起属性查找的那一个类的mro列表,依次去当前类的父类中查找属性。
super()会返回一个特殊的对象,该对象会参考发起属性查找的那一个类的mro列表,依次去当前类的父类中查找属性。
super()会返回一个特殊的对象,该对象会参考发起属性查找的那一个类的mro列表,依次去当前类的父类中查找属性。
super()会返回一个特殊的对象,该对象会参考发起属性查找的那一个类的mro列表,依次去当前类的父类中查找属性。

了解:
继承表达的是 is—a 的关系。如果想要使用多继承,遵循下面的两条规则:
1.用来表示归属关系的类往右放
2.用来添加功能的类往左放
class Vehicle:
	...
class FlyAbleMixin:
	def fly(self):
		print('flying')
class CivilAircraft(FlyAbleMixin,Vehicle):
	...
class Helicopter(FlyAbleMixin,Vehicle):
	...
class Car(Vehicle):
	...

5.4 组合(重要)

如果类与类,对象与对象之间的关系是 “xx是xx” ,那么我们就可以定义子类与父类,对象与类的关系来表达这个关系。如果类与类,对象与对象之间的关系 “不是xx是xx的关系” ,是其他的关系,就需要用到组合的关系。

class Student:
	def __init__(self,name,age,gender):
		self.name=name
		self.age=age
		self.gender=gender
# 实例化两个学生
stu1_obj=Student('wth',20,'male')
stu2_obj=Student('wth1',20,'female')

class Teacher:
	def __init__(self,name,level):
		self.name=name
		self.level=level
# 实例化一个老师
tea1_obj=Teacher('egon',10)

# 老师实例和学生实例的关系是老师教学生,不是“xx是xx”的关系,所以使用组合这一方法。
tea1_obj.teach_stu=[stu1_obj,stu2_obj]

六. 类的三大特性之三——多态

多态:同一种事物有多种形态。
多态性:多态性指的是可以在不考虑对象具体类型的情况下直接使用对象。

class Animal:
	def say(self):
		print('动物是这么叫的',end='')
		
class People(Animal):
	def say(self):
		super().say()
		print('嘤嘤嘤')
		
class Dog(Animal):
	def say(self):
		super().say()
		print('汪汪汪')
		
class Cat(Animal):
	def say(self):
		super().say()
		print('喵喵喵')
obj1=People()
obj2=Dog()
obj3=Cat()
obj1.say()-----># '动物是这么叫的嘤嘤嘤'
obj2.say()-----># '动物是这么叫的汪汪汪'
obj3.say()-----># '动物是这么叫的喵喵喵'

# 甚至可以定制统一的接口接受传入的动物对象
def animal_say(animal):
	animal.say()

其实我们之前一直在接触这种多态的思想,不管是字符串,列表,字典,元祖,都可以测量长度。因为这些数据类型都有__len__方法。

我们可以强制规定子类使用父类的功能(通过定义抽象基类),也可以约定俗成靠程序员自我遵守统一的命名规范,下面我们简单示范一下两种情况。

# 代码示范:强制子类使用父类的方法
import abc
class Animal(metaclass=abc.ABCMeta): # 定义抽象基类,Animal已不可单独调用
	@abd.abstractmethod # 强制约束所有子类都得有这些功能
	def say(self):
		print('动物是这样叫的',end='')
class Dog(Animal):
	def say(self):
		super().say()
		print('汪汪汪')
class Cat(Animal):
	def say(self):
		super().say()
		print('喵喵喵')
class Pig(Animal):
	def say(self):
		super().say()
		print('哼哼哼')
# 定义了抽象基类,给父类的功能添加装饰器后,就规定了子类定义名字相同的这些功能,如果子类没有,就会报错
# 代码示范:靠程序员自觉性实现相同效果
class Dog:
	def say(self):
		print('汪汪汪')
class Cat:
	def say(self):
		print('喵喵喵')
class Pig:
	def say(self):
		print('哼哼哼')

这种情况也叫做鸭子模型,只要你长得像鸭子,走路像鸭子,那你就是鸭子。

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