python二十九课--对象的封装与多态等知识

上周内容回顾

  • 动静态方法

    类体代码中编写的函数有三种类型
    
    1.绑定给对象的方法:对象调用自动当做第一个参数传入
    	类中直接定义函数
        	class C1:
                def func(self):
                    pass
    2.绑定给类的方法:类、对象调用自动将类当做第一个参数传入
     	装饰器
     		class C2:
                @classmethod  
                def index(cls):
                    pass
    3.普普通通的函数
    	装饰器
        	class C3:
                @staticmethod
                def foo():
                    pass
    
  • 面向对象三大特性之继承

    继承的本质也是站在巨人的肩膀上编程
    
    父类、超类、基类
    子类、派生类
    
    在类的定义阶段类名后面加括号填写要继承的其他类名即可并且支持多个逗号隔开
    class Son(F1):
    	pass
    
    class Son(F1, F2, F3):
    	pass
    
  • 对象查找属性的顺序

    不继承
    	对象自身	产生对象的类
    单继承
    	对象自身	产生对象的类		类的父类们	
    多继承
    	对象自身	产生对象的类		类的父类们	
    """
    1.菱形继承与非菱形继承
    	广度优先
    	深度优先
    2.mro列表
    3.以后看到self点名字一定要搞清楚self指代的是哪个对象!!!
    4.经典类与新式类
    	object
    ps:python2与python3的差异有哪些
    """
    

派生方法

派生:子类重写父类的方法,并且在重写的方法里面用super()调用一下父类的方法,然后在调的过程之上添加一些额外的操作

派生方法:子类中编写了与父类中相同的方法 并在该方法调用了父类的方法

super()    # 子类调用父类的方法
"""
class C:
    def func1(self):
        print('from C func1')

class A(C):
    def func1(self):
        print('from A func1')

    def func2(self):
        print('from A func2')
        super().func1()      # 调用的C里面的func1
        self.func1()         # 调用的B里面的func1

class B(A):
    def func1(self):
        print('from B func1')

obj = B()
obj.func2()
print(B.mro())
"""

今日内容概要

  • 派生方法实战演练
  • 面向对象三大特性之封装
  • 面向对象三大特性之多态
  • 面向对象之反射
  • 反射的实战案例

今日内容详细

派生方法实战演练

import json
import datetime

d = {
    't1': datetime.date.today(),
    't2': datetime.datetime.today(),
    't3': 'jason'
}
res = json.dumps(d)         # 会报错
print(res)


"""
序列化报错
raise TypeError(f'Object of type {o.__class__.__name__} '
TypeError: Object of type date is not JSON serializable
"""
"""
json能够序列化的数据是有限的>>>:里里外外都必须是下列左边的类型,才能序列化。
    +-------------------+---------------+
    | Python            | JSON          |
    +===================+===============+
    | dict              | object        |
    +-------------------+---------------+
    | list, tuple       | array         |
    +-------------------+---------------+
    | str               | string        |
    +-------------------+---------------+
    | int, float        | number        |
    +-------------------+---------------+
    | True              | true          |
    +-------------------+---------------+
    | False             | false         |
    +-------------------+---------------+
    | None              | null          |
    +-------------------+---------------+
"""
# 1.转换方式1:手动转类型(简单粗暴)
d = {
     't1': str(datetime.date.today()),
     't2': str(datetime.datetime.today())
 }
res = json.dumps(d)
print(res)
------------------------------------------------------

2.转换方式2:派生方法(儒雅高端)------该例题要多看看!!!!!!

json.JSONEncoder  # 键盘Ctrl键按住,鼠标左键点一点代码,查看能够被json序列化的数据类型。
源码里面的JSONEncoder  # 键盘Ctrl键按住,鼠标左键点一点代码,再查看源码

