python进阶篇-面向对象

1.对象的定义

1.1 什么是对象

面向过程:将程序流程化

对象:就是“容器“,是用来存储数据和功能的,是数据和功能的集合体

面向对象和面向过程没有优劣之分,它们只是使用的场景不同罢了。

1.2 为什么要有对象

有了对象之后,数据和功能之间有关系的会被放在同一个”容器里面“,它的解耦合程度会非常高,方便阅读和维护。

1.3 怎么造对象

如果一个模块里面存放了数据和功能,那么这个模块就是容器,可以被称之为对象。对象的本质就是存放数据和功能的”容器“。我们只要能做到这一点,我们就是在使用面向对象的思想在写程序。但是使用文件整合的话,文件就太多了。

2. 类

2.1 类的概念

类的一方面是将不同的对象做区分,归类的

为了解决不同的对象存储相同数据的问题。所以不仅仅对象是”容器“,类也是”容器“。类用来存放同类对象的他们共有的数据和功能的。所以类也是对象。

将同类对象共有的数据提取到类里面,不是说私有的数据变为共有的了。本质上还是它们自己的。这样做的目的,仅仅是为了归类和节省空间。

在查找数据时,先找对象本身的数据,没有才会到类里面去找。

88d4da0090f347e6875c47dc20ff3df5.png

2.2 类怎么定义

类名采用驼峰体命名法,首字母大写

类的子代码在定义阶段就会直接运行。所以类的名称空间在定义阶段就会产生

aa5467c33c404ec1a2befc7634dff409.png

2.3 类的属性访问

Hero.__dict__会得到一个字典,这个字典里面存放的就是类名称空间里面的名字

9c229bf6f83e484bb857b07f32c7ea32.png

可以使用字典取值的方式,来获取类的属性和方法 

cc13e90866664206b1ffd351e51f71d1.png

也可以通过类名.属性名的方式来对类的属性进行访问,但是本质上就是通过字典取值。这样只不过是python做的优化罢了。 

3.对象

3.1 对象的产生

类造出来之后,他本质上是为了将所有的同类对象提取出来。类里面存放的东西只是为了减少计算机资源的浪费,本质上还是属于具体对象的。所以将数据和功能提取到类里面之后还没有结束,还是需要建立对象和类之间的关联。让类顺着这个关联,还可以访问到原来属于它那部分的数据和功能。

只要基于类加括号就会创造一个对象,并且会将类和对象之间建好关联。调好之后就会返回值就是一个英雄的对象。

类加括号的调用,并不会触发类子代码的运行。类的子代码在定义阶段就已经执行过了。调用的时候只会帮我们造好一个对象,然后建立好类与对象之间的关联,并不会执行类的子代码。调用一次就会帮我们创建一个对象。

对象里面也存在一个__dict__,里面存放的就是对象的属性。现在三个字典里面都是空的。因为我们只是基于类创建了对象,并没有往对象里面添加内容。但是并不意味着这些对象里面没有任何属性,因为类里面有。

e634a5deb73f447d98f2022903cab7a0.png

3ff7107f80a8496884b74e9fbed2c5b7.png

3.2 对象属性的添加

 通过对象.属性的方式来增加,修改,访问属性,本质上都是通过字典的方式来操作属性的

hero1_obj.name = '鲁班七号'
hero1_obj.speed = 450
hero1_obj.hp = 3000
hero1_obj.atk = 100
print(hero1_obj.__dict__)
print(hero1_obj.hero_work)

hero2_obj.name = '后羿'
hero2_obj.speed = 460
hero2_obj.hp = 3100
hero2_obj.atk = 110
print(hero2_obj.__dict__)
print(hero2_obj.hero_work)

hero3_obj.name = '虞姬'
hero3_obj.speed = 470
hero3_obj.hp = 3200
hero3_obj.atk = 120
print(hero3_obj.__dict__)
print(hero3_obj.hero_work)

97a4382c836b44fa94ba19c216061bf7.png

问题:这样就出现了一个问题,给对象添加属性的过程都是一样的并且重复的,太过烦琐了。

3.3 __init__方法

只要我们在类里面定义了__init__方法,这个方法就会在类被调用的时候,自动执行

python在调用类创建对象的时候,会自动调用类下面的__init__,并且将创建的对象自动传递进来(现在这个对象还是空的,没有任何自己独有的属性)

调用类的过程

  • 创建空对象
  • 调用__init__方法,同时将空对象,以及调用类时候括号里面传递的参数,一同传递给__init__方法。
  • 返回初始化之后的对象(注意这个对象并不是__init__返回的,返回对象是类的底层做的一些事情)
class Hero:
    hero_work = '射手'
    count = 0

    def __init__(self, name, speed, hp, atk):
        self.name = name
        self.speed = speed
        self.hp = hp
        self.atk = atk
        self.equipment = []
        Hero.count += 1

    def get_hero_info(self):
        print(f'英雄属性:名字:{self.name} 移速:{self.speed} '
              f'生命值:{self.hp} 攻击力:{self.atk}')

    def set_hero_speed(self, speed_plus):
        self.speed += speed_plus

    def buy_equipment(self, e_name):
        self.equipment.append(e_name)


# 实例化
hero1_obj = Hero('鲁班七号', 450, 3000, 100)  # Hero.__init__(空对象,)
hero2_obj = Hero('后羿', 460, 3100, 110)
hero3_obj = Hero('虞姬', 470, 3200, 120)

3.4 属性查找顺序

我们现在调用类产生对象,就会产生对象自己的名称空间,里面放的也是对象的独有属性.

类里面放的是对象的共有属性

对象查找属性的时候,一定是先在自己的名称空间里面找,里面没有,才回到它所属类的名称空间里面找.

# _*_ coding utf-8 _*_
# george
# time: 2024/9/19下午5:28
# name: attribution.py
# comment:
class Hero:
    hero_work = '射手'
    count = 0

    def __init__(self, name, speed, hp, atk):
        self.name = name
        self.speed = speed
        self.hp = hp
        self.atk = atk
        self.equipment = []
        Hero.count += 1

    def get_hero_info(self):
        print(f'英雄属性:名字:{self.name} 移速:{self.speed} '
              f'生命值:{self.hp} 攻击力:{self.atk}')

    def set_hero_speed(self, speed_plus):
        self.speed += speed_plus

    def buy_equipment(self, e_name):
        self.equipment.append(e_name)


# 实例化
hero1_obj = Hero('鲁班七号', 450, 3000, 100)  # Hero.__init__(空对象,)
hero2_obj = Hero('后羿', 460, 3100, 110)
hero3_obj = Hero('虞姬', 470, 3200, 120)

hero3_obj.hero_work = "法师"

print(Hero.hero_work)
print(hero1_obj.hero_work)
print(hero2_obj.hero_work)
print(hero3_obj.hero_work)

8bb1a4771f584764906954ff5f81c5de.png

3.5 数据属性特点

我们只要修改了类里面的数据属性,通过这个类实例化的所有对象,都是可以感知到这个变化的.

需求:每次实例化一个对象,count就+1

通过Hero.count来修改类的属性之后,所有的对象都可以感知此变化

# _*_ coding utf-8 _*_
# george
# time: 2024/9/19下午5:28
# name: attribution.py
# comment:
class Hero:
    hero_work = '射手'
    count = 0

    def __init__(self, name, speed, hp, atk):
        self.name = name
        self.speed = speed
        self.hp = hp
        self.atk = atk
        self.equipment = []
        Hero.count += 1

    def get_hero_info(self):
        print(f'英雄属性:名字:{self.name} 移速:{self.speed} '
              f'生命值:{self.hp} 攻击力:{self.atk}')

    def set_hero_speed(self, speed_plus):
        self.speed += speed_plus

    def buy_equipment(self, e_name):
        self.equipment.append(e_name)


# 实例化
hero1_obj = Hero('鲁班七号', 450, 3000, 100)  # Hero.__init__(空对象,)
hero2_obj = Hero('后羿', 460, 3100, 110)
hero3_obj = Hero('虞姬', 470, 3200, 120)

hero3_obj.hero_work = "法师"

print(Hero.hero_work)
print(hero1_obj.hero_work)
print(hero2_obj.hero_work)
print(hero3_obj.hero_work)

3.6 绑定方法

3.6.1 类的函数属性

类的数据属性是共享给对象使用的,只要类的数据属性发生变化,对象是能够立马感知到的

对于类的函数属性,类本身是可以使用的.通过类来调用,要严格按照函数的用法来,有几个参数传递几个参数.

be24a6ee902f468d89f3ef0590644812.png

3.6.2 对象调用类的函数属性 

