第七周学习笔记总结

周总结

面向对象编程思想

区别 相当于
面向对象 核心就是'对象'二字 ,对象其实就是一个'容器'将数据与功能整合到一起 相当于让你创造出一些事物之后不用你管
面向过程 将程序的执行流程化 ,分步操作,分步的过程中解决问题 相当于让你给出一个问题的具体解决方案

共同点: 面向过程和面向对象都是解决实际问题的一种思维方式

二者相辅相成,并不是对立的

解决复杂问题,通过面向对象方式便于我们从宏观上把握事物之间复杂的关系,

方便我们分析整个系统:具体到微观操作,仍然使用面向过程方式来处理

类与对象

类与对象的概念

对象:数据与功能的结合体

类:多个对象相同的数据和功能的结合体

ps:在面向对象编程中 类仅仅是用于节省代码 对象才是核心

类与对象的创建

语法结构
class 类名:
    类体代码
1.class是定义类的关键字
2.类名类似于函数名 但是首字母推荐大写 用于区分
3.类体代码就是存放对象公共数据和功能的地方
    数据: 变量名 = 变量值
    功能: 函数

在代码编程中是先有类才能有对象,所有先定义类,后产生对象

产生对象:类名加括号

对象的实例化

给对象添加自己独有的属性

类中的__ init__方法会在类产生对象的时候自动执行,用来初始化对象的属性

类产生对象的具体步骤
          1.先创建一个没有独有数据的空对象  {}
          2.将空对象和类括号内传入的数据一并交给__init__执行
                   __init__的第一个参数就是对象本身
          3.将创建好的对象自动返回

ps:针对括号内第一个形参self其实就是一个普通的变量名而已,只不过该变量名将来专门接收对象的,所以给它起了个固定的名字叫self。

对象独有的功能

在类中定义的函数默认都是绑定给对象使用的。即对象来调,会自动将对象当做第一个参数传入。

class Person:
    h_type = '人类'
    def __init__(self, name):  # 让对象拥有独有的数据
         self.name = name
    # 定义在类中的函数,我们称之为方法
    def eat(self):
        print('%s正在干饭' % self.name)
    def others(self, a, b)
        print('from others')
   
'''
针对对象独有的方法  我们无法真正实现
1.如果在全局则不是独有的
2.如果在类中则是公共的
python解释器针对上述问题添加了一个非常牛的特性
定义在类中的函数默认是绑定给对象的(相当于是对象独有的方法)
'''

动态与静态方法

类体代码中编写的函数 对象调用 类调用
绑定给对象的方法 对象调用会自动将对象当做第一个参数传入 类调用则有几个形参就传几个实参
绑定给类的方法(@classmethod) 对象调用会自动将产生该对象的类当做第一个参数传入 类调用会自动将类当做第一个参数传入
静态方法(普普通通的函数)(@staticmethod) 按照普普通通的函数传参方式 按照普普通通的函数传参方式

面向对象三大特性之继承

继承 在编程世界 在现实生活
含义 继承其实就是用来描述类与类之间数据的关系 继承其实就是用来描述人与人之间资源的关系
目的 继承就是为了节省代码编写 继承就是想占有别人的财产
例子 类A继承类B(拥有了类B里面所有的数据和功能),可以继承一个类 ,也可以继承多个类 儿子继承父亲的财产(拥有了父亲所有的资源)

继承的基本使用

class 类名(需要继承的类名):
    pass
1.定义类的时候在类名后加括号
2.括号内填写你需要继承的类名
3.括号内可以填写多个父类
逗号隔开即可
"""
我们将被继承的类称之为: 父类或基类或超类
我们将继承类的类称之为: 子类或派生类
ps:平时最常用的就是父类和子类
"""

继承的本质

先抽象再继承

抽象:由下往上抽取相同特征,抽取类似或者说比较像的部分,是一种归类的方法

继承:由上往下直接白嫖资源,子类获取父类的数据和功能

对象:数据和功能的结合体

类:多个对象相同的数据和功能的结合体

父类:多个类相同的数据和功能的结合体

ps:类和父类最主要的功能其实就是节省代码

名字的查找顺序

不继承的情况

名字的查找顺序是:先从对象自己的名称空间里查找,没有的话就去类的名称空间查找,对面>>>类。

单继承的情况

名字的查找顺序是:先从对象自己的名称空间中查找,没有则取产生对象的类中查找,如果还没有并且类有父类则去父类中查找,重复,对象>>>类>>> 父类。

多继承的情况

  • 非菱形继承的情况下(最后不会归总到一个我们自定义类上):

