面向对象(三)

内容概要

  • 继承下的派生实际应用
  • 面向对象的三大特征之封装
  • 面向对象的三大特征之多态
  • 面向对象之反射

继承下的派生实际应用

    import datetime
    import json
	
    # 重写方法
    class MyJsonEncode(json.JSONEncoder):  # ctrl+鼠标左键参看内置函数
        def default(self,o):  # 形参o就是即将要被序列化的数据对象
            # 对o进行重写
            '''将o处理成json能够序列化的类型即可 字符串'''
            if isinstance(o,datetime.datetime):
                return o.strftime('%Y-%m-%d %H:%M:%S')  # 字符串格式化时间
            elif isinstance(o, datetime.date):
                return o.strftime('%Y-%m-%d')  # 字符串格式化时间

            return super().default(o)  # 调用内置的default父类(让父类的default方法继续执行 防止有其他额外操作)

    d1 = {'t1':datetime.datetime.today(),'t2':datetime.date.today()}
    res = json.dumps(d1,cls=MyJsonEncode)
    print(res)  # {"t1": "2022-04-08 17:50:58", "t2": "2022-04-08"}

    """
    TypeError: Object of type 'datetime' is not JSON serializable
    json不能序列化python所有的数据类型 只能是一些基本数据类型
        json.JSONEncoder

    1.手动将不能序列化的类型先转字符串
        {'t1': str(datetime.datetime.today()), 't2': str(datetime.date.today())}
    2.研究json源码并重写序列化方法
        研究源码发现报错的方法叫default
        raise TypeError("Object of type '%s' is not JSON serializable" % o.__class__.__name__)
        我们可以写一个类继承JSONEncoder然后重写default方法
    """

面向对象三大特征之封装

    # 封装的含义
    将类中的某些名字'隐藏'起来 不让外界直接调用 
    更具有保密性
    隐藏的目的是为了提供专门的通道才可访问 在通道内可以添加额外的功能

    """
    如何封装名字
        在变量名的前面加上两个下划线__
    封装的功能只在类定义阶段才能生效!!!
        在类中封装其实也不是绝对的 仅仅是做了语法上的变形而已
            __变量名       >>>     _类名__变量名
    我们虽然指定了封装的内部变形语法 但是也不能直接去访问
        看到了就表示这个属性需要通过特定的通道(接口)去访问
    """
    # 代码实操
    class Student(object):  # 在没有继承父类时括号内object有更好的兼容性
        school = '北京大学'
        __label = '学以致用'  # 用双下隐藏名字

        def __init__(self, name, age):
            self.name = name
            self.age = age
        def choose_course(self):
            print('%s正在选课'%self.name)
    stu1 = Student('Tom', 18)
    print(stu1.school)  # 北京大学
    print(stu1.name)  # Tom
    print(stu1.age)  # 18
    # print(stu1.__label) # AttributeError: 'Student' object has no attribute '__label'
    print(Student.__dict__)  # 'school': '北京大学', '_Student__label': '学以致用',
    print(Student._Student__label)  # 学以致用
    print(stu1._Student__label)  # 学以致用


    """
    将数据隐藏起来就限制了类外部对数据的直接操作,然后类内应该提供相应的接口来允许类外部间接地操作数据,
        接口之上可以附加额外的逻辑来对数据的操作进行严格地控制

    目的的是为了隔离复杂度,例如ATM程序的取款功能,该功能有很多其他功能组成
        比如插卡、身份认证、输入金额、打印小票、取钱等,而对使用者来说,只需要开发取款这个功能接口即可,其余功能我们都可以隐藏起来
    """
    class Student(object):
        __school = '北京大学'
        def __init__(self,name,son):
            self.__name = name
            self.__son = son
        # 专门设置一个可以访问到学生数据的接口
        def chenk_info(self):
            print("""
            学生姓名:%s
            学生学号:%s
            """ %(self.__name,self.__son))
        # 专门设置修改学生数据的接口
        def set_info(self,name,son):
            if len(name) == 0:
                print('名字不能为空')
                return
            if not isinstance(son,int):
                print('学号必须纯数字')
                return
            self.__name = name
            self.__son = son

    stu1 = Student('jimi',4250)
    stu1.chenk_info()  # 访问 学生姓名:jimi  学生学号:4250
    stu1.set_info('Tom',1024)  # 修改
    stu1.chenk_info()  # 学生姓名:Tom  学生学号:1024
    stu1.set_info('',666)  # 名字不能为空

   """
   由于python崇尚的是自由、简洁、大方不完全限制程序员的行为
   所以我们很多时候在类中象征性的用一个下划线开头来取名 意思也是封装的含义
   		class MyClass:
   		    _name = 'jason'
   		    def _study(self):
   		    	pass
   在类中看到下划线开头的变量名等同于两个下划线开头
   意思都是不想让你直接访问
   """
  总结:封装的目的不是隐藏 而是对外提供访问隐藏数据的接口
    	在该接口内就可以自定义额外的功能 