类里面定义的函数,主要是给对象去用的,而且是绑定给对象使用的,虽然所有的对象都是指向相同的功能,但是绑定到不同的对象,就会变成不同的绑定方法

73fc4bcf113348b597745100766f92d9.png

类的函数属性绑定给对象使用之后,就不再是普通的函数,而是绑定方法.我们使用谁来调用绑定方法,绑定方法就会把水作为第一个参数传递进去.我们在实例化对象的时候,自动触发的也是对象的init绑定方法,所以也不需要传递对象本身. 

3.6.3  类函数的形参个数

正是因为有绑定方法的存在,所以我们在类里面定义函数的时候,就至少需要一个形参.即便你的函数里面不需要任何参数.因为我们在通过对象调用这个函数的时候,绑定方法会将对象传递进来,所以需要一个形参来接收绑定方法传进来的对象.

3.6.4 类函数的self

从规范上面来说,第一个参数我们应该写成self,这个self没有任何特殊的地方,仅仅是一个变量名罢了

3.6.5 快捷键

快捷键:alt+j,按一次就会选中一个相同变量名

1f7181f56a6c45b6bacca16a9686cca3.png

3.6.6 绑定方法的方便之处

这就是绑定方法厉害的地方,谁调用的绑定方法处理的就是谁的数据.

# _*_ coding utf-8 _*_
# george
# time: 2024/9/19下午5:28
# name: attribution.py
# comment:
class Hero:
    hero_work = '射手'
    count = 0

    def __init__(self, name, speed, hp, atk):
        self.name = name
        self.speed = speed
        self.hp = hp
        self.atk = atk
        self.equipment = []
        Hero.count += 1

    def get_hero_info(self):
        print(f'英雄属性:名字:{self.name} 移速:{self.speed} '
              f'生命值:{self.hp} 攻击力:{self.atk}')

    def set_hero_speed(self, speed_plus):
        self.speed += speed_plus

    def buy_equipment(self,e_mp):
        self.equipment.append(e_mp)


# 实例化
hero1_obj = Hero('鲁班七号', 450, 3000, 100)  # Hero.__init__(空对象,)
hero2_obj = Hero('后羿', 460, 3100, 110)
hero3_obj = Hero('虞姬', 470, 3200, 120)

hero1_obj.buy_equipment("末世")
hero2_obj.buy_equipment("大棒")
hero3_obj.buy_equipment("复活甲")
print(hero1_obj.equipment)
print(hero2_obj.equipment)
print(hero3_obj.equipment)

f9d592bc5a274005ae27ae9d0166075a.png

3.6.7 python一切皆对象

基本数据类型也是类

我们定义好一个字符串,一个列表,字典的时候,其实就是在造对象.我们通过造出来的对象.他们的方法,其实就是对象在调用它们的绑定方法

de141c240e1043bdae209374a22317a2.png

都是存在self的 

bc8945dfbfa0483a9f2bc74902a966a1.png

x= "aaa",其实背后是触发了一个功能叫做x = str("aaa"),这其实就是类的实例化的过程,x就是通过str实例化出来的一个对象.只不过是python给我们优化了基本数据类型的实例化过程.直接使用"",就可以造出字符串对象. 

a0f36845d31d41faac2c2bb089999102.png

4.面向对象三大属性

面向对象的三大属性,封装,继承和多态

4.1 封装

封装是面向对象最核心的一个特性,封装就是将一堆东西放到容器里面,然后封起来,也就是前面一直在说的整合 

4.1.1 隐藏属性

特点:

  • 隐藏的本质,只是一种改名操作
  • 对外不对内
  • 这个改名操作,只会在类的定义阶段检查类的子代码的时候执行一次,之后定义的__开头的属性都不会改名

我们封装的时候可以将属性隐藏起来,让使用者没有办法直接使用,而不是让使用者不能使用。

我们无论是隐藏数据属性还是函数属性,直接在变量名前面加__就可以实现。

class Test:
    __x = 10

    def __f1(self):
        print("f1")
print(Test.x)

dad7011500d14cb7be1f90c1176b088d.png

我们只要在属性前面加上__,在类的定义阶段,__开头的属性名字就会被加上前缀_ 

d682eb67d8354a3db6dd6b6eac539a75.png 所以__x =》_Test__x,__f1=>_Test_f1.所以pyhton的这种隐藏机制,并没有做到真正意义上的隐藏,其实就是一种改名操作,我们其实只要知道了类名和属性名,就可以拼接出变形之后的名字,然后对她进行访问

e5e73478b10e465dbb597c26a537a1db.png

对外不对内的含义就是,我们在类的外部通过__x是访问不到隐藏属性的,但是在类的内部通过__x和__f1是可以访问到的。

class Test:
    __x = 10

    def __f1(self):
        print("f1")

    def f2(self):
        print(self.__x)
        print(self.__f1)


obj = Test()
obj.f2()

8e1a704a94f54eae93d50d72f3e2d78b.png

为什么在类的外部不可以访问隐藏属性,在类的内部却是可以访问的呢? 

在类的定义阶段,检查类子代码语法的时候,对类内部__开头的名字统一进行了改名操作。在f2里面的__x也被改名了。这也是类内内部可以被访问的原因,虽然看起来还是__x,但是其实已经被改名字了。

但是改名字,只是在类的定义阶段,检查子代码语法的时候发生的。我们在类的外面,无论是通过类来调用还是通过对象来调用。这都是类的调用阶段。类的调用阶段,类的子代码早就运行完毕了。该改的名字也早就修改完毕了。所以还是通过__x肯定是访问不到的。这也是在类的外部隐藏属性不能被访问的原因。

这个改名操作,只会在类的定义阶段检查类的子代码的时候执行一次,之后定义的__开头的属性都不会改名

我们要在设计的时候考虑什么属性不能直接对外界访问,而不是在使用的时候做这件事情。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2024/9/20 5:49
# @Author : George
class Test:
    __x = 10

    def __f1(self):
        print("f1")

    def f2(self):
        print(self.__x)
        print(self.__f1)

Test.__y = 20
print(Test.__dict__)

6568ba496d3d43cdb48d877c96a6272e.png

基于对象的属性隐藏 

class Test:
    def __init__(self, name, age):
        self.__name = name
        self.__age = age

    def f1(self):
        print(self.__name, self.__age)


test1 = Test('lisi', 30)
print(test1.__dict__)
test1.f1()

8de6845abedb40c68ddf177838eb5b22.png

4.1.2  为何隐藏属性

属性分为数据属性和函数属性

  • 隐藏数据属性

类的设计者将属性设计出来之后,就是要给使用者用的,只是不想要使用者直接使用罢了。那就需要给这个属性开两个接口,让使用者通过接口来使用这两个属性。

我们将属性隐藏起来之后,使用者就没办法随便修改这个属性了。使用者想改的话,必须通过我设计的接口来进行修改。

隐藏数据属性,并不是不让外界使用。而是利用隐藏属性,对外不对内的特点。让类的设计者能够严格控制,类的使用者对于这个属性的各种操作

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2024/9/20 6:19
# @Author : George
class Test:
    def __init__(self, name, age):
        self.__name = name
        self.__age = age

    def get_name(self):
        return self.__name

    def get_age(self):
        return self.__age

    def set_age(self, new_age):
        if type(new_age) is not int:
            print("你个傻子,年龄必须是整数")
            return
        if not 0 < new_age < 150:
            print("你个傻子,年龄必须在1-150岁之间")
            return
        self.__age = new_age


test1 = Test('lisi', 30)
age = test1.get_age()
print(age)
test1.set_age(100)
age = test1.get_age()
print(age)

5d62a78d774648f89e4cd2e24289b1b7.png

  • 隐藏函数属性

隐藏函数属性可以隔离复杂度,让使用者更加方便的使用设计者设计的类。

class Servant:

    def __run(self):
        print('跑起来')

    def __pay(self):
        print('给钱')

    def __take_bun(self):
        print('拿包子')

    def buy_bun(self):  # 买包子
        self.__run()
        self.__pay()
        self.__take_bun()
        self.__run()


ser = Servant()
ser.buy_bun()

2eb661758c49410d85d231083f438f8b.png

4.1.3 类装饰器property

隐藏属性的目的是为了开接口给别人使用,然后我们在接口之上附加逻辑.对于一个数据属性来说最多只有四种操作"增删改查",对于已经存在的属性而言只有"删改查"三种操作. 作为设计者已经开好接口,设计好了类.但是对于使用者就有难度了.

因为age听起来是一个数据属性.平时删改查age应该是,而不是调用函数接口来实现

obj = Test('lisi', 30)
del obj.age
obj.age = 100
print(obj.age)
obj = Test('lisi', 30)
age = obj.get_age()
obj.set_age(100)
obj.del_age()