"""
查看dumps源码:注意cls参数,默认传JsonEncoder
查看该类的源码,发现default方法是报错的发起者!!!
编写类,继承JsonEncoder类,并重写default方法, 之后调用dumps手动传cls=我们自己写的类
"""
class MyJsonEncoder(json.JSONEncoder):
    def default(self, o):            # 形参o接收无法被序列化的数据!!!
        """
        :param o: 接收无法被序列化的数据
        :return: 返回可以被序列化的数据
        """
        if isinstance(o, datetime.datetime):  # 判断形参o,是不是datetime类型
            return o.strftime('%Y-%m-%d %X')  # 如果是则处理成可以被序列化的类型
        elif isinstance(o, datetime.date):  # 判断形参o,是不是datetime.date类型
            return o.strftime('%Y-%m-%d')  # 如果是则处理成可以被序列化的类型
        return super().default(o)  # 最后还是调用原来的方法 防止有一些额外操作没有做!!!

res = json.dumps(d, cls=MyJsonEncoder)
print(res)

这样就实现了,序列化非默认的类型了!!!
以后就可以把这个类写在一个公共的地方,将来需要序列化的时候,如果项目里面频繁的需要产生一些json无法默认序列化的数据时,就可以用这个类来替代了,传参的时候cls=MyJsonEncoder,强行指定cls用我的类,这样就不需要每次手动的加一个str来转化了非json格式数据了。

image
.
image
.
当json序列化一个不能序列化的数据的时候,报错就是由def defaut方法发出的!!!
image
.
image
.
.
.

面向对象三大特性--------------封装

封装:就是将数据和功能'封装'起来

# 封装之隐藏  与  封装之伪装

# 隐藏:将数据和功能隐藏起来不让用户直接调用,而是开发一些接口,
       间接调用从而可以在接口内添加额外的操作

# 伪装:将类里面的方法伪装成类里面的数据

# 类在定义阶段 名字前面有两个下划线 那么该名字会被隐藏起来 无法直接访问

# 在python中其实没有真正意义上的隐藏 仅仅是换了个名字而已 _类名__名字

.

class MyClass:
    school_name = '老女孩大学'
    _ = '嘿嘿嘿'
    _name = 'tony'
    __age = 18  # 类在定义阶段,名字前面有两个下划线,该名字会被隐藏起来,无法直接访问
    def __choice_course(self):  # 在类的定义阶段,方法名前面有两个下划线,方法名字也会被隐藏起来,无法直接访问
        print('老北鼻正在选课')
-------------------------------------------------------------
# 类在定义阶段!!!,名字前面有两个下划线,该名字会被隐藏起来,无法直接访问
MyClass.__hobby = 'JDB'   # 在类的名称空间加属性,不在类的定义阶段,故__hobby无法隐藏
print(MyClass.__hobby)    # 结果为'JDB'

obj = MyClass()
obj.__addr = '派出所'   # 在对象的名称空间加属性,不在类的定义阶段,故__addr无法隐藏
print(obj.__addr)             # 结果为'派出所'

print(MyClass.__dict__)
# 在python中其实没有真正意义上的隐藏 仅仅是把名字变了一下而已,变成了 _类名__名字
print(MyClass._MyClass__age)     # 这样就能正常调用了
--------------------------------------------------------

image
.
image
.
image
.
.
.

隐藏真正的含义:

把一些简单的属性隐藏起来,然后开设一些接口,这些接口能够让我们访问的更方便,但可以在接口里面做一些手脚的,这才是隐藏的本质!!!

class Person:
    def __init__(self, name, age, hobby):
        self.__name = name      # 对象也可以拥有隐藏的属性
        self.__age = age
        self.__hobby = hobby
    def get_info(self):        # 类体代码中 是可以直接使用隐藏的名字
        print(f"""
        姓名:{self.__name}
        年龄:{self.__age}
        爱好:{self.__hobby}
        """)
    def set_name(self, new_name): # 隐藏的属性,开放修改的接口,可以自定义很多功能
        if len(new_name) == 0:
            raise ValueError('你好歹写点东西')  # 主动报错
        if new_name.isdigit():
            raise ValueError('名字不能是数字')  # 主动报错
        self.__name = new_name

obj = Person('jason', 18, 'read')
print(obj.__name)    # 也调不到隐藏的属性
obj.get_info()       # 调用接口,查看隐藏数据
obj.set_name('tony老师')   # 调用接口,改隐藏数据!!!
obj.set_name('')   # 调用接口,改隐藏数据!!!
隐藏了属性,但是开放了接口,将来想看隐藏的数据,就直接调get_info(self)函数,就可以看到隐藏的属性了。想改隐藏的数据,就直接调set_name(self, new_name)函数,就可以改了。

