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 = 0def __init__(self, name, speed, hp, atk):self.name = nameself.speed = speedself.hp = hpself.atk = atkself.equipment = []Hero.count += 1def 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_plusdef 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 = 0def __init__(self, name, speed, hp, atk):self.name = nameself.speed = speedself.hp = hpself.atk = atkself.equipment = []Hero.count += 1def 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_plusdef 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 = 0def __init__(self, name, speed, hp, atk):self.name = nameself.speed = speedself.hp = hpself.atk = atkself.equipment = []Hero.count += 1def 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_plusdef 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 = 0def __init__(self, name, speed, hp, atk):self.name = nameself.speed = speedself.hp = hpself.atk = atkself.equipment = []Hero.count += 1def 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_plusdef 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 = 10def __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 = 10def __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 = 10def __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 = nameself.__age = agedef 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 = nameself.__age = agedef get_name(self):return self.__namedef get_age(self):return self.__agedef set_age(self, new_age):if type(new_age) is not int:print("你个傻子,年龄必须是整数")returnif not 0 < new_age < 150:print("你个傻子,年龄必须在1-150岁之间")returnself.__age = new_agetest1 = 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 = nameself.__age = agedef get_name(self):return self.__name@property  # get_age = property(get_age)def get_age(self):return self.__agedef set_age(self, new_age):if type(new_age) is not int:print("你个傻子,年龄必须是整数")returnif not 0 < new_age < 150:print("你个傻子,年龄必须在1-150岁之间")returnself.__age = new_agedef del_age(self):del self.__ageobj = Test('lisi', 30)
print(obj.get_age)

6b6c3877c5d848168f46372a4c16f45d.png

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

class Test:def __init__(self, name, age):self.__name = nameself.__age = agedef get_name(self):return self.__name# @property  # get_age = property(get_age)def get_age(self):return self.__agedef set_age(self, new_age):if type(new_age) is not int:print("你个傻子,年龄必须是整数")returnif not 0 < new_age < 150:print("你个傻子,年龄必须在1-150岁之间")returnself.__age = new_agedef del_age(self):del self.__ageage = 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 = nameself.__age = agedef get_name(self):return self.__name# @property  # get_age = property(get_age)@propertydef age(self):return self.__age@age.setterdef age(self, new_age):if type(new_age) is not int:print("你个傻子,年龄必须是整数")returnif not 0 < new_age < 150:print("你个傻子,年龄必须在1-150岁之间")returnself.__age = new_age@age.deleterdef 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 = 10passclass Parent2(object):passclass Child1(Parent1):  # 单继承passclass Child2(Parent1, Parent2):  # 多继承passprint(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 = nameself.age = ageself.gender = genderclass 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__f1print('Test1.f1')def f2(self):print('Test1.f2')self.__f1()  # _Test1__f1class Test2(Test1):def __f1(self):  # _Test2__f1print('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:  # 家禽类passclass SwimMixIn:def swimming(self):passclass Chicken(Fowl):  # 鸡passclass Duck(SwimMixIn, Fowl):  # 鸭passclass 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 = nameself.age = ageself.gender = genderclass 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 = accountdef 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 = nameself.age = ageself.gender = genderclass 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 = nameself.age = ageself.gender = genderclass 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):passobj = 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 abcclass Car(metaclass=abc.ABCMeta):@abc.abstractmethoddef run(self):passclass Benz(Car):def run(self):passclass Lx(Car):def run(self):passclass Auto(Car):def run(self):passcar1 = Benz()
car2 = Lx()
car3 = Auto()
car1.run()
car2.run()
car3.run()

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

