面向对象的三大特性、封装的介绍及属性、装饰器property、员工管理系统

【一】面向对象的三大特性

  • 封装:
    • 封装指的就是把数据与功能都整合到一起
  • 继承
  • 多态

【二】什么是封装

  • 封装是对具体对象的一种抽象
    • 意思就是将某部分功能和代码隐藏起来,在程序外边看不到,只能在程序内部使用

【三】为什么要封装?

  • 封装数据的主要原因是:保护隐私(把不想别人知道的东西封装起来)

【四】封装的方法

  • 自己的身体其实也有封装的部分
  • 自己有并且自己能用但是别人看不到也用不了
  • 方法:
    • 在变量名前面加 __

【五】封装隐藏属性

【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()
posted @ 2024-05-08 20:25  光头大炮  阅读(9)  评论(0编辑  收藏  举报