【一】面向对象的三大特性
【二】什么是封装
- 封装是对具体对象的一种抽象
- 意思就是将某部分功能和代码隐藏起来,在程序外边看不到,只能在程序内部使用
【三】为什么要封装?
- 封装数据的主要原因是:保护隐私(把不想别人知道的东西封装起来)
【四】封装的方法
- 自己的身体其实也有封装的部分
- 自己有并且自己能用但是别人看不到也用不了
- 方法:
【五】封装隐藏属性
【1】数据属性
- Python的Class机制采用双下划线开头的方式将属性隐藏起来(设置成私有的)
- 但其实这仅仅只是一种变形操作,类中所有双下滑线开头的属性都会在类定义阶段、检测语法时自动变成
_类名__属性名
的形式:
class Person:
# 变形为_Person__NAME
__NAME = "Dream"
# 定义函数时,会检测函数语法,所以__开头的属性也会变形
def __init__(self):
# 变形为self._Person__age
self.__age = 18
# 变形为_Foo__run
def __run(self):
print('__f1 run')
# 定义函数时,会检测函数语法,所以__开头的属性也会变形
def walk(self):
# 变形为self._Person__run()
self.__run()
print(Person.__N)
# 报错AttributeError:类Person没有属性__N
obj = Person()
print(obj.__x)
# 报错AttributeError:对象obj没有属性__x
class Person:
# 在变量名前面加 __ : 在类初始化对象的时候会对当前变量名进行变形 变形成 _Person__变量名
__SCHOOL_NAME = '清华'
def __init__(self, name):
self.name = name
student = Person(name='dream')
print(student.name)
# 查看类的名称空间
# {'_Person__SCHOOL_NAME': '清华',}
print(Person.__dict__)
print(student.__SCHOOL_NAME) # 找不到
print(student._Person__SCHOOL_NAME) # 能找到
【2】函数属性
- 在类内部是可以直接访问双下滑线开头的属性的,比如
self.__f1()
,因为在类定义阶段类内部双下滑线开头的属性统一发生了变形。
class Person:
# 在变量名前面加 __ : 在类初始化对象的时候会对当前变量名进行变形 变形成 _Person__变量名
__SCHOOL_NAME = '清华'
def __init__(self, name):
self.name = name
# 函数属性 : 在类初始化对象的时候会对当前变量名进行变形 变形成 _Person__变量名
def __change_name(self):
self.name = 'nb_' + self.name
student = Person(name='dream')
print(student.name)
# 查看类的名称空间
# {'_Person__SCHOOL_NAME': '清华','_Person__change_name': <function Person.__change_name at 0x000001D09384C0D0>,}
print(Person.__dict__)
print(student.__change_name())
# 当我们在对象中调用相关属性的时候会发现,找不到
class Person:
# 变形为_Person__NAME
__NAME = "Dream"
# 定义函数时,会检测函数语法,所以__开头的属性也会变形
def __init__(self):
# 变形为self._Person__age
self.__age = 18
# 变形为_Foo__run
def __run(self):
print('__f1 run')
# 定义函数时,会检测函数语法,所以__开头的属性也会变形
def walk(self):
# 变形为self._Person__run()
self.__run() # 等同于 self._Person__run()
print(self.__NAME) # 等同于 print(self._Person__age)
【3】变形操作只会发生一次
- 变形操作只在类定义阶段发生一次,在类定义之后的赋值操作,不会变形。
class Person:
# 变形为_Person__NAME
__NAME = "Dream"
# 定义函数时,会检测函数语法,所以__开头的属性也会变形
def __init__(self):
# 变形为self._Person__age
self.__age = 18
# 变形为_Foo__run
def __run(self):
print('__f1 run')
# 定义函数时,会检测函数语法,所以__开头的属性也会变形
def walk(self):
# 变形为self._Person__run()
self.__run() # 等同于 self._Person__run()
print(self.__NAME) # 等同于 print(self._Person__age)
# 修改类的变量
Person.__NAME = "Hope"
# 查看内存空间 --- 发现并没有被修改
print(Person.__dict__)
# {'__module__': '__main__', '_Person__NAME': 'Dream', '__init__': <function Person.__init__ at 0x00000260E28E13F0>, '_Person__run': <function Person.__run at 0x00000260E28E1B40>, 'walk': <function Person.walk at 0x00000260E28E1BD0>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None, '__NAME': 'Hope'}
# 实例化得到类对象
person = Person()
# Dream
# 修改对象变量
person.__sex = "男"
person.__age = 22
# 查看对象的名称空间 --- 新增了相应的键值
print(person.__dict__)
# {'_Person__age': 18, '__sex': '男', '__age': 22}
# 可以直接根据键取值
print(person.__sex)
# 男
【六】开放接口
【1】隐藏数据属性
- 将数据隐藏起来就限制了类外部对数据的直接操作,然后类内部应该提供相应的接口来允许类外部间接地操作数据,接口之上可以附加额外的逻辑来对数据的操作进行严格地控制
class Teacher:
def __init__(self, name, age):
self.__name = name
self.__age = age
def set_info(self, name, age):
# 修改名字和修改年龄之前要校验当前的格式是否正确
# 名字前面必须 + lj_
if not name.startswith('lj_'):
raise ValueError("名字必须是 lj 前缀")
# 年龄必须是数字且大于 0
if not age.isdigit():
raise ValueError("年龄必须是数字")
if int(age) < 0:
raise ValueError("年龄超出常人,建议回炉重造!")
self.__name = name
self.__age = age
# 做一个接口查看当前讲师的个人信息
def tell_info(self):
print(f"当前讲师是 :>>>> {self.__name} 年龄是 :>>>> {self.__age}")
teacher = Teacher(name="dream", age=18)
# teacher.tell_info()
#修改当前讲师的姓名和年龄
teacher.set_info(name='lj_opp',age='1')
teacher.tell_info()
【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:
def __init__(self, name):
self.name = name
@property
def vip_name(self):
return self.name
student = Student(name='chosen')
print(student.name) # chosen
print(student.vip_name) # chosen
【2】BMI例子
- MI指数是用来衡量一个人的体重与身高对健康影响的一个指标
- 成人的BMI数值:
- 过轻:低于18.5
- 正常:18.5-23.9
- 过重:24-27
- 肥胖:28-32
- 非常肥胖, 高于32
- 计算公式
体质指数(BMI)=体重(kg)÷身高^2(m)
EX:70kg÷(1.75×1.75)=22.86
- 身高或体重是不断变化的
- 所以在每次要查看BMI值都需要通过计算才能得到,但很明显BMI听起来更像是一个特征而不是功能,所以就有了装饰器 property
- 可以将类中的函数“伪装成”对象的数据属性,对象在访问该特殊属性时会触发功能的执行,然后将返回值作为本次访问的结果
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)
obj = People('dream', 62, 1.70)
# 触发方法bmi的执行,将obj自动传给self,执行后返回值作为本次引用的结果
# 正常我们调用对象的方法应该是 obj.bmi() 但是这里我们没有 加 () 就调用成功了
print(obj.bmi)
# 21.453287197231838
【3】为什么要使用property
- 在所有编程语言中都有三种封装方式
- public : 公开
- protected : 对外不公开,对内公开
- private : 对谁都不公开
class Foo:
def __init__(self, val):
# 将属性隐藏起来
self.__NAME = val
@property
def name(self):
return self.__NAME
@name.setter
def name(self, value):
# 在设定值之前进行类型检查
if not isinstance(value, str):
raise TypeError('%s must be str' % value)
# 通过类型检查后,将值value存放到真实的位置self.__NAME
self.__NAME = value
@name.deleter
def name(self):
raise PermissionError('Can not delete')
f = Foo('dream')
print(f.name)
# 触发name.setter装饰器对应的函数name(f,'Hope')
f.name = 'Hope'
# 触发name.setter对应的的函数name(f,123),抛出异常TypeError
f.name = 123
# 触发name.deleter对应的函数name(f),抛出异常PermissionError
del f.name
class Foo:
def __init__(self, val):
# 将属性隐藏起来
self.__NAME = val
def get_name(self):
return self.__NAME
def set_name(self, value):
# 在设定值之前进行类型检查
if not isinstance(value, str):
raise TypeError('%s must be str' % value)
# 通过类型检查后,将值value存放到真实的位置self.__NAME
self.__NAME = value
def del_name(self):
raise PermissionError('Can not delete')
# 不使用装饰器,而是使用 包装的 形式
name = property(get_name, set_name, del_name)
f = Foo('dream')
print(f.name)
# 触发name.setter装饰器对应的函数name(f,'Hope')
f.name = 'Hope'
# 触发name.setter对应的的函数name(f,123),抛出异常TypeError
f.name = 123
# 触发name.deleter对应的函数name(f),抛出异常PermissionError
del f.name
【八】员工管理系统(使用类方法)
import hashlib
import json
import os
import random
class Tools(object):
def __init__(self):
self.BASE_DIR = os.path.dirname(__file__)
self.DB_DIR = os.path.join(self.BASE_DIR, 'data')
os.makedirs(self.DB_DIR, exist_ok=True)
self.file_path = os.path.join(self.DB_DIR, 'emp_data.json')
def check_data_type(self):
...
def get_username_password(self):
username = input("请输入用户名 :>>>> ").strip()
password = input("请输入 密码 :>>>> ").strip()
return username, password
# 把函数属性编程数据属性 property
# salt() --- @property ---> salt
@property
def salt(self):
code = ''
for i in range(5):
num = random.randint(0, 9)
alf = chr(random.randint(65, 90))
add = random.choice([num, alf])
code = "".join([code, str(add)])
return code
def encrypt_password(self, salt=None, password=None):
# 将所有数据变为字符串
original_data = str(salt) + str(password)
# 将字符串转换为二进制
encrypted_data = original_data.encode()
# 生成md5对象
md5 = hashlib.md5()
# nd5加密数据
md5.update(encrypted_data)
# 返回 32 为 16 进制的字符串
return md5.hexdigest()
def read_data(self):
try:
# 读取文件路径
# 读取文件数据
with open(file=self.file_path, mode='r', encoding='utf-8') as fp:
data = json.load(fp)
return data
except:
return {}
def save_data(self, data):
with open(file=self.file_path, mode='w', encoding='utf-8') as fp:
json.dump(obj=data, fp=fp, ensure_ascii=False)
class Worker(object):
# 初始化属性
def __init__(self):
self.func_menu = f'''
------------------ 功能菜单 ------------------
1.注册
2.登录
3.添加员工信息
4.删除员工信息
5.查看员工信息
q.退出系统
'''
self.func_dict = {
'1': self.register,
'2': self.login,
'3': self.add_emp,
'4': self.del_emp,
'5': self.check_emp,
}
self.tools = Tools()
self.login_dict = {}
# 校验当前用户是否存在!
def __exist_user(self, username):
# 读取到所有的用户信息 {'dream':{'name':"dream","age":18}}
emp_data_dict_all = self.tools.read_data()
# 根据用户名获取到指定的用户字典 {'name':"dream","age":18}
emp_data_dict = emp_data_dict_all.get(username)
if not emp_data_dict:
return False
else:
return True
# 登录
def login(self):
print(f'欢迎来到登录功能!')
# 获取用户名和密码
username, password = self.tools.get_username_password()
# 校验用户名是否存在
exist_user = self.__exist_user(username)
if not exist_user:
return False, f'当前用户 {username} 不存在!请先注册!'
# 存在则继续登录
user_data_dict_all = self.tools.read_data()
user_data_dict = user_data_dict_all.get(username)
salt = user_data_dict.get('salt')
old_password = user_data_dict.get('password')
new_password = self.tools.encrypt_password(salt=salt, password=password)
if old_password != new_password:
return False, f'当前密码错误!'
# 初始化登录字典
self.login_dict['username'] = username
return True, f'当前用户 {username} 登录成功!'
# 注册
def register(self):
print(f"欢迎来到注册功能!")
user_data_dict_all = self.tools.read_data()
# 需要用户名和密码
username, password = self.tools.get_username_password()
# 校验当前用户名和密码是否存在
user_exist = self.__exist_user(username=username)
# 用户存在咋不允许注册,去登录
if user_exist:
return False, f'当前用户 {username} 已存在!请登录!'
# 获取到密码加密需要用到盐
salt = self.tools.salt
# 对密码进行加密
encrypted_password = self.tools.encrypt_password(salt=salt, password=password)
# 更新所有人的用户数据
user_data_dict_all[username] = {
'username': username,
'password': encrypted_password,
'salt': salt
}
self.tools.save_data(data=user_data_dict_all)
return True, f'当前用户 {username} 注册成功!'
def login_auth(func):
def inner(*args, **kwargs):
# print(func) # <function Worker.add_emp at 0x00000187E1466430>
# print(kwargs) # {}
self = args[0]
# print(args) # (<__main__.Worker object at 0x00000187E116A760>,)
# print(self.login_dict) # {'username': 'dream'}
if not self.login_dict.get('username'):
return False,'请先登录!'
else:
return func(*args, **kwargs)
return inner
# 添加员工
@login_auth # login_auth = login_auth(add_emp)
def add_emp(self):
...
# 删除员工
def del_emp(self):
print(self) # <__main__.Worker object at 0x000001EFAAA8C640>
...
# 查看员工信息
def check_emp(self):
...
def main(self):
while True:
print(self.func_menu)
func_id = input("请输入功能ID :>>>> ").strip()
if not func_id.isdigit():
print(f"当前ID格式不正确!")
continue
if func_id not in self.func_dict:
print(f"当前ID不存在!")
continue
func = self.func_dict[func_id]
flag, msg = func()
if not flag:
print(msg)
continue
else:
print(msg)
if __name__ == '__main__':
s = Worker()
s.main()