property伪装

property就是将方法伪装成数据

"""
扩展:
	体质指数(BMI)=体重(kg)÷身高^2(m)
"""

有时候很多数据需要经过计算才可以获得 
	但是这些数据给我们的感觉应该属于数据而不是功能
  	BMI指数>>>:应该属于人的数据而不是人的功能
class Person(object):
    def __init__(self, name, height, weight):
        self.__name = name
        self.height = height
        self.weight = weight
    @property  # 装饰器
    def BMI(self):
        return '%s的BMI指数是:%s' % (self.__name, self.weight / (self.height ** 2))

man1 = Person('Tom',1.80,65)
print(man1.BMI)  # Tom的BMI指数是:20.061728395061728

面向对象三大特征之多态

    '''
    多态:一种事物的多种形态
        eg:
            水   固态 >> 液态 >> 气态
            昆虫 虫卵 >> 幼虫 >> 成虫	
    '''

    """
    面向对象的多态性其实在很早之前就已经接触过了
        len方法也符合多态
            s1 = 'hello world'
            s2 = 'how are you'
            print(len(s1))
            print(len(s2))
    """

    '''多态的使用 子类在调用时都会调用同一相同的方法 更便于管理'''
    # 多态性
    class Animal(object):
        def speak(self):
            pass

    class Cat(Animal):
        def speak(self):
            print('喵喵喵')

    class Dog(Animal):
        def speak(self):
            print('汪汪汪')

    class Pig(Animal):
        def speak(self):
            print('哼哼哼')

    c1 = Cat()  # 实例化猫🐱
    c1.speak()  # 喵喵喵
    d1 = Dog()  # 实例化狗🐕
    d1.speak()  # 汪汪汪
    p1 = Pig()  # 实例化猪🐖
    p1.speak()  # 哼哼哼
    """
    上述场景下 虽然体现了事物的多态性 但是并没有完整的体现出来
    因为现在不同的形态去叫 需要调用不同的方法 不够一致

        只要你是动物 那么你想要说话 就应该调用一个相同的方法 这样便于管理
    """

    """
    多态性的好处在于增强了程序的灵活性和可扩展性,比如通过继承Animal类创建了一个新的类,实例化得到的对象obj,可以使用相同的方式使用obj.speak()

    面向对象的多态性也需要python程序员自己去遵守
    虽然python推崇的是自由 但是也提供了强制性的措施来实现多态性
        不推荐使用
    """
    
    # 使用abc模块
    import abc
    # 指定metaclass属性将类设置为抽象类,抽象类本身只是用来约束子类的,不能被实例化
    class Animal(metaclass=abc.ABCMeta):
        @abc.abstractmethod # 该装饰器限制子类必须定义有一个名为talk的方法
        def talk(self): # 抽象方法中无需实现具体的功能
            pass
    class Person(Animal): # 但凡继承Animal的子类都必须遵循Animal规定的标准
        def talk(self):
            print('啊啊啊!!!')
    # 若子类中没有一个名为talk的方法则会抛出异常TypeError,无法实例化
    p1=Person() # 实例化一个人
    p1.talk()  # 啊啊啊!!!
    
    """
    由多态性衍生出一个鸭子类型理论
        只要你看着像鸭子 走路像鸭子 说话像鸭子 那么你就是鸭子!!!
    """
    # 鸭子类型的实战案例
    """
    在linux系统中有一句话>>>:一切皆文件!!!
        内存可以存取数据
        硬盘可以存取数据
        ...
        那么多有人都是文件
    """
    class Memory(object):
        def read(self):
            pass
        def write(self):
            pass
    class Disk(object):
        def read(self):
            pass
        def write(self):
            pass
    # 得到内存或者硬盘对象之后 只要想读取数据就调用read 想写入数据就调用write 不需要考虑具体的对象是谁

