面向对象封装、多态、反射

概要

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

内容详细

继承下的派生实际应用

import datatime
import json
d1 = {'t1': datetime.datetime.today(), 't2': datetime.date.today()}
res = json.dumps(d1, cls=MyJsonEncoder)
print(res) 
1.在运行后计算机会出现报错
2.得到报错信息为:TypeError: Object of type 'datetime' is not JSON serializable  # 意思是json不能序列化python所有的数据类型 只能是一些基本的数据类型 因为d1字典中的v值不属于json序列化的数据类型
3.解决这个报错问题的方法有两种思路:
    第一种:手动将不能序列化的类型先转化成字符串类型 直接在v值前面加字符串类型转换str
    {'t1': str(datetime.datetime.today()), 't2': str(datetime.date.today())}   # 简单粗暴
    第二种:不能序列化的原因 不就是出现在json身上吗,不能将所有的数据类型都支持json序列化,所以我们直接研究json的底层源码并重写它序列化方法不就可以了
4.从第二种思路出发,我们在点开dump内部源码中发现cls =None,往下看得时候又发现了JSONEncoder,我们通过json内部源码发现啊JSONEncoder就是一个类(class JSONEncoder) ,在这个类下面,我们又找到了def default下面的信息:raise TypeError("Object of type '%s' is not JSON serializable" % o.__class__.__name__),发现与我们序列化报错的信息一样,由此我们就知道知道哦问题就是出现在这里,我们想是不是把def default方法里面的数据我们自己写不就好了,问题不就解决了吗,因为我们发现def default方法是类中的子类,所以我们就想到了用派生的方法,只要我们可以写一个类继承JSONEncoder然后再重写default方法就可以了 写完default方法后也是会先执行我们写的default方法,这样我们就达到了重写序列化的目的
所以有了:
class MyJsonEncoder(json.JSONEncoder):
    def default(self,0):
        print('重写了') #  结果会出现null 因为我们没写下面子类的条件,运行时因无法识别 故用null来写
        print('重写了',0)  # 我们通过打印发现形参0就是即将要被序列化的数据对象  所以针对问题,我们只要将0处理成json能够序列化的数据类型即可 所以我们在类下面添加一些特定的条件:
        if isinstance(o,datetime.datetime):
            return o.strftime('%Y-%m-%d %X')
        elif isinstance(o, datetime.date):
            return o.strftime('%Y-%m-%d')
        # 最后在继承父类的default方法继续执行 防止有其他额外的操作
        return super().default(o)
综上第二种方法的思想:就是在问题出现中间,我们先在中间截住,我们直接通过先继承父类的功能,然后在类里面的子类重新添加一些我们的条件,从而会自动优先之行我们修改后的子类,而不是去执行父类的子类,最后在继承父类的方法继续执行即可 防止有其他额外的操作     

面向对象三大特性之封装

封装的定义:将类中的某些名字'隐藏起来' 不让外界直接调用

隐藏的目的是为了提供专门的通道去访问 在通道内可以添加额外的功能

代码实操

class Student(object):
#     school = '清华大学'
#     __label = '逆来顺受  
def __init__(self, name, age):
#         self.name = name
#         self.age = age
#     def choose_course(self):
#         print('%s正在选课'%self.name)
# stu1 = Student('jason', 18)
# print(stu1.school)  # 清华大学
# print(stu1.name)  # jason
# print(stu1.age)  # 18
# print(stu1.__label)
# print(Student.__dict__)  # '_Student__label': '逆来顺受'
# print(Student._Student__label)
# print(stu1._Student__label)

其实封装并没有真正的隐藏 而是自动转换成了特定的语法 _student__label

如何封装名字

​ 在变量名的前面加上两个下划线__,需要注意的是:封装的功能只在类定义阶段才能生效

​ 在类中封装其实也不是说绝对隐藏的,仅仅是做了语法上的变形而已

__变量名   >>>>>> 通过类名._变量名的方法>>>>_类名__变量名


我们虽然指定了封装的内部变形语法 但是也不能直接去访问

如果想要看到这个属性 需要通过特定的通道(接口)去访问

比如需要专门开设一个访问学生数据的通道(接口)

def check_info(self):
        print("""
        学生姓名:%s
        学生年龄:%s
        """ % (self.__name, self.__age))

比如需要专门开设一个修改学生数据的通道(接口),在接口下我们可以通过添加一些额外的条件来修改

def set_info(self,name,age):
        if len(name) == 0:
            print('用户名不能为空')
            return
        if not isinstance(age,int):
            print('年龄必须是数字')
            return
        self.__name = name
        self.__age = age
        
stu1 = Student('jason', 18)
stu1.check_info()  # 需要通过接口来查看隐藏的数据
stu1.set_info('jasonNB',28) # 也可以通过接口来实现对隐藏的函数进行修改
stu1.check_info()
stu1.set_info('','haha')  # 不满足接口的设置的条件无法获取隐藏的数据

# 封装的思想
将数据隐藏起来就'限制'了类外部对数据的'直接'操作,然后类内应该提供相应的'接口'来允许类外部'间接'地'操作数据',接口之上可以'附加额外的逻辑'来对数据的操作进行严格的控制 目的是为了隔离复杂度

property 伪装

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):
        # print('%s的BMI指数是:%s' % (self.name, self.weight / (self.height ** 2)))
        return '%s的BMI指数是:%s' % (self.__name, self.weight / (self.height ** )

                                           

面向对象三大特性之多态

# 什么是多态?
一种事物的多种形态
例如:
    水  固态 液态 气态
    动物  猫 狗 猪
# 多态性
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()
# d1 = Dog()
# p1 = Pig()
# c1.speak()
# d1.speak()
# p1.speak()

其实面向对象的多态性在很早之前我们就已经接触过了

s1 = 'hello world'
l1 = [1, 2, 3, 4]
d1 = {'name': 'jason', 'pwd': 123}
print(len(s1))
print(len(l1))
print(len(d1))

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

面向对象的多态性也需要python程序员自己去遵守

由多态性衍生出一个鸭子类型理论

只要你看着像鸭子 走路像鸭子 说话像鸭子 那么你就是鸭子

在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差距大不大"""?
本质区别
# guess_name = input('请输入你想要查找的名字>>>:').strip()
  # 不使用反射不太容易实现
  # print(hasattr(Student, 'school'))  # True
  # print(hasattr(Student, 'get'))  # True
  # print(hasattr(Student, 'post'))  # False

  # print(getattr(Student, 'school'))  # 清华大学
  # print(getattr(Student, 'get'))  # <function Student.get at 0x10527a8c8>

  # 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__)

  def index():
      pass
  obj = Student()
  setattr(obj, '血量', 10000)
  setattr(obj, '功能', index)
  print(obj.__dict__)
  delattr(obj, '功能')
  print(obj.__dict__)


反射的使用场景,什么时候使用反射,可以牢记的口诀:

以后只要在业务中看到关键字:对象和字符串(用户输入、自定义、指定)那么肯定是用反射

posted @ 2022-04-09 00:01  一颗平凡的小石头  阅读(36)  评论(0)    收藏  举报