如何实现让使用者用起来是数据属性,并且背后触发的还是我们设置的接口呢?

property:Pyhton内置装饰器(类实现的装饰器)

(装饰器的码以及调作用是在不修改被装饰对象源代用方式的前提下,给被装饰对象添加新功能的可调用对象),装饰器不一定非要是函数,只要是一个可调用对象就可以了.类可以加括号调用,所以说类也可以实现装饰器的功能.

property的作用:用来把绑定给对象的方法伪装成一个数据属性

class Test:
    def __init__(self, name, age):
        self.__name = name
        self.__age = age

    def get_name(self):
        return self.__name

    @property  # get_age = property(get_age)
    def get_age(self):
        return self.__age

    def set_age(self, new_age):
        if type(new_age) is not int:
            print("你个傻子,年龄必须是整数")
            return
        if not 0 < new_age < 150:
            print("你个傻子,年龄必须在1-150岁之间")
            return
        self.__age = new_age

    def del_age(self):
        del self.__age


obj = Test('lisi', 30)
print(obj.get_age)

6b6c3877c5d848168f46372a4c16f45d.png

装饰器如此使用还是太麻烦了,所以还是得使用语法糖 

class Test:
    def __init__(self, name, age):
        self.__name = name
        self.__age = age

    def get_name(self):
        return self.__name

    # @property  # get_age = property(get_age)
    def get_age(self):
        return self.__age

    def set_age(self, new_age):
        if type(new_age) is not int:
            print("你个傻子,年龄必须是整数")
            return
        if not 0 < new_age < 150:
            print("你个傻子,年龄必须在1-150岁之间")
            return
        self.__age = new_age

    def del_age(self):
        del self.__age

    age = property(get_age,set_age,del_age)

obj = Test('lisi', 30)
print(obj.age)
obj.age = 40
print(obj.age)

 使用语法糖

class Test:
    def __init__(self, name, age):
        self.__name = name
        self.__age = age

    def get_name(self):
        return self.__name

    # @property  # get_age = property(get_age)
    @property
    def age(self):
        return self.__age

    @age.setter
    def age(self, new_age):
        if type(new_age) is not int:
            print("你个傻子,年龄必须是整数")
            return
        if not 0 < new_age < 150:
            print("你个傻子,年龄必须在1-150岁之间")
            return
        self.__age = new_age

    @age.deleter
    def age(self):
        del self.__age

    # age = property(get_age,set_age,del_age)

obj = Test('lisi', 30)
print(obj.age)
obj.age = 100
print(obj.age)

cb7941db04254bd08b326a2135c6d835.png

 4.2 继承

4.2.1 继承介绍

  • 继承是一种创建新类的方式,通过继承创建的类被称之为子类,被继承的类被称为父类(基类)
  • python支持多继承
  • 查看一个类的父类是谁:类.__bases__ 
class Parent1(object):
    x = 10
    pass


class Parent2(object):
    pass


class Child1(Parent1):  # 单继承
    pass


class Child2(Parent1, Parent2):  # 多继承
    pass

print(Child1.__bases__)
print(Child2.__bases__)

e2bc80800be6437bb89ae35b06bf5cb6.png

在python2里面存在新式类和经典类的区分。

新式类:继承了object类的子类(object类是python的内置类),以及继承了这个类的子子孙孙类。

经典类:没有继承了object类的子类(object类是python的内置类),以及继承了这个类的子子孙孙类。

所以pyhton2里面默认都是经典类,只有继承了object才会变为新式类。

958d3bf929914482839930fb0ec3b148.png

在python3里面默认继承的就是object,python3里面所有的类都是新式类。

cfc2458567554588b47d38114b3ea385.png

 如果想让自己的代码可以兼容python2的话,就可以让类继承object.这样我们的代码在python2中运行,它依然还是新式类。

4.2.2 继承的特性

继承的特性是遗传

子类会遗传父类的属性:子类继承父类之后,父类有的属性,子类都可以访问的到。儿子自己有就用自己的,儿子没有的就找老爸要。

继承就是用来解决代码冗余问题的:可以将多个类的共同的部分提取到一个父类里面,这样共同的属性,只是需要存储一份即可。

  • 类是为了解决对象有共同属性的问题。
  • 继承是为了解决类有共同属性的问题

object是python的内置类,类存储的是数据和功能,这些数据和功能都是python是认为比较有用的。python将其封装到了object类里面,让所有的新式类全部都来继承它。

在python里面所有的新式类,只要往上找,最后一定是继承了object,所以这些新式类,一定都有object提供的数据和功能。比如:__dict__

4.2.3 多继承的优缺点

优点:

  • 一个子类可以遗传多个父类的属性

缺点:

  • 多继承违背了人的思维习惯
  • 多继承会让代码的可读性变差

如果必须要使用多继承时,可以使用MixIns机制(它是一种编程规范,本质上还是多继承),使用它可以使用多继承的优点,也可以避免代码可读性变差的问题。MixIns才是pyhton多继承的正确打开方式。

4.2.4 继承的实现

抽象:就是抽取它们共同的部分

da9fba1723fc4623ad000d27946275cd.png

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2024/9/21 3:12
# @Author : George
class Human:
    star = 'earth'

    def __init__(self, name, age, gender):
        self.name = name
        self.age = age
        self.gender = gender


class Chinese(Human):
    # star = 'earth'
    nation = 'China'

    def speak_chinese(self):
        print(f'{self.name}在说普通话')


class American(Human):
    star = 'earthxxx'
    nation = 'America'

    def speak_english(self):
        print(f'{self.name}在说英语')


dy_obj = Chinese('董永', 18, '男')
print(dy_obj.__dict__)
print(dy_obj.nation)
print(dy_obj.star)
dy_obj.speak_chinese()

iron_man_obj = American('iron_man', 19, '男')
print(iron_man_obj.__dict__)
print(iron_man_obj.nation)
print(iron_man_obj.star)
iron_man_obj.speak_english()

 4.2.5 派生

  • 子类派生父类没有的属性
  • 子类存在和父类同名的属性,优先以子类自己的属性为主
  • 子类造一个新的功能,这个新的功能并不是完全覆盖父类的功能,而是在父类功能的基础上进行的扩展。

现在的中国人属性上面要添加一个账户余额,重写父类的__init__

1a69fd18136446f2aec567acfca15607.png

4.2.6 单继承属性查找

对象->类->父类->.....object,都找不到直接报错 

  • 通过对象访问属性的时候,会先在对象里面找,对象里面没有才会到对象的类里面找。对象的类也没有就去父类里面找
  • 对象在调用绑定方法的时候,哪个对象调用的绑定方法,就会将哪个对象传递进去。所以self.f1()的这个self是obj
class Test1:
    def f1(self):
        print('Test1.f1')

    def f2(self):
        print('Test1.f2')
        self.f1()


class Test2(Test1):
    def f1(self):
        print('Test2.f1')


obj = Test2()
obj.f2()

00775979352d45879b60f82b9029bed8.png

如果想要打印Test1.f2,Test1.f1.可以基于类的隐藏属性来实现 

class Test1:
    def __f1(self):  # _Test1__f1
        print('Test1.f1')

    def f2(self):
        print('Test1.f2')
        self.__f1()  # _Test1__f1


class Test2(Test1):
    def __f1(self):  # _Test2__f1
        print('Test2.f1')


obj = Test2()
obj.f2()

956a7829c2364e3f80227f239ba830dc.png

4.2.7 多继承属性查找

4.2.7.1 菱形问题(钻石问题)

210611c3b9f54576b9ee82bfbbdb9c2b.png

对象访问f1,先在对象里面找,对象里面没有在类D里面查找,类D里面也没有,在父类里面找。但是D的父类有两个(B,C) 。应该先找哪一个??

pyhton继承的实现原理:查找属性的时候,会根据一个MRO(Method Resolution Order,方法解析顺序)列表作为依据,它是通过C3算法来实现的。每定义一个类,python都会基于C3算法,算出一个MRO列表。MRO列表就是一个简单的所有基类的线性顺序列表

我们查找属性的顺序,就是基于这个MRO列表的顺序来查找的。我们通过对象查找属性,对象是没有MRO列表的,只有它的类才有。

class A:
    def f1(self):
        print('A.f1')


class B(A):
    def f1(self):
        print('B.f1')


class C(A):
    def f1(self):
        print('C.f1')


class D(B, C):
    def f2(self):
        print('D.f2')


print(D.mro())

obj = D()
obj.f1()

D的MRO列表

82905e03f2ea48a1b2e38d6ad215f2fa.png