image
.
image
.
image
这种隐藏的方式和文件的本质有点像
文件本质是:操作系统开放给用户操作计算机硬盘的快捷方式!!!隐藏复杂丑陋的接口,开放简单的快捷的接口!!!

以后我们在编写面向对象代码类的定义时,也会看到很多单下划线开头的名字!!!

表达的意思通常也是不要直接访问,而是查找一下下面,可能定义的接口!!!




伪装

伪装:将类里面的方法伪装成类里面的数据,以后调用装饰的函数时就不用加括号了,在源码里面会看到!!!

对象.errors 实际上是运行的errors函数 因为定义在类里面的errors函数被@property装饰了!!!
image

class C:
	def func(self):pass
    obj = C()
    obj.func()
    '''让别人根本看不出来func到底是个方法还是一个数据,经过伪装后,'''
    obj.func   # 把一个方法伪装成一个数据
-----------------------------------------------
BMI指数:衡量一个人的体重与身高对健康影响的一个指标
    体质指数(BMI)=体重(kg)÷身高^2(m)        18-24之间都是正常的
	 EX:70kg÷(1.75×1.75)=22.86

class Person(object):
    def __init__(self, name, height, weight):
        self.name = name
        self.height = height
        self.weight = weight
    @property       # 伪装装饰器,以后调用装饰的函数时就不用加括号了
    def BMI(self):
        return self.weight / (self.height ** 2)

p1 = Person('jason', 1.83, 78)
p1.BMI()            # 想将,BMI应该作为人的基本数据,而不是方法
print(p1.BMI)       # 利用装饰器伪装成数据
--------------------------------------------------------
class Foo:
    def __init__(self, val):
        self.__NAME = val       # 将属性隐藏起来

    @property  # 这样,类产生的对象点name也能访问到这个隐藏的__name的值
    def name(self):
        return self.__NAME

    @name.setter  # 用户通过对象名点name=  触发下面的函数的运行
    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')

f = Foo('jason')
print(f.name)
f.name = 'jason123'  # 当用户想改名字对应的值时
# 这个时候左边实际上运行的是函数方法,而不是点的变量,所以操作不了

f.name = 'jason'  # 触发name.setter装饰器对应的函数name(f,'jason')
f.name = 123    # 触发name.setter对应的的函数name(f,123),抛出异常TypeError
del f.name     # 触发name.deleter对应的函数name(f),抛出异常PermissionError

.
.
.

三大特性之多态

多态:一种事物的多种形态

class Animal:
    def spark(self):
        '''叫的方法'''
        pass

class Cat(Animal):
    # def miao(self):
    #     print('喵喵喵')
    def spark(self):
        print('喵喵喵')

class Dog(Animal):
    # def wang(self):
    #     print('汪汪汪')
    def spark(self):
        print('汪汪汪')

class Pig(Animal):
    # def heng(self):
    #     print('哼哼哼')
    def spark(self):
        print('哼哼哼')
"""
面向对象中多态意思是, 一种事物可以有多种形态但是针对相同的功能应该定义相同的方法
这样无论我们拿到的是哪个具体的事物 都可以通过相同的方法调用功能
"""
-----------------------------------------------------
s1 = 'hello world'
l1 = [11, 22, 33, 44]
d = {'name': 'jason', 'pwd': 123}
print(s1.__len__())
print(l1.__len__())
print(d.__len__())
"""
鸭子类型:只要你看上去像鸭子 走路像鸭子 说话像鸭子 那么你就是鸭子
"""
-------------------------------------------------------
# linux系统
"""
文件      能够读取数据也能够保存数据
内存      能够读取数据也能够保存数据
硬盘      能够读取数据也能够保存数据
......
一切皆文件
"""
class File:
    def read(self): pass

    def write(self): pass

class Memory:
    def read(self): pass

    def write(self): pass

class Disk:
    def read(self): pass

    def write(self): pass