父类中名字的查找顺序就是按照继承时从左往右依次查找,如果多个父类还有分类,那么遵循"深度优先"(每个分支都走到底 再切换)。

image

  • 菱形继承的情况下(最后归总到一个我们自定义类上):

父类中名字的查找顺序就是按照继承时从左往右依次查找,如果多个父类还有分类,那么遵循"广度优先"(前面几个分支都不会走最后一个类 最后一个分支才会走)。

image

ps:也可以使用类点mro()方法查看该类产生的对象名字的查找顺序

名字的查找顺序永远都是,先从当前对象自身开始查找。

经典类与新式类

经典类
	不继承object或其子类的类(什么都不继承)
新式类
	继承了object或其子类的类
"""
在python3中所有的类默认都会继承object
	也就意味着python3里面只有新式类
在python2中有经典类和新式类
	由于经典类没有核心的功能 所以到了python3直接砍掉了
	
以后我们在定义类的时候  如果没有想要继承的父类 一般推荐以下写法
    class MyClass(object):
        pass
目的是为了兼容python2
"""
以后写代码针对object无需关心 知道它的存在即可

派生方法

如果我们自己写的子类需要使用父类的方法,并且还需要基于父类方法之上做扩展。这样的子类我们称之为派生类(本质还是子类),可以使用super关键字来实现。

子类调用父类的方法 super().父类的方法()
	拦截 添加 原路返回

派生方法的实际应用

当前需要序列化一个字典,如下代码:

d1 = {'t1': datetime.datetime.today(), 't2': datetime.date.today()}
res = json.dumps(d1)
print(res)

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

解决方法:

方法1 - 手动将不能序列化的类型先转字符串

d1={'t1': str(datetime.datetime.today()), 't2': str(datetime.date.today())}
res = json.dumps(d1)
print(res)  # {"t1": "2022-04-08 20:45:06.122407", "t2": "2022-04-08"}

方法2 - 研究json源码并重写序列化方法

'''
研究源码发现报错的方法叫default
        raise TypeError("Object of type '%s' is not JSON serializable" % o.__class__.__name__)
    我们可以写一个类继承JSONEncoder然后重写default方法
'''
class MyJsonEncoder(json.JSONEncoder):
    def default(self, o):
        # 形参o就是即将要被序列化的数据对象
        print('重写了', o)
        '''将o处理成json能够序列化的类型即可'''
        if isinstance(o, datetime.datetime):
            return o.strftime('%Y-%m-%d %X')
        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=MyJsonEncoder)
print(res)  # {"t1": "2022-04-08 20:51:41", "t2": "2022-04-08"}

面向对象三大特性之封装

封装其实就是将数据或者功能隐藏起来(包起来 装起来)

隐藏的目的不是让用户无法使用 而是给这些隐藏的数据开设特定的接口,让用户使用接口才可以去使用 我们在接口中添加一些额外的操作

  • 1.在类定义阶段使用双下划线开头的名字 都是隐藏的属性
    后续类和对象都无法直接获取

  • 2.在python中不会真正的限制任何代码
    隐藏的属性如果真的需要访问 也可以 只不过需要做变形处理
    __ 变量名 _ 类名 __变量名

ps:既然隐藏了 就不该使用变形之后的名字去访问 这样就失去了隐藏的意义

property伪装属性

property就是将方法伪装成数据。

很多时候有些数据需要通过计算才能获取,但这些数据给我们的感觉应该是属于数据而不是功能,这个时候我们就可以用到property来把功能伪装成数据。

property伪装方法
"""
@classmethod
@staticmethod
@property
"""

面向对象三大特性之多态

多态的含义

什么是多态?多态指的是一种食物的多种形态,像水有固态,也太和气态等。

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

强制多态性

虽然python推崇的是自由,但是也提供了强制性的措施来实现多态性,使用了abc模块,不推荐使用性多态。

衍生多态性

其实我们完全可以不依赖于继承,只需要制造出外观和行为相同对象,同样可以实现不考虑对象类型而使用对象,这正是Python崇尚的“鸭子类型”(duck typing):“如果看起来像、叫声像而且走起路来像鸭子,那么它就是鸭子”。

面向对象之反射

反射的含义

指程序可以访问、检测和修改本身状态或者行为的一种能力。其实就是通过字符串来操作对象的数据和功能。

反射需要掌握的四个方法

主要方法 作用
hasattr() 判断对象是否含有字符串对应的数据或者功能
getattr() 根据字符串获取对应的变量名或者函数名
setattr() 根据字符串给对象设置键值对(名称空间中的名字)
delattr() 根据字符串删除对象对应的键值对(名称空间中的名字)

反射实战应用

反射其实就可以让用户和代码更好的交互起来