4.2.7.2 非菱形查找 

  • 对于菱形继承来说,新式类和经典类的属性查找顺序是不一样的。 
  • 但是对于非菱形继承来说,新式类的经典类的属性查找顺序都是一样的
  • python2里面也没有提供mro这个属性
  • 在代码里面要么全是新式类,要么全是经典类,不能一部分是新式类,一部分是经典类

f287e489f7774ec9ac2c3adfbfde1939.png

属性查找顺序:

F->C->A->D->B->E->object 

class A:
    def f2(self):
        print('A.f1')


class B:
    def f1(self):
        print('B.f1')


class C(A):
    def f2(self):
        print('C.f1')


class D(B):
    def f1(self):
        print('D.f1')


class E:
    def f1(self):
        print('E.f1')


class F(C, D, E):
    def f2(self):
        print('F.f2')


print(F.mro())

obj = F()
obj.f1()

F类的MRO列表

b063f6641fa54e3e90a50f7fd3400e69.png

4.2.7.3 菱形查找

对于非菱形继承,新式类和经典类的属性查找是一样的,但是对于菱形继承.属性的查找方式就不一样了. 

580fd7501dde435ca32e81fe57a58df1.png

  • 经典类:深度优先查找,F->C->A->Z->D->B->E,找第一条分支的时候,就要找到共同的父类
  • 新式类:广度优先查找,F->C->A->D->B->E->Z,找完最后一条分支之后,才找最后的父类
class Z:
     def f1(self):
         print('Z.f1')


class A(Z):
    def f2(self):
        print('A.f1')


class B(Z):
    def f2(self):
        print('B.f1')


class C(A):
    def f2(self):
        print('C.f1')


class D(B):
    def f2(self):
        print('D.f1')


class E(Z):
    def f2(self):
        print('E.f1')


class F(C, D, E):
    def f2(self):
        print('F.f2')


print(F.mro())

obj = F()
obj.f1()

a64c5e94e46f4296b80823bc5df5437f.png

4.2.8 MixIns机制

多继承可以最大程度的帮组我们重用代码,但是多继承会使得代码的可读性变差,并且它还违背了人的思维习惯.在人的思维习惯里面,继承应该表达的是 什么"是"什么的关系

在使用多继承的时候需要注意以下问题:

  • 多继承的结构不要太过复杂
  • 要满足什么"是"什么的关系

使用MixIns机制可以一定程度提高我们代码的可读性,并且可以让多继承满足什么"是"什么的关系

原理:

它没有改变多继承的本质,只是通过命名规范来达到提升代码可读性的效果.它规定我们可以在需要的父类后面加上一个后缀名MixIn,让别人阅读代码的时候知道这个父类只是用于混入功能的,而不是作为真正的父类.

使用MixIns注意:

用了MixIns之后,从语义层面上我们就可以区分出"真正"的父类..但是本质上面还是两个父类.我们在

  • 使用MixIn的时候,它必须表示某一种功能,而不是某一种事物.我们来表达"是"这种关系的类,才能是一种事物.
  • 对于Mixin类的命名,一般以mixin,able,ible作为后缀
  • mixin类的责任必须单一(不要将所有的功能都往mixin里面放置)
  • mixin类里面功能的实现不能依赖于子类(mixin类里面不应该调用子类的功能)
  • 我们在使用mixin表达多继承的时候,表达"是"这种关系的类一定要放在最后,并且只应该继承一个.但是可以继承多个mixin类
  • 一个类继承的mixin类越多,代码的可读性就越差.并且继承的层级太多,阅读代码越容易被绕晕
class Fowl:  # 家禽类
    pass

class SwimMixIn:
    def swimming(self):
        pass

class Chicken(Fowl):  # 鸡
    pass

class Duck(SwimMixIn, Fowl):  # 鸭
    pass

class Goose(SwimMixIn, Fowl):  # 鹅
    pass

4.2.9 super

对于派生的第三种情况,子类重用父类的方法,但是不是单纯的重用,而是在父类方法的基础上进行拓展。

4.2.9.1 super严格依赖继承

  • 这是不依赖继承关系,直接使用父类的类名调用__init__方法。

ba110ae4e140464db11331e8e9a270db.png

  • super是严格依赖继承关系,实现的重用父类的代码 

0196430b01b14c53a19c0964a1541983.png

4.2.9.2 super实现的原理 

 在这里调用super会得到一个特殊的对象,这个对象会参照self所属类的mro列表。从super当前类的所处位置的下一个类开始查找属性.

class Human:
    star = 'earth'

    def __init__(self, name, age, gender):
        self.name = name
        self.age = age
        self.gender = gender


class Chinese(Human):
    # star = 'earth'
    nation = 'China'

    def __init__(self,  name, age, gender,account):
        # Human.__init__(self, name, age, gender)
        super(Chinese,self).__init__(name, age,gender)
        self.account = account

    def speak_chinese(self):
        print(f'{self.name}在说普通话')


class American(Human):
    star = 'earthxxx'
    nation = 'America'

    def speak_english(self):
        print(f'{self.name}在说英语')


dy_obj = Chinese('董永', 18, '男',1000)
print(Chinese.mro())

4d308539166c452f835dc1471efec70c.png

  • self所属类的mro列表,就是Chinese.mro()
  • super当前所处类是Chinese,下一个位置就是Human类开始查找属性(查找init)

4.2.9.3 super 自动传self 

使用super调用方法的时候,super也会自动传self.

4.2.9.4 super python2-3间的区别

super只能用在新式类里面,在新式类里面还有python2和python3的区别

  • 在python2里面必须要将super(chinese,self)两个参数加上
  • 在python3里面可以直接super().__init__
  • 如果是为了兼容python2代码可以直接将两个参数加上
  • 为了让python2和python3之间的代码兼容可以让父类都继承object变为新式类,同时让super(chinese,self)完整形式来创建对象

经典类使用super会报错,因为经典类不存在mro列表

class Human:
    star = 'earth'

    def __init__(self, name, age, gender):
        self.name = name
        self.age = age
        self.gender = gender


class Chinese(Human):
    # star = 'earth'
    nation = 'China'

    def __init__(self,  name, age, gender,account):
        # Human.__init__(self, name, age, gender)
        super(Chinese,self).__init__(name, age,gender)
        self.account = account

    # def speak_chinese(self):
    #     print(f'{self.name}在说普通话')


class American(Human):
    star = 'earthxxx'
    nation = 'America'

    # def speak_english(self):
    #     print(f'{self.name}在说英语')


dy_obj = Chinese('董永', 18, '男',1000)
print(Chinese.mro())
print(dy_obj.__dict__)

e863514016864cb5ad3586b46230e5fa.png

新式类python2里面的效果:

class Human(object):
    star = 'earth'

    def __init__(self, name, age, gender):
        self.name = name
        self.age = age
        self.gender = gender


class Chinese(Human):
    # star = 'earth'
    nation = 'China'

    def __init__(self,  name, age, gender,account):
        # Human.__init__(self, name, age, gender)
        super(Chinese,self).__init__(name, age,gender)
        self.account = account

    # def speak_chinese(self):
    #     print(f'{self.name}在说普通话')


class American(Human):
    star = 'earthxxx'
    nation = 'America'

    # def speak_english(self):
    #     print(f'{self.name}在说英语')


dy_obj = Chinese('董永', 18, '男',1000)
print(Chinese.mro())
print(dy_obj.__dict__)

025993fc359642e9aa93ac5d3b6e8c49.png

4.2.9.5 super找属性不是看的父类

super查找属性,参考的是对象所属类的mro列表,从super当前所在类的下一个类开始查找属性。绝对不是我们看到的父类。 

class A:
    def f1(self):
        print('A.f1')
        super(A,self).f1()


class B:
    def f1(self):
        print('B.f1')


class C(A, B):
    pass


obj = C()
obj.f1()
print(C.mro())

f42db5076cae43dfb6017ca41d5f61ed.png

4.3 多态

4.3.1 多态概念

多态是一种编程思想,在程序里面表达多态使用的就是继承,多态只是在继承的背景下演化出来的一个概念.

多态性:可以在不考虑对象具体类型的情况下,而直接使用对象.

多态性的好处:

我们只需要学习他们统一父类里面的方法就可以了,只要父类里面有,我们通过子类对象肯定也可以访问.

统一了使用标准,以不变应万变,不论对象千变万化,使用者都是同一种形式去调用.这个好处本质上是父类带给我们的.父类的作用就是统一所有子类的方法,父类统一子类的方法之后,我们才有了统一接口的可能性.

class Car:
    def run(self):
        print('开始跑', end=' ')

class Benz(Car):
    def run(self):
        super().run()
        print('加98号汽油')

