【一】面向对象的三大特性
- 面向对象的三大特性:封装、继承、多态
- 其中最重要的就是封装,封装就是将数据和功能整合到一起
- 我们可以对封装在类和对象中的属性进行访问的控制,有隐藏的和开发的接口
【1】什么是封装
- 封装是对具体对象的一种抽象
- 封装就是将某些数据和功能隐藏起来,只能通过程序内部查看,而外部看不了
【2】为什么要封装
- 封装就是为了保护隐私,不让别人知道内部的功能
【3】封装的方法
- 你的身体的每一个器官,每一块皮肤,以至于你这整个人,都是属于自己一个人的,也只有你一个人可以使用,别人也用不了,也看不到你怎么用的
【二】隐藏属性
【1】隐藏属性方法
- 采用双下划线开头的方式将属性隐藏起来
- 类中所有双下滑线开头的属性都会在类定义阶段、检测语法时自动变成
_类名__属性名
的形式
class Student:
__SCHOOL = "北京大学"
# 定义函数时,会检测函数语法,所以__开头的属性也会变形
def __init__(self, name):
self.__name = name
# 定义函数时,会检测函数语法,所以__开头的属性也会变形
def __read(self):
print(f"学生{self.__name}的学校是{self.__SCHOOL}")
student = Student('ligo')
print(student.name)
# AttributeError: 'Student' object has no attribute 'name'
【2】隐藏属性访问
- 可以通过
__类名__变量名
来从类外部访问属性
class Student:
__SCHOOL = "北京大学"
# 定义函数时,会检测函数语法,所以__开头的属性也会变形
def __init__(self, name):
self.__name = name
# 定义函数时,会检测函数语法,所以__开头的属性也会变形
def __read(self):
print(f"学生{self.__name}的学校是{self.__SCHOOL}")
# 查看当前类的名称空间
print(Student.__dict__)
# '_Student__SCHOOL': '北京大学'
# 发现可以通过特定的方法,仍然可以访问到相应的属性 _Student__SCHOOL
print(Student.__SCHOOL) #找不到
print(Student._Student__SCHOOL) # 北京大学(可以找到)
student = Student('ligo')
# 查看对象的名称空间
print(student.__dict__)
# {'_Student__name': 'ligo'}
# 也可以访问到类里的函数_Student__read
student._Student__read()
# 学生ligo的学校是北京大学
【3】隐藏方法变形
- 在类内部是可以直接访问双下滑线开头的属性的
class Student:
__SCHOOL = "北京大学"
# 定义函数时,会检测函数语法,所以__开头的属性也会变形
def __init__(self, name):
self.__name = name
# 定义函数时,会检测函数语法,所以__开头的属性也会变形
def __read(self):
print(f"学生{self.__name}的学校是{self.__SCHOOL}")
# 直接在内部调用
def read(self):
self.__read()
print(self.__name)
student = Student('ligo')
student.read()
# 学生ligo的学校是北京大学
# ligo
【4】变形操作只会发生一次
- 变形操作只在类定义阶段发生一次,在类定义之后的赋值操作,不会变形
class Student:
__SCHOOL = "北京大学"
# 定义函数时,会检测函数语法,所以__开头的属性也会变形
def __init__(self, name):
self.__name = name
# 定义函数时,会检测函数语法,所以__开头的属性也会变形
def __read(self):
print(f"学生{self.__name}的学校是{self.__SCHOOL}")
# 修改类的变量
Student.__SCHOOL = '清华大学'
# 查看当前类的名称空间
print(Student.__dict__)
# '_Student__SCHOOL': '北京大学'
# 将__SCHOOL改为_Student__SCHOOL
Student._Student__SCHOOL = '清华大学'
print(Student.__dict__)
student = Student('ligo')
# 修改对象变量
student.__name = 'scott'
# 查看对象的名称空间
print(student.__dict__)
# {'_Student__name': 'ligo', '__name': 'scott'}
# 可以直接根据键取值
print(student.__name) # scott
student._Student__read()
# 学生ligo的学校是清华大学
【三】开放接口
- 定义属性就是为了使用,所以隐藏并不是目的
【1】隐藏数据属性
- 将数据隐藏起来就限制了类外部对数据的直接操作,然后类内应该提供相应的接口来允许类外部间接地操作数据,接口之上可以附加额外的逻辑来对数据的操作进行严格地控制
class Student(object):
def __init__(self, name, school):
self.__name = name
self.__school = school
# 对外提供访问学生信息的接口
def read(self):
print(f"学生{self.__name}的学校是{self.__school}")
# 对外提供设置学生信息的接口,并附加类型检查的逻辑
def set_info(self, name, school):
if not isinstance(name, str):
raise TypeError('学生姓名必须是字符串类型的')
if len(school) != 4:
raise TypeError('必须是四字大学')
self.__name = name
self.__school = school
# 实例化类得到对象
student = Student(name='ligo', school='北京大学')
student.read() # 学生ligo的学校是北京大学
# 修改
student.set_info(name='ligo', school='清华大学')
student.read() # 学生ligo的学校是清华大学
# 不符合要求会抛出异常
student.set_info(name='ligo', school='大学')
student.read() # TypeError: 必须是四字大学
【2】隐藏函数属性
- 目的的是为了隔离复杂度
- 例如ATM有插卡、身份认证、输入金额、打印小票、取钱等功能,对于使用者只需要开放取款功能的接口就行了,其余功能我们都可以隐藏起来
class ATM:
def __card(self):
print('插卡')
def __auth(self):
print('用户认证')
def __input(self):
print('输入取款金额')
def __print_bill(self):
print('打印账单')
def __take_money(self):
print('取款')
# 取款功能
def withdraw(self):
self.__card()
self.__auth()
self.__input()
self.__print_bill()
self.__take_money()
obj = ATM()
obj.withdraw()
# 插卡
# 用户认证
# 输入取款金额
# 打印账单
# 取款
【3】小结
- 隐藏属性与开放接口,是为了明确地区分内外,类内部可以修改封装内的东西而不影响外部调用者的代码
- 而外部使用者只需拿到一个接口,只要接口名、参数不变,使用者的代码永远无需改变
- 只要接口这个基础不变,代码的无论怎么修改都没有影响
【四】property
【1】什么是property
- property是一种特殊的属性,执行函数得到返回值,然后作为数据属性返回
class Student(object):
school = "清华大学"
def __init__(self, name):
self.name = name
@property
def read(self):
return f"学生{self.name}的学校是{self.school}"
student = Student(name='ligo')
print(student.name) # ligo
print(student.read) # 学生ligo的学校是清华大学
【2】BMI例子
- BMI指数是用来衡量一个人的体重与身高对健康影响的一个指标
- 成人的BMI数值:
- 过轻:低于18.5
- 正常:18.5-23.9
- 过重:24-27
- 肥胖:28-32
- 非常肥胖: 高于32
- 计算公式
体质指数(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
@property
def bmi(self):
return self.weight / (self.height ** 2)
people = People('ligo', 80, 1.80)
# 触发方法bmi的执行,将people自动传给self,执行后返回值作为本次引用的结果
# 正常我们调用对象的方法应该是people.bmi(),但是这里我们没有加()就能正常调用
print(people.bmi) # 24.691358024691358
【3】为什么要用property
- 面向对象的封装有三种方式:
- public:不封装,是对外公开的
- protected:对外不公开,但对朋友或者子类公开,就是对内公开
- private:对谁都不公开
- public
class Student(object):
def __init__(self, name):
self.name = name
student = Student(name='ligo')
# 查看姓名
print(student.name) # ligo
# 修改姓名
student.name = 'scott'
print(student.name) # scott
# 删除姓名
del student.name
print(student.name)
# AttributeError: 'Student' object has no attribute 'name'
- protected
class Student(object):
def __init__(self, name):
self.__name = name
@property
def name(self):
return self.__name
@name.setter
def name(self,value):
# 通过类型检查后,将值value存放到真实的位置self.__name
self.__name = value
@name.deleter
def name(self):
del self.__name
student = Student(name='ligo')
# 查看姓名
print(student.name) # ligo
# 修改姓名
student.name = 'scott'
print(student.name) # scott
# 删除姓名
del student.name
print(student.name)
# AttributeError: 'Student' object has no attribute 'name'
- private
class Student(object):
def __init__(self, name):
self.__name = name
def name(self):
return self.__name
def show_name(self, value):
# 通过类型检查后,将值value存放到真实的位置self.__name
self.__name = value
def del_name(self):
del self.__name
# 不使用装饰器,而是使用 包装的 形式
name = property(name, show_name, del_name)
student = Student(name='ligo')
# 查看姓名
print(student.name) # ligo
# 修改姓名
student.name = 'scott'
print(student.name) # scott
# 删除姓名
del student.name
print(student.name)
# AttributeError: 'Student' object has no attribute 'name'
标签:__,.__,封装,进阶,student,Python,self,Student,name
From: https://www.cnblogs.com/ligo6/p/18183502