1.加载配置文件纯大写的配置
	# 配置文件加载:获取配置文件中所有大写的配置 小写的直接忽略组织成字典
    import settings
    new_dict = {}
    # print(dir(settings))  # dir获取括号中对象可以调用的名字
    # ['AGE', 'INFO', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'desc', 'name']
    for i in dir(settings):
        if i.isupper():  # 如果名字是纯大写 那么获取该大写名字对应的值   'AGE'   'INFO'
            v = getattr(settings, i)
            new_dict[i] = v
    print(new_dict)
2.模拟操作系统cmd终端执行用户命令
	class WinCmd(object):
        def dir(self):
            print('dir获取当前目录下所有的文件名称')

        def ls(self):
            print('ls获取当前路径下所有的文件名称')

        def ipconfig(self):
            print('ipconfig获取当前计算机的网卡信息')
    obj = WinCmd()
    while True:
        cmd = input('请输入您的命令>>>:')
        if hasattr(obj, cmd):   # 根据用户输入的cmd,判断对象obj有无对应的方法属性
            cmd_name = getattr(obj, cmd)  # 根据字符串cmd,获取对象obj对应的方法属性
            cmd_name()
        else:
            print('%s 不是内部或外部命令,也不是可运行的程序或批处理文件' % cmd)

面向对象之魔法方法(双下方法)

魔法方法其实就是类中定义的双下方法,之所以会叫魔法方法原因是这些方法都是到达某个条件自动触发 无需调用

魔法方法 触发条件
__ init__ 实例化对象的时候
__ str__ 对象被执行打印操作的时候
__ call__ 对象加括号调用的时候
__ getattr__ 对象获取一个不存在的属性名的时候
__ setattr__ 对象操作属性值的时候
__ del__ 对象在被删除(主动 被动)的时候
__ getattribute__ 对象获取属性的时候自动触发,无论这个属性存不存在
__ enter__ 对象被with语法执行的时候
__ exit__ 对象被with语法执行并运行完with子代码的时候

元类

元类简介

所有的对象都是实例化或者说是通过调用类而得到的,python中一切皆对象,通过class关键字定义的类本质也是对象,对象又是通过调用类得到的,因此通过class关键字定义的类肯定也是调用了一个类得到的,这个类就是元类。

产生类的两种表现形式

  1. class关键字
class C1(object):
    pass
print(C1)  # <class '__main__.C1'>

2.type元类

type(类名,父类,类的名称空间)
  	res = type('C1', (), {})
print(res)  # <class '__main__.C1'>

元类进阶操作

双下call方法,对象加括号会自动执行产生该对象的类里面的双下call方法,并且该方法返回什么对象加括号就会得到什么。

推导:类加括号会执行元类的里面的双下call该方法返回什么其实类加括号就会得到什么。

'''类里面的__init__方法和元类里面的__call__方法执行的先后顺序'''
class MyTypeClass(type):
    def __call__(self, *args, **kwargs):
        print('__call__ run')
        super().__call__(*args, **kwargs)

"""如果想要切换产生类的元类不能使用继承 必须使用关键字metaclass声明"""

class MyClass(metaclass=MyTypeClass):
      def __init__(self, name):
          print('__init__ run')
          self.name = name
obj = MyClass('jason')

'''
是先执行元类里面的__call__方法,再执行类里面的__init__方法。
'''

定制对象的产生过程

"""强制规定:类在实例化产生对象的时候,对象的独有数据必须采用关键字参数"""
class MyTypeClass(type):
    def __call__(self, *args, **kwargs):
        # 1.会产生一个空对象
        # 2.调用类里面的__init__实例化
        # print('__call__ run')
        print(args,kwargs)
        if args:
            raise Exception('必须全部采用关键字参数')
        super().__call__(*args, **kwargs)

class MyClass(metaclass=MyTypeClass):
    def __init__(self, name):
        # print('__init__ run')
        self.name = name       
        
# obj1 = MyClass('jason')
obj2 = MyClass(name='jason')


总结:
1.如果你想高度定制类的产生过程,那么编写元类里面的__init__方法
2.如果你想高度定制对象的产生过程,那么编写元类里面的__call__方法

双下new方法

双下new是用于产生空对象用的,它会返回一个空对象。双下init和双下new执行都会产生一个空对象,这个空对象就是由双下new产生的

"""
类产生对象的步骤
	1.产生一个空对象
	2.自动触发__init__方法实例化对象
	3.返回实例化好的对象
"""
__new__方法专门用于产生空对象	   骨架
__init__方法专门用于给对象添加属性	   血肉
posted @   空白o  阅读(28)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示