import abc
class Car(metaclass=abc.ABCMeta):@abc.abstractmethoddef 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 settingsclass GetIp:def __init__(self, ip, port):self.ip = ipself.port = portdef get_ip(self):print(self.ip, self.port)@classmethoddef f1(cls):obj = cls(settings.IP, settings.PORT)return objobj = 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 = nameself.age = agedef 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 = nameself.age = agedef __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 = nameself.age = agedef 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 = nameself.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 = nameself.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 = nameself.age = agedef 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. 返回初始化好的对象Humanclass Human(metaclass=Mytype):def __init__(self, name, age):self.name = nameself.age = agedef 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. 返回初始化好的对象Humanclass Human(metaclass=Mytype):"""这是文档注释"""def __init__(self, name, age):self.name = nameself.age = agedef 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. 返回初始化好的对象Humanclass Human(metaclass=Mytype):"""这是文档注释"""def __init__(self, name, age):self.name = nameself.age = agedef 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 = nameself.age = agedef __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}# 定制化对象的属性,所有的属性名字前面加Hdict = {}for key in human_obj.__dict__:dict["H_"+key] = human_obj.__dict__[key]human_obj.__dict__ = dictreturn human_objclass Human(metaclass=Mytype):def __init__(self, name, age):self.name = nameself.age = agedef info(self):print("name:", self.name, "age", self.age)def __new__(cls, *args, **kwargs):# 造Human的空对象obj = super().__new__(cls)# 定制化操作return objobj = 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 = nameself.age = ageobj = 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 timedef outer(func):print(111)def wrapper(*args, **kwargs):start = time.time()reponse = func(*args, **kwargs)end = time.time()print(end - start)return reponsereturn wrapperdef recharge(num):for i in range(num, 101):time.sleep(0.05)print(f"\r当前电量为:{'|' * i} {i}%", end="")print("电量已充满")return 100recharge = outer(recharge)
recharge(10)
recharge(20)
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2025/2/17 20:05
# @Author : Georgedef single_mode(cls):obj = Nonedef wrapper(*args, **kwargs):nonlocal objif not obj:obj = cls(*args, **kwargs)return objreturn wrapper@single_mode  # Human = single_mode(Human)
class Human:def __init__(self, name, age):self.name = nameself.age = ageobj1 = 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 = Nonedef __init__(self,name,age):self.name = nameself.age = age@classmethoddef get_obj(cls,*args,**kwargs):if not cls.obj:cls.obj = cls(*args,**kwargs)return cls.objobj1 = Human.get_obj("张大仙",23)
obj2 = Human.get_obj("张大仙",24)
print(obj1,obj2)

9.4 __new__方式实现

class Human:obj = Nonedef __init__(self,name,age):self.name = nameself.age = agedef __new__(cls, *args, **kwargs):if not cls.obj:cls.obj = super().__new__(cls)return cls.objobj1 = Human("张大仙",23)
obj2 = Human("张大仙",24)
print(obj1,obj2)

9.5 元类