'''python永远提倡自由简介大方 不约束程序员行为 但是多态提供了约束的方法'''

------------------------------------------------------------
import abc
# 指定metaclass属性将类设置为抽象类,抽象类本身只是用来约束子类的,不能被实例化
class Animal(metaclass=abc.ABCMeta):
    @abc.abstractmethod  # 该装饰器限制子类必须定义有一个名为talk的方法
    def talk(self):  # 抽象方法中无需实现具体的功能
        pass

class Cat(Animal):  # 但凡继承Animal的子类都必须遵循Animal规定的标准
    def talk(self):
        pass

cat = Cat()  # 若子类中没有一个名为talk的方法则会抛出异常TypeError,无法实例化



面向对象--------------反射reflect

利用字符串操作对象的数据和方法

利用字符串操作对象的数据和方法(前两个是最最重要的!!!!!!)

有了反射才能真正的让面向对象的代码和用户交互起来!!!

1.hasattr(obj,'name')
	判断对象是否含有某个字符串对应的属性名或方法名
注意:该方法是自动将字符串形式的名字想办法转化成变量名,然后去判断对象的名称空间,以及产生对象的类里面,和产生对象的类的父类里面,有没有该名字可以被调用!!!一条龙操作全做了。由就返回一个True,没有就返回一个False
2.getattr(obj,'name')     # obj后面的参数必须是字符串!!!
	根据字符串获取对象对应的属性名的值或方法名的值就是函数名(函数体代码绑定的内存地址)
注意:如果名字不存在是会报错的!!!
-------------------------------------------------
3.setattr(obj,'name','value')
	根据字符串给对象设置或者修改数据
4.delattr()
	根据字符串删除对象里面的名字
--------------------------------------------------
class C1:
     school_name = '小姐姐学院'

     def choice_course(self):
         print('大宝贝们正在洗澡')

obj = C1()
-------------------------------------------------
需求:'''判断对象是否可以使用某个名字'''也就是判断这个名字存不存在
# 推导思路1
try:
     obj.xxx
except AttributeError:
     print('你木有这个名字')
-------------------------------------------------
需求:'''判断用户随意输入的名字,对象是否可以使用'''也就是判断用户随意输入的名字存不存在
# 推导思路2
target_name = input('请输入对象可能使用的名字>>>:').strip()
try:
     obj.target_name         # 此处target_name已经变成了字符串了
 except AttributeError:
     print('你木有这个名字')
注意这样是完全不行的!!!obj点的是字符串了,而不是变量名了!!!,所以该语法不能实现需求
----------------------------------------------------
"""
字符串的名字跟变量名区别很大
'school_name'
 school_name
意思完全不一样
"""
-----------------------------------------------------
# 和用户交互,用户输入进来的都是字符串类型的数据
# 反射:利用字符串操作对象的数据和方法
class C1:
    school_name = '小姐姐学院'

    def choice_course(self):
        print('大宝贝们正在洗澡')

obj = C1()  # 先生成对象
while True:
    target_name = input('请输入您想要操作的名字>>>:')
    if hasattr(obj, target_name):      # 利用反射做判断
        print('恭喜您 系统中有该名字')
        data_or_func = getattr(obj, target_name)  # 获取字符串对应的值,可能是数据值,也可能是函数体代码
        if callable(data_or_func):  # 判断名字是否可以被加括号调用
            print('您本次使用的是系统中的某个方法')
            data_or_func()      # 可以就直接加括号调用
        else:
            print('您本次使用的是系统中的某个数据')
            print(data_or_func)   # 不可以调用,就是数据值,就直接打印数据值
    else:
        print('很抱歉 系统中没有该名字')
------------------------------------------------
print(hasattr(obj, 'school_name'))      # True
print(getattr(obj, 'school_name'))      # 小姐姐学院
print(getattr(obj, 'choice_course'))    # <bound method C1.choice_course of <__main__.C1 object at 0x00000248C0B65A30>>
-------------------------------------------------
class C1:
    school_name = '小姐姐学院'

    def choice_course(self):
        print('大宝贝们正在洗澡')

