python基础24 面向对象和类3

136 阅读5分钟

派生方法的实战演练

import datetime
import json
d = {
    't1': datetime.datetime.today(),
    't2': datetime.date.today()
}
res = json.dumps(d)
print(res)
  • 输出结果:

    image.png

'''
以上代码执行时会报错,原因是因为json模块在python中是有限制的。
   对于时间类型不可以序列化。
   
* 解决方式1: 手动在数据前将类型转成符合要求的数据类型 
    't1': str(datetime.datetime.today()),
    't2': str(datetime.date.today())
* 解决方式2: 利用派生方法
    报错发生的流程:
        class JSONEncoder:
            pass
        dumps(obj,cls=None):
            if cls == None:
                cls = JSONEncoder
            return cls(...)   # JSONEncoder()
   查看源码:shift+鼠标左键
      我们先查看有限制的json模块的dumps方法源码
   dumps有许多形参。
   源码如下图:
'''

image.png

   我们看到当不给cls形参传值时,会默认给cls赋值一个JSONencoder
   后面return cls().encode(obj) 就是实例化JSONencoder的对象,所以查看JSONencoder   
       JSONencoder源码如下图:

image.png

    查看源码之后发现我们报错的和default方法主动抛出的报错信息一致。
    由此得出。序列化报错是default方法触发的
        raise TypeError(f'Object of type {o.__class__.__name__} '
                        f'is not JSON serializable')
    
    
所以我们需要对源码的方法进行修改(派生方法)
    class MyJsonEncode(json.JSONEncoder):    # 继承了JSONEncoder
        def default(self, 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)  # 可以序列化的类型,不做处理,直接序列化
    res = json.dumps(d, cls=MyJsonEncode)
    print(res)
    json.dumps(d, cls=MyJsonEncode)
  • json模块可以序列化的类型只有以下类型:

    | python | JSON | | --- | --- | | dict | object | | list | array | | tuple | array | | str | string | | int | number | | float | number | | True | true | | False | false | | None | null |

面向对象三大特性之封装

  • 封装的目的是为了把数据或者功能隐藏起来(点不出来),给隐藏的数据开设特定接口,用户通过接口才能使用,我们在接口可以添加一些额外的操作

    • 在类定义的阶段使用__开头的名字,都是隐藏的属性,后续类和对象均无法直接获取
    • 隐藏的属性其实就是做了处理
        将  __变量名 处理为了 _类名__变量名
    
    ps:做了隐藏也可以访问,但是这样做失去了隐藏的意义。不推荐
    
    例:
    
    class Student(object):
        __school = '清华大学'
        def __init__(self,name,age):
            self.__name = name
            self.__age = age
        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.set_info('', '大了')
    # 我们编写python很多时候都是大家墨守成规的东西 不需要真正的限制
    

property伪装属性

  • 将方法伪装为数据,可以点出来 property
obj.name
obj.name()

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)
        self.__NAME = value     # 通过类型检查后,将值value存放到真实的位置self.__NAME
    @name.deleter      # 删除
    def name(self):
        raise PermissionError('Can not delete')  

obj = Foo('jason')
del obj.name

面向对象三大特性之多态

  • 一个事物的多种状态称为多态
    • 相同的功能应该有相同的名字,这样无论拿到哪个具体的名字,直接调用相同的功能即可
    • 多态在以前数据类型内置方法就接触过(len方法)
  • python也提供了一种强制性的操作
import abc
# 指定metaclass属性将类设置为抽象类,抽象类本身只是用来约束子类的,不能被实例化
class Animal(metaclass=abc.ABCMeta):
    # 该装饰器限制子类必须定义有一个名为talk的方法
    @abc.abstractmethod
    # 抽象方法中无需实现具体的功能
    def talk(self):
        pass
# 但凡继承Animal的子类都必须遵循Animal规定的标准
class Person(Animal):
    def talk(self):
        pass
    def run(self):
        pass
obj = Person()
  • 鸭子类型
    • 只要你长得像鸭子 走路像鸭子 说话像鸭子 那么你就是鸭子
  • 对于linux来说一切皆文件
    • Txt,Disk,Memory类有两个与文件类型同名的方法,即read和write
  • 对于python来说一切皆对象
    • 只要有数据,有功能就是对象

面向对象之反射

  • 反射就是通过字符串操作对象的数据或方法(主要有四个)
hasattr():   判断对象是否含有某个字符对应的属性
getattr():   获取对象字符串对应的属性
setattr():   根据字符串给对象设置属性
delattr():   根据字符串给对象删除属性
class Student:
    school = '清华大学'

    def choice_course(self):
        print('选课')
stu = Student()


# 需求:判断用户提供的名字在不在对象可以使用的范围内
# 方式1:利用异常处理(过于繁琐)
try:
    if stu.school:
        print(f"True{stu.school}")
except Exception:
    print("没有属性")
"""
变量名school与字符串school区别很大,两者虽然只差了引号,但是本质是完全不一样的
    stu.school
    stu.'school'
"""
# print(hasattr(stu, target_name)) # # 给一个字符串去get里面,找一下打印布尔值
# print(getattr(stu, target_name)) # 给一个字符串去get里面,找一下打印出来

while True:
    target_name = input('请输入您想要核查的名字>>>:').strip()
    '''上面的异常更加不好实现 需要用反射'''
    if hasattr(stu, target_name):
        res = getattr(stu, target_name)
        if callable(res):
            print('拿到的名字是一个函数', res())
        else:
            print('拿到的名字是一个数据', res)
    else:
        print('不好意思 您想要查找的名字 对象没有')
print(stu.__dict__)
stu.name = 'jason'
stu.age = 18
print(stu.__dict__)
setattr(stu,'gender','male')
setattr(stu,'hobby','read')
print(stu.__dict__)
del stu.name
print(stu.__dict__)
delattr(stu, 'age')
print(stu.__dict__)

"""
遇到关键字....对象....字符串就要使用反射
"""

反射实战演练

class FtpServer:
    def serve_forever(self):
        while True:
            inp = input('input your cmd>>: ').strip()
            cmd, file = inp.split()
            # 根据用户输入的cmd,判断对象self有无对应的方法属性
            if hasattr(self, cmd):
                # 根据字符串cmd,获取对象self对应的方法属性
                func = getattr(self, cmd)  
                func(file)
    def get(self, file):
        print('Downloading %s...' % file)

    def put(self, file):
        print('Uploading %s...' % file)
obj = FtpServer()
obj.serve_forever()