class Lx(Car):
    def run(self):
        super().run()
        print('充电')

class Auto(Car):
    def run(self):
        super().run()
        print('加92号汽油')

car1 = Benz()
car2 = Lx()
car3 = Auto()
car1.run()
car2.run()
car3.run()

def drive_car(car):
    car.run()

drive_car(car1)
drive_car(car2)
drive_car(car3)

1db4ed3b9f0b435da0aa1ee9d8c1e90e.png

在python里面我们用到的所有数据类型都是类,我们所有的数据都是一个个对象.

len()就可以称之为一个接口,它可以len()各个类型的数据.为什么它可以达到这个统一的使用方法?

len("abc")
len([1,2.3])
len({"a":1,"b":2})
print("abc".__len__())
print([1, 2, 3].__len__())
print({"a": 1, "b": 2}.__len__())

它类似于前面的drive_car功能,我们可以传不同类型的对象.它们下面都有一个__len__()方法.这就是多态性的体现,不同类型的都有共同的方法名.这样我们就可以定义一个统一的接口my_len().只要传进来的obj对象统一了标准,都有__len__方法,那么我们就可以使用这个接口

def my_len(obj):
    return obj.__len__()


print(my_len("abc"))
print(my_len([1, 2.3]))
print(my_len({"a": 1, "b": 2}))

c0f42940187d4b00a79bd7f7619c7f6e.png

但是在python里面多态不是这么使用的,python推崇的不是继承的方式表达多态. 

4.3.2 鸭子类型

父类的作用就是用来统一标准的,让子类都有run这个功能。如果一开始不继承父类,我在一开始定义这三个类方法命名的时候,按照统一的方法规范来命名,都有run方法。这样我们即便是不使用继承,也能够达到统一标准的目的。

python很多地方只是从规范上面规定,并不会限制我们。我们即便没有继承父类,只要我们定义类的时候按照统一的标准来。也能够达到统一规范的效果。

继承是一种耦合思想的表现,我们不用继承就达到了一种解耦合的思想

所以在python里面实现多态,可以不依赖于继承,只要定义的时候,让他们长的像就可以了,这就是python推崇的鸭子类型。

linux一切皆文件,就是多态性的体现。实现统一的标准。

# 鸭子类型
# linux:一切皆文件

class Disk:
    def read(self):
        print('Disk.read')

    def write(self):
        print('Disk.write')

class Memory:
    def read(self):
        print('Memory.read')

    def write(self):
        print('Memory.write')

class Txt:
    def read(self):
        print('Txt.read')

    def write(self):
        print('Txt.write')
        print('Txt.write')

4.3.3 抽象基类

如果非要使用父类来达到规范子类的效果,可以引入抽象基类的概念。

我们在继承的背景下,来实现多态性时,父类的方法更多的就不是来实现功能的,而是主要用来规范子类标准的。

可以通过抽象基类,让父类强制子类遵循父类的标准。

  • 导入模块abc(abstructmethod)
  • 父类继承metaclass=abc.ABCMeta,父类就变为了抽象基类

  • run()加上装饰器,@abc.abstractmethod

import abc

