Python 面向对象编程
面向对象编程
了解
面向过程:
- 核心是 “ 过程 ”,过程的核心思想是将程序流程化
- 过程是 “ 流水线 ”,用来分步骤解决问题
面向对象:
- 核心是 “ 对象 ”,对象的核心思想就是将程序 整合
- 对象是 “ 容器 ”,用来存储数据与功能
程序 = 数据 + 功能
类与对象
类也是容器,该容器存放同类对象共有的数据与功能
类的定义
类是对象相似数据与功能的集合体
类体中最常见的是变量与函数的定义,但是类体也可以包含任意其他代码
注意:类代码是在类定义阶段就会立即执行
class Student: # 类的命名应该使用“驼峰体”
school='清华大学' # 数据
def choose(self): # 功能
print('%s is choosing a course' %self.name)
# 类属性访问语法
print(Student.choose) #print(Student.__dict__['choose]')
输出结果:
{..., 'school': '清华大学', 'choose': <function Student.choose at 0x1018a2950>, ...}
类的调用
在类内部新增一个__init__
方法,该方法会在对象产生之后自动执行
class Student:
school='清华大学'
# 该方法会在对象产生之后自动执行,专门为对象进行初始化操作,可以有任意代码,但一定不能返回非None的值
def __init__(self,name,sex,age):
self.name=name # 空对象.name = name
self.sex=sex # 空对象.sex = sex
self.age=age # 空对象.age = age
def choose(self):
print('%s is choosing a course' %self.name)
调用类产生对象
调用类的过程称为将类实例化,拿到的返回值就是程序中的对象,或称为一个实例
实例化对象
stu1=Student('李建刚','男',28) # ---->把值传给__init__(空对象,name,sex,age)
stu2=Student('王大力','女',18)
stu3=Student('牛嗷嗷','男',38)
调用类产生三件事
- 产生一个空对象
- python会自动调用类中的
__init__
方法将空对象以及调用类时括号内传入的参数一同传递给__init__
方法 - 返回初始完的对象
__init__
方法总结
- 会在调用类时自动触发执行,用来为对象初始化自己独有的数据
__init__
内应该存放的是为对象初始化属性的功能,但是可以存放任意其他代码,想要在类调用时就立刻执行的代码都可以放到该方法内。__init__
方法必须返回None
类的访问
- 类的数据属性(共享给所有对象用的)
print(Student.school) # 访问数据属性,等同于Student.__dict__['school']
# 返回结果
'清华大学'
- 类的函数属性(绑定给对象用的)
print(Student.choose) # 访问函数属性,等同于Student.__dict__['choose']
# 返回结果
<function Student.choose at 0x1018a2950>
属性查找
类中的东西是给对象使用的。
- 类的数据属性是共享给所有对象用的,访问的地址都是一样的。
print(Student.school)
print(stu1.school)
print(stu2.school)
print(stu3.school)
# 输出的结果都为
'清华大学'
# 因此如果修改类的属性,对象的属性都会修改
# 但修改对象的属性只是修改了该对象自身的属性,不会影响其他对象的属性
例如:
stu1.school = '北京大学'
print(stu1.school)
print(stu2.school)
# 输出结果
'北京大学'
'清华大学'
-
类中定义的函数主要是给对象使用的,而且是绑定给对象的,虽然所有对象指向的都是相同的功能,但是绑定到不同的对象就是不同的绑定方法,内存地址各不相同。
类调用自己的函数属性必须严格的按照函数的用法
print(id(Student.choose)) # 4335426280
print(id(stu1.choose)) # 4300433608
封装
封装是面向对象三大特性最核心的一个特性
封装就是整合
隐藏属性
在属性名前加__
前缀,就会实现对外隐藏的效果
class Foo:
__x = 1 # _Foo__x
def __f1(self): # _Foo__f1
print('from test')
Foo.x
# 直接报错
隐藏注意的问题
- 在类外部无法直接访问双下滑线开头的属性,但知道了类名和属性名就可以拼出名字:
_类名__属性
,然后就可以访问了,如Foo._A__N,所以说这种操作并没有严格意义上地限制外部访问,仅仅只是一种语法意义上的变形。 - 这种隐藏是对外不对内的,因为
__
开头的属性会在检测类内代码语法时统一发生变形
class Foo:
__x = 1 # _Foo__x
def __f1(self): # _Foo__f1
print('from test')
def f2(self):
print(self.__x) # print(self._Foo.__x)
print(self.__f1) # print(self._Foo.__f1)
obj = Foo()
obj.f2()
# 输出结果
1
<bound method Foo.__f1 of <__main__.Foo object at 0x000002263775B5C8>>
- 这种变形的操作只会在检测类体语法的时候发生一次,之后定义的
__
开头的属性都不会发生
开放接口
隐藏数据属性
将数据隐藏起来就限制了类外部对数据的直接操作,然后类内应该提供相应的接口来允许类外部间接地操作数据,接口之上可以附加额外的逻辑来对数据的操作进行严格地控制
class Teacher:
def __init__(self,name,age): # 将名字和年纪都隐藏起来
self.__name=name
self.__age=age
def tell_info(self): # 对外提供访问老师信息的接口
print('姓名:%s,年龄:%s' %(self.__name,self.__age))
def set_info(self,name,age): # 对外提供设置老师信息的接口,并附加类型检查的逻辑
if not isinstance(name,str): # isinstance 判断该name对象是否是str类型
raise TypeError('姓名必须是字符串类型') # raise 显示异常
if not isinstance(age,int):
raise TypeError('年龄必须是整型')
self.__name=name
self.__age=age
t=Teacher('lili',18)
t.set_info('Li','19') # 年龄不为整型,抛出异常
# 抛出异常,报错
t.set_info('LiLi',19) # 名字为字符串类型,年龄为整型,可以正常设置
t.tell_info() # 查看老师的信息
# 输出结果
姓名:LiLi,年龄:19
隐藏函数属性
目的的是为了隔离复杂度,例如ATM程序的取款功能,该功能有很多其他功能组成,比如插卡、身份认证、输入金额、打印小票、取钱等,而对使用者来说,只需要开发取款这个功能接口即可,其余功能我们都可以隐藏起来
property
装饰器是在不修改被装饰器对象源代码以及调用方式的前提下为被装饰器对象添加新功能的可调用对象。
property也是装饰器,是用来绑定给对象的方法伪装成一个数据属性。
例子
体质指数(BMI)=体重(kg)÷ 身高^2(m)
EX:70kg÷(1.75×1.75)=22.86
class People:
def __init__(self, name, weight, height):
self.name = name
self.weight = weight
self.height = height
#
# 定义函数:
# 1.从bmi的公式看,bmi应该是触发功能计算得到的
# 2.bmi是随着身高、体重的变化而动态变化的,不是一个固定的值
# 3.bmi听起来更像一个数据属性不是功能属性
# 4.所以引用property,把bmi伪装成数据属性
@property
def bmi(self):
return self.weight / (self.height ** 2)
# 调用
obj = People('hp', 55, 1.75)
# print(obj.bmi()) # 未加装饰器前
print(obj.bmi) # 加了装饰器
property还提供设置和删除属性的功能
class Foo:
def __init__(self,val):
self.__name=val # 将属性隐藏起来
@property
def name(self): # f.name
return self.__name
@name.setter
def name(self,value): # f.name = 'lili'
if not isinstance(value,str): # 在设定值之前进行类型检查
raise TypeError('%s must be str' %value)
self.__name=value # 通过类型检查后,将值value存放到真实的位置self.__name
@name.deleter
def name(self): # del f.name
raise PermissionError('Can not delete')
f=Foo('lili')
f.name
# 输出结果
lili
f.name='LiLi' # 触发name.setter装饰器对应的函数name(f,’LiLi')
f.name=123 # 触发name.setter对应的的函数name(f,123),抛出异常TypeError
del f.name # 触发name.deleter对应的函数name(f),抛出异常PermissionError
继承
继承是一种创建新类的方式,在Python中,新建的类可以继承一个或多个父类,新建的类可称为子类或派生类,父类又可称为基类或超类,子类会遗传父类的属性,解决了类与类冗余的问题。
注意
python支持多继承,在python中,新建的类可以继承一个或多个父类,多继承应该使用MIxins。
class Parent1:
pass
class Parent2:
pass
class Sub1(Parent1): #单继承
pass
class Sub2(Parent1,Parent2): #多继承
pass
print(Sub1.__bases__) #查看父类
print(Sub2.__bases__)
#输出结果
(<class '__main__.Parent1'>,)
(<class '__main__.Parent1'>, <class '__main__.Parent2'>)
实现继承以及重用父类功能
class Hp_People:
school = 'HP'
def __init__(self,name,age,sex):
self.name = name
self.age = age
self.sex = sex
class Studen(Hp_People): # 继承父类Hp_People
# school = 'HP'
# def __init__(self,name,age,sex):
# self.name = name
# self.age = age
# self.sex = sex
def choose_course(self):
print('学生%s 正在选课'%self.name)
stu_obj = Studen('lili',18,'female')
print(stu_obj.__dict__)
print(stu_obj.school)
stu_obj.choose_course()
class Teacher(Hp_People): # 继承父类Hp_People
# school = 'HP'
#
def __init__(self, name, age, sex , salary , level):
# self.name = name
# self.age = age
# self.sex = sex
# 重用父类功能
# 方法一、与父类重复,指明道姓去父类Hp_People要__init__方法
Hp_People.__init__(self, name, age, sex)
# 方法二、使用super()函数,该函数会得到一个特殊的对象,该对象会参照当前类的mro,去当前的父类中找属性
# super(Teacher,self).__init__(name, age, sex)
super().__init__(name, age, sex)
self.salary = salary
self.level = level
def score(self):
print('老师 %s 正在给学生打分'% self.name)
tea_obj = Teacher('hp',20,'male',300000,10)
print(tea_obj.__dict__)
print(tea_obj.school)
tea_obj.score()
print(Teacher.mro)# 查看Teacher类的mro
Mixins机制
多继承使用Mixins机制。
Mixins机制核心是在多继承背景下尽量可能的提升多继承的可读性
Python语言可没有接口功能,但Python提供了Mixins机制,简单来说Mixins机制指的是子类混合(mixin)不同类的功能,而这些类采用统一的命名规范(例如Mixin后缀),以此标识这些类只是用来混合功能的,并不是用来标识子类的从属"is-a"关系的,所以Mixins机制本质仍是多继承,但同样遵守”is-a”关系
class Vehicle: # 交通工具
pass
class FlyableMixin:
def fly(self):
'''
飞行功能相应的代码
'''
print("I am flying")
class CivilAircraft(FlyableMixin,Vehicle): # 民航飞机
pass
class Helicopter(FlyableMixin,Vehicle): # 直升飞机
pass
class Car(Vehicle): # 汽车并不会飞,但按照上述继承关系,汽车也能飞了
pass
使用Mixin类实现多重继承要非常小心
- 首先它必须表示某一种功能,而不是某个物品,python 对于mixin类的命名方式一般以 Mixin, able, ible 为后缀
- 其次它必须责任单一,如果有多个功能,那就写多个Mixin类,一个类可以继承多个Mixin,为了保证遵循继承的“is-a”原则,只能继承一个标识其归属含义的父类
- 然后,它不依赖于子类的实现
- 最后,子类即便没有继承这个Mixin类,也照样可以工作,就是缺少了某个功能。(比如飞机照样可以载客,就是不能飞了)