obj = C1()  # 先生成对象
print(obj.__dict__)  # 结果是个空字典{}
setattr(obj,'name','jason')  # 等价于obj.name = 'jason' 结果是{'name':'jason'}
print(obj.__dict__)
setattr(obj,'name','jason123')  # 名字存在就修改了,结果是{'name':'jason123'}
print(obj.__dict__)

delattr(obj,'name')   # 把对象名称空间里面的name删掉了 结果又是个空字典{}了

这个地方'jason'就是一个值,可以用数型,列表等代替,可以不用加引号。主要如果让用户动态输入,拿我们拿到的只能是转成字符串形式的数据了。
这样做有个好处:用这种方式后,将来就可以写成动态的了,属性名与属性值都可以让用户自己去输入

注意:对象是没有资格删类里面的名字的!!!

.
.
.
.

反射实战案例

1.实战案例: 模拟简单的cmd终端

class WinCmd:
    def tasklist(self):
        print("""
            1.学习编程
            2.学习python
            3.学习英语
            """)

def ipconfig(self):
    print("""
            地址:127.0.0.1
            地址:上海浦东新区
            """)

def get(self, target_file):
    print('获取指定文件', target_file)

def put(self, target_file):
    print('上传指定文件', target_file)

def server_run(self):
    print('欢迎进入简易版本cmd终端')
    while True:
        target_cmd = input('请输入您的指令>>>:')
        res = target_cmd.split(' ')  # cmd命令里面有些命令需要中间有空格操作,先按空格切割一下
        if len(res) == 1:  # 说明输入的命令里面就一个参数没有空格
            if hasattr(self, res[0]):  # self就是当前的对象obj,换种写法
                getattr(self, res[0])()  # hasatter判断有用户输入的方法,用getattr去调用执行
            else:
                print(f'{res[0]}不是内部或者外部命令')
        elif len(res) == 2:   # 说明输入的命令里面有两个参数
            if hasattr(self, res[0]):
                getattr(self, res[0])(res[1])
            else:
                print(f'{res[0]}不是内部或者外部命令')
# getattr(self, res[0]) 相当于拿到了函数名绑定的函数体代码的内存地址,(res[1])就是函数名加括号运行前面的函数,并将res[1]最为参数给传到了函数里面了。
# 我们在cmd里面输命令的时候,如果命令中间有一个空隔,实际上第一个是命令就是对应的函数数名,第二个是要传到函数里面的参数!!!
obj = WinCmd()
obj.server_run()

这样一个简单的cmd终端的模拟就好了,运行代码,手动输入命令,就可以在去执行类class WinCmd里面有的函数了
------------------------------------------------------------------

image
.
image
.
image
.
image
.
image
.
.
.

2.实战案例: 利用反射保留某个py文件中所有的大写变量名及对应的数据值

比如通过代码将settings文件里面的小写的变量去掉。
python种一切皆对象,这个settings文件也是一个对象!!!
image

import settings
print(dir(settings))      # dir()列举对象可以使用的名字

useful_dict = {}  # 定义一个空字典
for i in dir(settings):    # 对这些名字组成的列表进行for循环
    if i.isupper():        # 比较如果名字是大写
        useful_dict[name] = getattr(settings, i)
# 将名字最为键,利用反射拿到名字对应的值作为值,添加到空字典里面去!!!
print(useful_dict)   # 这样空字典里面就只剩下了,settings文件里面纯大写的变量名和对应的值了,小写的就被过滤掉了!!!
# 以后在使用框架功能时,里面的配置文件就会这么写

.

需求:判断某个模块文件里面有没有我想要的名字
while True:
     target_name = input('请输入某个名字')
     if hasattr(settings, target_name):
         print(getattr(settings, target_name))
     else:
         print('该模块文件中没有该名字')

image
.
image
.
.

反射的总结:将来可能所有的代码都在一个类里面,然后我们用类产生一个对象,让用户去输入一些命令,然后根据命令去自动调类里面的函数去运行。

.
.

作业

1.整理今日内容及博客
	一定要思考总结面向对象各个知识点的思想
2.尝试利用反射编写一个简易版本的用户赠删改查功能
3.预习明日内容
posted @   tengyifan  阅读(28)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
点击右上角即可分享
微信分享提示