面向对象之反射

# 什么是反射
	专业解释:指程序可以访问、检测和修改本身状态或者行为的一种能力
	大白话:其实就是通过字符串来操作对象的数据和功能
    
# 反射需要掌握的四个方法
  hasattr():判断对象是否含有字符串对应的数据或者功能
  getattr():根据字符串获取对应的变量名或者函数名
  setattr():根据字符串给对象设置键值对(名称空间中的名字)
  delattr():根据字符串删除对象对应的键值对(名称空间中的名字)

# 反射实际应用
class Student(object):
    school = '清华大学'
    def get(self):
        pass
# 编写一个小程序 判断Student名称空间中是否含有用户指定的名字 如果有则取出展示
'''字符串的school 跟 变量名school差距大不大?  本质区别'''
'''
用户输入之后的是字符串
将字符串  >>> 变量名
如不用不使用反射不太容易实现 
'''
print(hasattr(Student, 'school'))  # True
print(hasattr(Student, 'get'))  # True
print(hasattr(Student, 'post'))  # False

guess_name = input('请输入你想要查找的名字>>>:').strip()
if hasattr(Student, guess_name):
    target_name = getattr(Student, guess_name)
    if callable(target_name):
        print('类中有一个功能名字是%s'%guess_name,target_name)
    else:
        print('类中有一个数据名字是%s'%guess_name,target_name)
else:
    print('类中没有该名字')

setattr(Student,'level','贵族学校')
print(Student.__dict__)  # 可用的名字会多 'level': '贵族学校'

def index():
    hobby='啪啪啪'
    return hobby

stu1 = Student()
setattr(stu1, '智商', 10000)
setattr(stu1, '爱好', index())  
print(stu1.__dict__)  # # {'智商': 10000, '爱好': '啪啪啪'}
delattr(stu1, '爱好')  # 相当于del stu1['爱好']
print(stu1.__dict__)  # {'智商': 10000}
"""
什么时候使用反射 可以记固定的口诀
	以后只要在业务中看到关键字 
		对象 和 字符串(用户输入、自定义、指定) 那么肯定用反射
"""

反射的实际案例

# 利用反射获取配置文件中的配置信息
"""一切皆对象 文件也是对象"""

# sttings模块代码
NAME = 'NAME'

# 执行文件
import setting
# 获取对象中所有可以使用的名字
print(dir(setting))  # ['NAME'...]  
print(getattr(setting, 'NAME'))	# Tom

# hasattr
class FtpServer:
    def serve_forever(self):
        while True:
            inp = input('input your cmd>>: ').strip()
            cmd, file = inp.split()  # 切割空格符 解压赋值
            if hasattr(self, cmd):  # 根据用户输入的cmd,判断对象self有无对应的方法属性
                func = getattr(self, cmd)  # 根据字符串cmd,获取对象self对应的方法属性
                func(file)

    def get(self, file):
        print('Downloading %s...' % file)

    def put(self, file):
        print('Uploading %s...' % file)


obj = FtpServer()
obj.serve_forever()

posted @ 2022-04-08 21:34  Mr_胡萝卜须  阅读(7)  评论(0编辑  收藏  举报