class Car(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def run(self):
        pass


class Benz(Car):
    def run(self):
        pass

class Lx(Car):
    def run(self):
        pass

class Auto(Car):
    def run(self):
        pass

car1 = Benz()
car2 = Lx()
car3 = Auto()
car1.run()
car2.run()
car3.run()

加上这个装饰器之后,子类如果不写父类规定的run方法。不用子类没有问题,一用子类就会报错。

import abc
class Car(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def run(self):
        pass
class Benz(Car):
    pass
class Lx(Car):
    pass
class Auto(Car):
    pass
car1 = Benz()
car2 = Lx()
car3 = Auto()
car1.run()
car2.run()
car3.run()

e5e298092c7a45eea6df991938e9a393.png

目的:

让父类成为一个规范标准的作用,所有继承我这个抽象基类的子类。必须定义抽象基类实现的规定的方法。

注意:

抽象基类不能直接用于实例化,它的作用只是用来规范标准的。

e1b2edf742d4490f814c0ff6ec0cd8b5.png

 5.类方法与静态方法

5.1 类方法

前面学的绑定方法是绑定给对象使用的,还有一种方法是专门绑定给类使用的。绑定给对象的方法会自动将对象传递进去,绑定给类的方法,会自动将类传递进去。

类方法的作用就是自动将类传递进去,通过类能做的事情其实就是实例化对象。通过它我们只是有了一种新的造对象的方式。

还有一种情况就是使用将类作为参数进行传递时,也是可以使用类方法的

  • 在类方法前面加上一个装饰器,@classmethod
  • 给函数里面传递类,cls,参数使用class是规范要求
import settings


class GetIp:
    def __init__(self, ip, port):
        self.ip = ip
        self.port = port

    def get_ip(self):
        print(self.ip, self.port)

    @classmethod
    def f1(cls):
        obj = cls(settings.IP, settings.PORT)
        return obj

obj = GetIp.f1()
print(obj.__dict__)

44348e60e0494c92875ea36f2fc2dac3.png

99d9602353a14bfe8b983e03ed25ff2e.png

5.2 静态方法

 类里面的函数有两种用途,一种是绑定给对象使用的,一种是绑定给类使用的。如果函数子代码需要使用到对象,这个函数就需要绑定给对象。如果函数子代码需要使用到类,这个函数就需要绑定给类。

还有一种方法不需要用到类,也不要用到对象。它就是一个独立的功能。就是静态方法,对象和类都是可以调用它的,只是没有自动传参的功能。

在函数上面加上 @staticmethod

e89214ea8b7f42c1ac3ce5b373d1ddda.png

6.反射机制

6.1 何为反射机制

 python是一门强类型动态解释型语言,反射机制则是动态语言的关键。

动态语言,在程序里面定义变量的时候,不需要指定数据类型。只有执行代码赋值的时候,才识别它的数据类型。

静态语言,在定义变量的时候,就要指定它的数据类型。

只要是动态语言就一定会有反射机制

定义:在程序运行过程中,动态获取对象信息,以及动态调用对象方法的功能。

程序只要开始运行,就是计算机来识别代码,和程序员就没有关系了。这时候程序获取到一个对象,程序如何知道有哪些属性??如果程序不知道对象存在哪些属性,就没有办法调用。所以需要让程序在运行过程中,自己能动态获取到对象属性和方法

我们 可以通过__dict__来判断对象有没有属性,但是有局限性,不是所有的对象都是存在__dict__的。

d4da80da10d74b90b4b57daefcb11ae7.png

6.2 反射的实现

python提供的内置函数dir(),可以查看一个对象下面存在哪些属性 

class Human:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def info(self):
        print('name:', self.name, 'age:', self.age)


obj = Human('张大仙', 73)

print(dir(obj))

列表里面的都是字符串类型的属性,所以需要通过字符串反射到真正的属性上面,从而得到属性的值

86197e1e632143069833ecb8a230c081.png

 提供了四个函数通过字符串来操作属性

1bd089b0f4c444108288249a2f4da216.png

这里的obj不仅可以是对象,也可以是类,当然后面放置的就要是类的属性了。

cccd20cd19c6457dbcbb4839dba47610.png

04567fc19f4b41129f86a78c163a0d04.png

反射就是在我们不知道对象是什么的情况下,让我们的程序程序能动态的分析出来我们的对象里面有什么 ,反射是一种动态分析的能力。

6.3 反射的案例

输入的是什么就执行什么方法,但是要判断对象是否存在这个方法,并且直接输入的内容是字符串。

通过getattr判断输入的内容是否是对象存在的属性,如果存在就通过字符串获取该属性值。如果不存在,直接调用默认的self.warning函数。

class Ftp:
    def put(self):
        print('正在上传数据。。。')

    def get(self):
        print('正在下载数据。。。')

    def interact(self):
        opt = input('>>>')
        getattr(self, opt, self.warning)()

    def warning(self):
        print('功能不存在!')


obj = Ftp()
obj.interact()

7.内置方法

以__开头__结尾的方法,会在满足条件的时候自动执行。

作用:为了定制化我们的对象或是类

__init__:在实例化对象的时候自动执行的,作用就是给对象定制独有的属性。

__str__:调用print方法的时候自动执行

打印列表的时候,打印的是列表的内容。打印对象的时候,打印的是对象的内存地址。因为列表是python的内置数据类型,python为了让列表打印的时候,容易观看特意做了定制化的处理。自定的类没有考虑到这个,所以打印出来的是内存地址。

__del__:在删除对象的时候,会先执行它。

作用:计算机的资源有两种,一种是应用程序的资源,一种是操作系统的资源。我们的对象现在有一个属性f,引用到了系统的资源。当我的对象被删除之后,python回收的只是应用程序的资源,操作系统资源python是不管的。所以要在对象删除之前告诉操作系统,对象已经不存在了,回收操作系统资源。

class Human:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __str__(self):
        return f'<{self.name}: {self.age}>'

    def __del__(self):
        self.f.close()


obj = Human('张大仙', 73)
print(obj.__str__())
print(obj)  # <张大仙:73>

bfb826fb651a4602b5166178fbc22de3.png

8.元类

8.1 元类定义

在python中一切皆是对象,类也是对象。元类就是用来实例化产生类的类

元类 --> 实例化 --> 类(Human) --> 实例化 --> 对象(obj)

  • 查看类的元类和查看对象的类是一样的,可以通过type方法
  • 通过class关键字定义的所有的类,以及内置的类,都是由内置元类type实例化产生的
class Human:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def info(self):
        print('name:', self.name, 'age:', self.age)

# print(Human.__dict__)
# # 基于类创建了一个对象
obj = Human('张大仙', 73)

print(type(obj))
print(type(Human))
print(type(str))

682ea9d12af144689753bc8acce5fa4e.png

8.2 class分析

class关键字是如何一步步将类造出来的:

  • 1.类名,class_name = 'Human'
  • 2.基类,继承自哪个父类,可以继承多个父类 class_bases = (object,)
  • 3.类的子代码,类的子代码本身就是一串字符串。类的子代码会在定义阶段执行,执行类的子代码就会产生类的名称空间。对应的就是一个类的字典 class_dict.类的子代码运行过程中,会将产生的名字都丢到class_dict中
# # 3、执行类子代码,产生名称空间
class_dic = {}
class_body = '''
def __init__(self, name, age):
    self.name = name
    self.age = age
def info(self):
    print('name:', self.name, 'age:', self.age)
'''
exec(class_body, {}, class_dic)
print(class_dic)

9a9e5db0c30149ffa9c0e2633436c39b.png

class关键字产生的名称空间,比exec执行类的子代码产生的名称空间,多了更多的内置属性。我们创建的名字空间,只存在类子代码里面的名字0e5f7d217ad54146bd6c7d6981972517.png 

  • 4.调用元类 ,元类是可以指定的,我们可以自定义元类。如果没有自定义元类,使用的就是内置元类type.调用type将前面散步创建的参数传递进去。

Human不是类,type的返回值才是类。他的类名则是字符串格式的叫做class_name = 'Human'。前面使用class定义的Human,也只是变量名而已。变量名指向的值才是我们的类。

# # 4、调用元类
Human = type(class_name, class_bases, class_dic)
print(Human)

d03ce5648245431380ee6b91aadf1a99.png class底层做的事情就是以上四步:获取类名,获取基类,获取名称空间,调用元类。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2024/10/16 0:39
# @Author : George
# # 1、类名
class_name = 'Human'
#
# # 2、基类
class_bases = (object,)
#
# # 3、执行类子代码,产生名称空间
class_dic = {}
class_body = '''
def __init__(self, name, age):
    self.name = name
    self.age = age
def info(self):
    print('name:', self.name, 'age:', self.age)
'''
exec(class_body, {}, class_dic)
# print(Human.__dict__)
# print(class_dic)
#
#
# # 4、调用元类
Human = type(class_name, class_bases, class_dic)
obj = Human('张大仙', 73)
obj.info()

fa90717029984b5d94b71b1ad6aab405.png

通过调用元类最后得到一个类,这个类就是元类实例化的对象。清楚了这个步骤之后,我们以后就可以针对性的对这些步骤做出定制化的操作.主要是第四步。在调用元类进行实例化类的时候,可以进行定制化的操作,比如将名称空间里面的属性隐藏掉,要求类名不能有下划线。这些都是要求我们自定义元类。

8.3 自定义元类

  • 自定义元类的话,必须使用关键字 metaclass,指定使用的自定义元类
  • 只有继承type的类才是元类,自定义元类的话,必须让其继承type

现在通过class造Human的时候也是四步,拿类名,拿基类,执行类子代码拿名称空间,调用元类,就是自定义的Mytype( Human = Mytype(class_name,class_bases,class_dict)),进行实例化,得到Human这个类

class Human(metaclass=Mytype):
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def info(self):
        print("name:", self.name, "age", self.age)

现在Human是通过Mytype实例化得到的,Mytype实例化的步骤如下:

  • # 1. 产生空对象Human
  • # 2. 调用Mytype的__init__方法,同时将空对象,以及调用类时候括号里面传递的参数,一同传递给__init__方法.初始化对象Human
  • # 3. 返回初始化好的对象Human
# 自定义元类
class Mytype(type):
    def __init__(self,class_name,class_bases,class_dict):
        print("Mytype.init")


# Human = Mytype(class_name,class_bases,class_dict)
# 1. 产生空对象Human
# 2. 调用Mytype的__init__方法,同时将空对象,以及调用类时候括号里面传递的参数,一同传递给__init__方法.初始化对象Human
# 3. 返回初始化好的对象Human


class Human(metaclass=Mytype):
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def info(self):
        print("name:", self.name, "age", self.age)

8.4 元类的init 

进行定制化操作,也就是类的实例化的第二步,调用Mytype的__init__方法.

  • 类名不能加下划线
  • 创建类的时候,必须添加文档注释
# 自定义元类
class Mytype(type):
    def __init__(self,class_name,class_bases,class_dict):
        if "_" in class_name:
            raise NameError("类名不能存在下划线!")
        if not class_dict.get("__doc__"):
            raise SyntaxError("创建类的时候必须添加文档注释!")


# Human = Mytype(class_name,class_bases,class_dict)
# 1. 产生空对象Human
# 2. 调用Mytype的__init__方法,同时将空对象,以及调用类时候括号里面传递的参数,一同传递给__init__方法.初始化对象Human
# 3. 返回初始化好的对象Human


class Human(metaclass=Mytype):
    """
    这是文档注释
    """
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def info(self):
        print("name:", self.name, "age", self.age)

8.5 元类的__new__

类的实例化第一步,产生空对象Human,进行定制化操作.

__new__是在__init__之前被调用的,也就是说,调用Mytype的__new__方法,产生一个空对象Human

# 自定义元类
class Mytype(type):
    def __init__(self, class_name, class_bases, class_dict):
        if "_" in class_name:
            raise NameError("类名不能存在下划线!")
        if not class_dict.get("__doc__"):
            raise SyntaxError("创建类的时候必须添加文档注释!")

    def __new__(cls, *args, **kwargs):
        print(cls)  # Mytype这个类本身
        print(args)  # args,就是class机制第四步传递进来的参数,类名/基类/名称空间
        print(kwargs)
        return super().__new__(cls, *args, **kwargs)  # 在动底层代码没有自动传参


# Human = Mytype(class_name,class_bases,class_dict)
# 1. 产生空对象Human
# 2. 调用Mytype的__init__方法,同时将空对象,以及调用类时候括号里面传递的参数,一同传递给__init__方法.初始化对象Human
# 3. 返回初始化好的对象Human


class Human(metaclass=Mytype):
    """
    这是文档注释
    """

    def __init__(self, name, age):
        self.name = name
        self.age = age

    def info(self):
        print("name:", self.name, "age", self.age)

8.6 元类的__call__

调用Mytype等价于调用__call__ ,在__call__的内部就是做的这几步

  • 调用Mytype的__new__方法,产生一个空对象Human
  • 调用Mytype的__init__方法,将及调用类时候括号里面传递的参数,一同传递给__init__方法.初始化对象Human
  • 返回初始化好的对象Human

__call__()会在对象被调用的时候进行触发.所以如果想把一个对象,做成一个可以加括号调用的对象,就可以在对象的类里面加一个__call__方法.

class Test:
    def __init__(self,name,age):
        self.name = name
        self.age = age

    def __call__(self, *args, **kwargs):
        print("Test.__call__")
        return "abc"

obj = Test("张大仙",23)
res = obj() # => Test.__call__
print(res) # => abc

现在Human可以加括号调用,也就是说它的元类Mytype里面一定有一个__call__方法.

Human = Mytype(class_name,class_bases,class_dict),调用了内置元类type的__call__方法.

也就是说

  • 对象()        --> 类内部的__call__
  • 类()            --> 自定义元类里面的__call__
  • 自定义元类()  --> 内置元类的__call__

Mytype里面的__init__和__new__是为了控制Human的产生,Human造好之后.

我们现在需要控制的是Human对象的产生(Human("张大仙",23) => 触发了Mytype的__call__方法

  • 调用Human的__new__方法
  • 调用Human的__init__方法
  • 返回初始化好的对象 
class Mytype(type):
    def __call__(self, *args, **kwargs):
        human_obj = self.__new__(self)
        self.__init__(human_obj, *args, **kwargs)
        # print(human_obj.__dict__) # => {'name': '张大仙', 'age': 23}
        # 定制化对象的属性,所有的属性名字前面加H
        dict = {}
        for key in human_obj.__dict__:
            dict["H_"+key] = human_obj.__dict__[key]
        human_obj.__dict__ = dict
        return human_obj


class Human(metaclass=Mytype):
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def info(self):
        print("name:", self.name, "age", self.age)

    def __new__(cls, *args, **kwargs):
        # 造Human的空对象
        obj = super().__new__(cls)
        # 定制化操作
        return obj


obj = Human("张大仙", 23)
print(obj.__dict__)

8.7 元类的属性查找

  • 通过元类查找属性,不会涉及到元类的查找
  • 通过查找属性的时候,会先顺着父类的路线查找,和对象的查找属性是一样的.如果所有的父类都没有,最后会去元类找
  • 父类使用了自定义元类,所有继承它的子类,都会使用这个自定义元类

9.单例模式

是为了保证某一个类,只能有一个实例对象存在.如果我们在写代码的过程中,使用同一个类产生了很多对象,而这些对象只是为了执行某一个或是某几个功能.这种频繁创建和销毁对象的过程就会特别浪费内存资源.

单例模式:只允许内存中有一个实例,它减少了内存的消耗

单例模式的实现

9.1 模块导入

python模块就是一个天然的单例模式

# _*_ coding utf-8 _*_
# george
# time: 2025/2/17下午5:12
# name: setting.py
# comment:
class Human:
    def __init__(self,name,age):
        self.name = name
        self.age = age


obj = Human("张大仙",73)
 _*_ coding utf-8 _*_
# george
# time: 2025/2/17下午5:12
# name: 单例模式.py
# comment:

from setting import obj

9.2 类装饰器的实现

需要知道的是,装饰器的作用就是@outer => recharge = outer(recharge)

现在recharge指向的是outer.wrapper的内存地址。现在就是wrapper调用两次。

import time


def outer(func):
    print(111)

    def wrapper(*args, **kwargs):
        start = time.time()
        reponse = func(*args, **kwargs)
        end = time.time()
        print(end - start)
        return reponse

    return wrapper


def recharge(num):
    for i in range(num, 101):
        time.sleep(0.05)
        print(f"\r当前电量为:{'|' * i} {i}%", end="")
    print("电量已充满")
    return 100


recharge = outer(recharge)
recharge(10)
recharge(20)
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2025/2/17 20:05
# @Author : George


def single_mode(cls):
    obj = None

    def wrapper(*args, **kwargs):
        nonlocal obj
        if not obj:
            obj = cls(*args, **kwargs)
        return obj

    return wrapper


@single_mode  # Human = single_mode(Human)
class Human:
    def __init__(self, name, age):
        self.name = name
        self.age = age


obj1 = Human("zs", 11)
obj2 = Human("li", 22)
# <__main__.Human object at 0x000001EA06B0F6E0> <__main__.Human object at 0x000001EA06B0F6E0>
print(obj1, obj2)

9.3 类绑定方法的实现

class Human:
    obj = None
    def __init__(self,name,age):
        self.name = name
        self.age = age

    @classmethod
    def get_obj(cls,*args,**kwargs):
        if not cls.obj:
            cls.obj = cls(*args,**kwargs)
        return cls.obj

obj1 = Human.get_obj("张大仙",23)
obj2 = Human.get_obj("张大仙",24)
print(obj1,obj2)

9.4 __new__方式实现

class Human:
    obj = None
    def __init__(self,name,age):
        self.name = name
        self.age = age

    def __new__(cls, *args, **kwargs):
        if not cls.obj:
            cls.obj = super().__new__(cls)
        return cls.obj

obj1 = Human("张大仙",23)
obj2 = Human("张大仙",24)
print(obj1,obj2)

9.5 元类

class Mytype(type):
    obj = None

    def __call__(self, *args, **kwargs):
        if not self.obj:
            self.obj = super().__call__(*args, **kwargs)
        return self.obj


class Human(metaclass=Mytype):
    obj = None

    def __init__(self, name, age):
        self.name = name
        self.age = age

obj1 = Human("张大仙", 23)
obj2 = Human("张大仙", 24)
print(obj1, obj2)
class Mytype(type):
    obj = None

    def __call__(self, *args, **kwargs):
        if not self.obj:
            # self.obj = super().__call__(*args, **kwargs)
            self.obj = self.__new__(self)
            self.__init__(self.obj, *args, **kwargs)
        return self.obj


class Human(metaclass=Mytype):
    obj = None

    def __init__(self, name, age):
        self.name = name
        self.age = age

obj1 = Human("张大仙", 23)
obj2 = Human("张大仙", 24)
print(obj1, obj2)

9.6 并发

Pyhton里面的None就是一个单例模式,代码里面所有的None都是同一个对象.这是我们在用None进行判断的时候使用的都是is None.因为都是同一个对象.

10.枚举与拉链

  • enumerate(li)返回的是一个迭代器
  • enumerate(li,1) : 让index从1开始
  • zip()返回的也是一个迭代器
  • zip里面可以传递多个值,如果值的长度不一致,就会以最短的为标准
li = ["a","b","c","d"]

for index,value in enumerate(li,1):
    print(index,value)

li = ["a","b","c","d"]
li2 = [1,2,3,4]

for i in zip(li,li2):
    print(i)

11.eval和exec

__import__:以字符串的形式导入模块

time = __import__('time')
print(time.time())

eval和exec都是将字符串作为代码执行的

eval主要用于执行表达式,也就是说它的执行代码需要有一个返回结果。

exec主要用于执行代码块的

a = 1
res = eval("a+1+2")
print(res)
res = exec("a+1+2")
print(res)

11.1 globals() locals()

  • globals()显示的就是全局名称空间里面的名字
  • locals()显示的就是局部名称空间里面的名字
print(globals())
def func():
    a = 1
    print(locals())
func()

11.2 eval

  • eval()后面两个参数就是用来传递参数的,如果不传递默认传输的就是globals()和locals()
  • 如果局部名称空间里面传递了参数,就不会使用全局空间里面的参数了
  • 执行的表达式必须有返回值
a = 3
g = {"a":a}
l = {"a":4}

res = eval("a+1+2",g,l)
print(res)
print(g)
print(l)

a = 3
g = {"a":a}
l = {"a":4}

res = eval("rr=a+1+2",g,l)

 

11.3 exec

  • exec的表达式没有返回值,他只是会将执行表达式里面的名字存储到第三个参数里面
  • 现在在全局打印a会报错,因为全局不存在。但是不传递g,l的就不会报错。因为默认传递的就是globals()和locals().locals()在全局里面就是globals()
g = {"b":3}
l = {"b":4}
exec("a=1+2+b",g,l)
print(l)

def func():
    b = 3
    g = {"b": b}
    l = {}
    exec("a=1+2+b")
    print(l)
    print(a)
func()

globals()产生的字典和全局名称空间是同一个东西。 

locals()产生的字典并没有和当前的局部名称空间绑定,它只是一个拷贝版本的字典。

11.4 系统攻击 

evel(0和exec()经常会被黑客利用,成为执行系统级别命令的入口,进而达成攻击.所以以后使用这两个函数的时候,一定要非常谨慎.

  • 用户输入什么类型,不需要做出转换,就可以获取什么类型
  • 如果确实需要他们来执行input输入的内容,就可以控制它的全局名称空间,将全局名称空间定义为空字典.它会默认将内置名称空间添加进去,我们只需要将内置名称空间重置为None即可.这样的话eval里面就访问不到任何内置函数了

 12.异常处理

异常总体上面分为两种,语法上面的错误和逻辑上面的错误.语法上面的错误没法做任何处理,语法错误是不允许存在的.

逻辑上面的错误也是分为两类:

  • 错误的发生条件可以预知
  • 错误发生的条件不可以预知

try:

        子代码块

except 异常类型 as e:

        子代码块

  • 如果是子代码块3报错,子代码块4不会执行.如果异常被异常类型1捕获,剩下的异常类型都不会执行.包括Exception.
  • Exception可以捕获所有的异常.
  • 对于同样的异常处理可以将异常放在一起
  • try不能单独和else连用,必须搭配except才可以
  • try可以和finally连用,只捕获不处理异常,但是报错之前,一定会将finally下面的代码全部执行完毕.后续的代码不会执行
try:
    子代码块1
    子代码块2
    子代码块3
    子代码块4
except 异常类型1 as e:
    子代码块

try:
    子代码块
except 异常类型2 as e:
    子代码块

try:
    子代码块
except 异常类型3 as e:
    子代码块
except Exception as e:  # Exception可以捕获所有的异常
    子代码块

else:
    子代码块: 会在被检测代码(try下面的子代码块), 没有异常的时候执行
finally:
    子代码块: 不管前面有没有发生异常都会执行

try/expect会减弱代码的可读性,可以用判断解决的问题,就不要用try

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/972151.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Spring Boot(8)深入理解 @Autowired 注解:使用场景与实战示例

搞个引言 在 Spring 框架的开发中&#xff0c;依赖注入&#xff08;Dependency Injection&#xff0c;简称 DI&#xff09;是它的一个核心特性&#xff0c;它能够让代码更加模块化、可测试&#xff0c;并且易于维护。而 Autowired 注解作为 Spring 实现依赖注入的关键工具&…

用deepseek学大模型04-机器学习建模过程

https://metaso.cn/s/7tEmBHr 目前已经学完深度学习的数学基础&#xff0c;给出机器学习建模的基本过程 好的&#xff0c;我现在需要回答用户的问题&#xff1a;已经学完深度学习的数学基础&#xff0c;现在要给出机器学习建模的基本过程。我需要结合我搜索到的资料来构建一个详…

【网络编程】网络编程基础:TCP/UDP 协议

一、什么是网络&#xff1f; 网络是信息传输&#xff0c;接收和共享的虚拟世界&#xff0c;通过把网络上的信息汇聚在一起&#xff0c;将这些资源进行共享。 初衷&#xff1a;知识共享。这里不得不提到Internet 的历史&#xff0d;它其实是“冷战”的产物&#xff1a; 1957年…

【算法】双指针(上)

目录 双指针 左右指针(对撞指针) 快慢指针 移动零 双指针解题 复写零 暴力解题 双指针解题(快慢指针) 快乐数 双指针解题(快慢指针) 盛最多水的容器 暴力解题(会超时) 双指针解题(左右指针) 有效三角形的个数 暴力解题 双指针解题(左右指针) 双指针 常见的双指…

CES Asia 2025:构建长效价值运营体系,赋能科技产业新发展

CES Asia 2025作为亚洲消费电子技术领域的盛会&#xff0c;将带来诸多令人瞩目的创新与变革。其中&#xff0c;亮点四——增加长效价值运营体系备受关注&#xff0c;为展会的参展企业和整个科技产业发展注入了新动力。 展会将推出365天在线供需对接平台&#xff0c;打破了传统…

【亚马逊开发者账号02】终审问题SA+review_Pre-review+Doc.xlsx

1.终审问题 你好感谢您在此过程中的回复和协作。所有想要构建具有受限 SP-API 角色的公开可用应用程序的开发人员都必须与我们的解决方案架构师团队一起完成架构审核。 这将需要详细说明应用程序的数据流、个人身份信息 &#xff08;PII&#xff09; 的数据保护控制&#xff0…

DeepSeek-R1论文阅读及蒸馏模型部署

DeepSeek-R1论文阅读及蒸馏模型部署 文章目录 DeepSeek-R1论文阅读及蒸馏模型部署摘要Abstract一、DeepSeek-R1论文1. 论文摘要2. 引言3. DeepSeek-R1-Zero的方法3.1 强化学习算法3.2 奖励建模3.3 训练模版3.4 DeepSeek-R1-Zero的性能、自进化过程和顿悟时刻 4. DeepSeek-R1&am…

地理探测器数据准备及驱动因素分析

地理探测器 地理探测器是一种用于分析空间数据的工具&#xff0c;主要用于检测和量化地理现象的空间异质性。它通过分析变量在不同区域内的分布特征及其相互关系&#xff0c;帮助我们理解自然和社会现象的空间分布规律以及背后可能的驱动因素。地理探测器主要由以下几个部分组…

【数据结构】(10) 排序算法

一、排序算法 冒泡排序在C语言部分学过&#xff0c;堆排序上一章学过&#xff0c;还剩五种常见排序算法。以下默认从小到大排序。 稳定性&#xff1a;相同元素在排序过后&#xff0c;前后相对位置依旧不变。一个本身稳定的排序&#xff0c;可以改成不稳定的&#xff1b…

机器学习实战(1): 入门——什么是机器学习

机器学习入门——什么是机器学习&#xff1f; 欢迎来到“机器学习实战”系列的第一篇博文&#xff01;在这一集中&#xff0c;我们将带你了解机器学习的基本概念、主要类型以及它在现实生活中的应用。无论你是初学者还是有一定经验的开发者&#xff0c;这篇文章都会为你打下坚…

HTML【详解】input 标签

input 标签主要用于接收用户的输入&#xff0c;随 type 属性值的不同&#xff0c;变换其具体功能。 通用属性 属性属性值功能name字符串定义输入字段的名称&#xff0c;在表单提交时&#xff0c;服务器通过该名称来获取对应的值disabled布尔值禁用输入框&#xff0c;使其无法被…

《TSP6K数据集进行交通场景解析》学习笔记

paper&#xff1a;2303.02835 GitHub&#xff1a;PengtaoJiang/TSP6K: The official PyTorch code for "Traffic Scene Parsing through the TSP6K Dataset". 目录 摘要 1、介绍 2、相关工作 2.1 场景解析数据集 2.2 场景解析方法 2.3 实例分割方法 2.4 无监…

Tomcat下载,安装,配置终极版(2024)

Tomcat下载&#xff0c;安装&#xff0c;配置终极版&#xff08;2024&#xff09; 1. Tomcat下载和安装 进入Apache Tomcat官网&#xff0c;我们可以看到这样一个界面。 现在官网目前最新版是Tomcat11&#xff0c;我用的是Java17&#xff0c;在这里我们选择Tomcat10即可。Tom…

【算法】回溯算法

回溯算法 什么是回溯 人生无时不在选择。在选择的路口&#xff0c;你该如何抉择 ..... 回溯&#xff1a; 是一种选优搜索法&#xff0c;又称为试探法&#xff0c;按选优条件向前搜索&#xff0c;以达到目标。但当探索到某一步时&#xff0c;发现原先选择并不优或达不到目标&am…

图解JVM-1. JVM与Java体系结构

一、前言 在 Java 开发的广袤天地里&#xff0c;不少开发者都遭遇过令人头疼的状况。线上系统毫无征兆地卡死&#xff0c;陷入无法访问的僵局&#xff0c;甚至直接触发 OOM&#xff08;OutOfMemoryError&#xff0c;内存溢出错误&#xff09;&#xff1b;面对 JVM 的 GC&#…

深入浅出 Python Logging:从基础到进阶日志管理

在 Python 开发过程中&#xff0c;日志&#xff08;Logging&#xff09;是不可或缺的调试和监控工具。合理的日志管理不仅能帮助开发者快速定位问题&#xff0c;还能提供丰富的数据支持&#xff0c;让应用更具可观测性。本文将带你全面了解 Python logging 模块&#xff0c;涵盖…

设计模式15:中介者模式

系列总链接&#xff1a;《大话设计模式》学习记录_net 大话设计-CSDN博客 1.概述 中介者模式&#xff08;Mediator Pattern&#xff09;是一种行为设计模式&#xff0c;旨在通过一个中介对象来封装一系列对象之间的交互方式&#xff0c;从而减少这些对象间的直接依赖。在该模式…

爬取网站内容转为markdown 和 html(通常模式)

我们遇到一些自己喜欢内容&#xff0c;想保存下来&#xff0c;手动复制粘贴很麻烦&#xff0c;我们使用 python 来爬取这些内容。 一、代码 downlod.py import os import requests from bs4 import BeautifulSoup from urllib.parse import urljoin# 目标网页&#xff08;可…

【Linux】命令操作、打jar包、项目部署

阿华代码&#xff0c;不是逆风&#xff0c;就是我疯 你们的点赞收藏是我前进最大的动力&#xff01;&#xff01; 希望本文内容能够帮助到你&#xff01;&#xff01; 目录 一&#xff1a;Xshell下载 1&#xff1a;镜像设置 二&#xff1a;阿里云设置镜像Ubuntu 三&#xf…

Unity合批处理优化内存序列帧播放动画

Unity合批处理序列帧优化内存 介绍图片导入到Unity中的处理Unity中图片设置处理Unity中图片裁剪 创建序列帧动画总结 介绍 这里是针对Unity序列帧动画的优化内容&#xff0c;将多个图片合批处理然后为了降低Unity的内存占用&#xff0c;但是相对的质量也会稍微降低。可自行进行…