class Mytype(type):obj = Nonedef __call__(self, *args, **kwargs):if not self.obj:self.obj = super().__call__(*args, **kwargs)return self.objclass Human(metaclass=Mytype):obj = Nonedef __init__(self, name, age):self.name = nameself.age = ageobj1 = Human("张大仙", 23)
obj2 = Human("张大仙", 24)
print(obj1, obj2)
class Mytype(type):obj = Nonedef __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.objclass Human(metaclass=Mytype):obj = Nonedef __init__(self, name, age):self.name = nameself.age = ageobj1 = 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 = 1print(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 = 3g = {"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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

网络安全“挂图作战“及其场景

文章目录 一、网络安全挂图作战来源与定义1、网络安全挂图作战的来源2、网络安全挂图作战的定义 二、挂图作战关键技术三、挂图作战与传统态势感知的差异四、挂图作战主要场景五、未来趋势结语 一、网络安全挂图作战来源与定义 1、网络安全挂图作战的来源 网络安全挂图作战的…

【嵌入式Linux应用开发基础】read函数与write函数

目录 一、read 函数 1.1. 函数原型 1.2. 参数说明 1.3. 返回值 1.4. 示例代码 二、write 函数 2.1. 函数原型 2.2. 参数说明 2.3. 返回值 2.4. 示例代码 三、关键注意事项 3.1 部分读写 3.2 错误处理 3.3 阻塞与非阻塞模式 3.4 数据持久化 3.5 线程安全 四、嵌…

嵌入式八股文(四)计算机网络篇

第一章 基础概念 1. 服务 指网络中各层为紧邻的上层提供的功能调用,是垂直的。包括面向连接服务、无连接服务、可靠服务、不可靠服务。 2. 协议 是计算机⽹络相互通信的对等层实体之间交换信息时必须遵守的规则或约定的集合。⽹络协议的三个基本要素:语法、…

LabVIEW 天然气水合物电声联合探测

天然气水合物被认为是潜在的清洁能源&#xff0c;其储量丰富&#xff0c;预计将在未来能源格局中扮演重要角色。由于其独特的物理化学特性&#xff0c;天然气水合物的探测面临诸多挑战&#xff0c;涉及温度、压力、电学信号、声学信号等多个参数。传统的人工操作方式不仅效率低…

JAVA代码走查重构常用prompt

代码重构prompt&#xff1a; ## 主题&#xff1a; 代码重构 ## 角色扮演: 你是软件开发大师Martin Fowler&#xff0c;精通代码重构、面向对象编程、Clean Code和设计模式&#xff0c;且熟练掌握《重构&#xff0c;改善既有代码的设计》这本书中的重构思想和各种重构方法。 ## …

[数据结构]红黑树,详细图解插入

目录 一、红黑树的概念 二、红黑树的性质 三、红黑树节点的定义 四、红黑树的插入&#xff08;步骤&#xff09; 1.为什么新插入的节点必须给红色&#xff1f; 2、插入红色节点后&#xff0c;判定红黑树性质是否被破坏 五、插入出现连续红节点情况分析图解&#xff08;看…

STM32 HAL库USART串口DMA IDLE中断编程:避坑指南

HAL_UART_Receive接收最容易丢数据了,STM32 HAL库UART查询方式实例 可以考虑用中断来实现,但是HAL_UART_Receive_IT还不能直接用,容易数据丢失,实际工作中不会这样用,STM32 HAL库USART串口中断编程&#xff1a;演示数据丢失, 需要在此基础优化一下. STM32F103 HAL库USART串口…

sql注入中information_schema被过滤的问题

目录 一、information_schema库的作用 二、获得表名 2.1 sys.schema_auto_increment_columns 2.2 schema_table_statistics 三、获得列名 join … using … order by盲注 子查询 在进行sql注入时&#xff0c;我们经常会使用information_schema来进行爆数据库名、表名、…

Jenkins 给任务分配 节点(Node)、设置工作空间目录

Jenkins 给任务分配 节点(Node)、设置工作空间目录 创建 Freestyle project 类型 任务 任务配置 Node 打开任务-> Configure-> General 勾选 Restrict where this project can be run Label Expression 填写一个 Node 的 Label&#xff0c;输入有效的 Label名字&#x…

Electron:使用electron-react-boilerplate创建一个react + electron的项目

使用 electron-react-boilerplate git clone --depth 1 --branch main https://github.com/electron-react-boilerplate/electron-react-boilerplate.git your-project-name cd your-project-name npm install npm start 安装不成功 在根目录加上 .npmrc文件 内容为 electron_…

数控机床设备分布式健康监测与智能维护系统MTAgent

数控机床设备分布式健康监测与智能维护系统MTAgent-v1.1融合了目前各种先进的信号处理以及信息分析算法以算法工具箱的方式&#xff0c;采用了一种开发的、模块化的结构实现信号各种分析处理&#xff0c;采用Python编程语言&#xff0c;满足不同平台需求(包括Windows、Linux)。…

FPGA VIVADO:axi-lite 从机和主机

FPGA VIVADO:axi-lite 从机和主机 TOC在这里插入代码片 前言 协议就不详细讲解了&#xff0c;直接看手册即可。下面主要如何写代码和关键的时序。 此外下面的代码可以直接用于实际工程 一、AXI-LITE 主机 数据转axi lite接口&#xff1a; 读/写数据FIFO缓存 仲裁&#xff1a…

1. 对比 LVS 负载均衡群集的 NAT 模式和 DR 模式,比较其各自的优势 。2. 基于 openEuler 构建 LVS-DR 群集。

DR 模式 * 负载各节点服务器通过本地网络连接&#xff0c;不需要建立专用的IP隧道 原理&#xff1a;首先负载均衡器接收到客户的请求数据包时&#xff0c;根据调度算法决定将请求发送给哪个后端的真实服务器&#xff08;RS&#xff09;。然后负载均衡器就把客户端发送的请求数…

ollama server启动服务后如何停止

要停止 Ollama 服务器服务&#xff0c;取决于如何启动该服务的。以下是几种常见的启动方法和相应的停止服务的步骤&#xff1a; 1. 直接在命令行中启动 如果是在命令行中直接启动 Ollama 服务器的&#xff0c;例如使用以下命令&#xff1a; ollama serve 可以通过以下方式停…

【设计模式】03-理解常见设计模式-行为型模式(专栏完结)

前言 前面我们介绍完创建型模式和创建型模式&#xff0c;这篇介绍最后的行为型模式&#xff0c;也是【设计模式】专栏的最后一篇。 一、概述 行为型模式主要用于处理对象之间的交互和职责分配&#xff0c;以实现更灵活的行为和更好的协作。 二、常见的行为型模式 1、观察者模…

mapbox基础,使用geojson加载line线图层,实现纯色填充、图片填充、虚线和渐变效果

👨‍⚕️ 主页: gis分享者 👨‍⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅! 👨‍⚕️ 收录于专栏:mapbox 从入门到精通 文章目录 一、🍀前言1.1 ☘️mapboxgl.Map 地图对象1.2 ☘️mapboxgl.Map style属性1.3 ☘️line线图层样式二、🍀使用geojson加载…

深入浅出:CUDA是什么,如何利用它进行高效并行计算

在当今这个数据驱动的时代&#xff0c;计算能力的需求日益增加&#xff0c;特别是在深度学习、科学计算和图像处理等领域。为了满足这些需求&#xff0c;NVIDIA推出了CUDA&#xff08;Compute Unified Device Architecture&#xff09;&#xff0c;这是一种并行计算平台和编程模…

LNMP+Zabbix安装部署(Zabbix6.0 Lnmp+Zabbix Installation and Deployment)

LNMPZabbix安装部署&#xff08;Zabbix6.0&#xff09; 简介 LNMP&#xff08;Linux Nginx MySQL PHP&#xff09;是一种流行的Web服务器架构&#xff0c;广泛用于搭建高性能的网站和应用程序。Zabbix 是一个开源的监控软件&#xff0c;可以用来监控网络、服务器和应用程序…

Docker 部署 Dify:轻松集成 Ollama 和 DeepSeek

1 Ollama的安装及使用 1.1 什么是Ollama&#xff1f; Ollama 是一个用于本地部署和运行大型语言模型的框架。 Ollama 的作用包括&#xff1a; 本地模型运行&#xff1a;Ollama 允许在本地机器上运行大型语言模型&#xff08;如 LLaMA、DeepSeek 等&#xff09;&#xff0c;无…

C++笔记之标准库中用于处理迭代器的`std::advance`和`std::distance`

C++笔记之标准库中用于处理迭代器的std::advance和std::distance code review! 文章目录 C++笔记之标准库中用于处理迭代器的`std::advance`和`std::distance`一.`std::advance`函数原型参数说明使用场景示例代码示例 1:移动 `std::vector` 的随机访问迭代器示例 2:移动 `st…