1.对象的定义
1.1 什么是对象
面向过程:将程序流程化
对象:就是“容器“,是用来存储数据和功能的,是数据和功能的集合体。
面向对象和面向过程没有优劣之分,它们只是使用的场景不同罢了。
1.2 为什么要有对象
有了对象之后,数据和功能之间有关系的会被放在同一个”容器里面“,它的解耦合程度会非常高,方便阅读和维护。
1.3 怎么造对象
如果一个模块里面存放了数据和功能,那么这个模块就是容器,可以被称之为对象。对象的本质就是存放数据和功能的”容器“。我们只要能做到这一点,我们就是在使用面向对象的思想在写程序。但是使用文件整合的话,文件就太多了。
2. 类
2.1 类的概念
类的一方面是将不同的对象做区分,归类的
为了解决不同的对象存储相同数据的问题。所以不仅仅对象是”容器“,类也是”容器“。类用来存放同类对象的他们共有的数据和功能的。所以类也是对象。
将同类对象共有的数据提取到类里面,不是说私有的数据变为共有的了。本质上还是它们自己的。这样做的目的,仅仅是为了归类和节省空间。
在查找数据时,先找对象本身的数据,没有才会到类里面去找。
2.2 类怎么定义
类名采用驼峰体命名法,首字母大写
类的子代码在定义阶段就会直接运行。所以类的名称空间在定义阶段就会产生
2.3 类的属性访问
Hero.__dict__会得到一个字典,这个字典里面存放的就是类名称空间里面的名字
可以使用字典取值的方式,来获取类的属性和方法
也可以通过类名.属性名的方式来对类的属性进行访问,但是本质上就是通过字典取值。这样只不过是python做的优化罢了。
3.对象
3.1 对象的产生
类造出来之后,他本质上是为了将所有的同类对象提取出来。类里面存放的东西只是为了减少计算机资源的浪费,本质上还是属于具体对象的。所以将数据和功能提取到类里面之后还没有结束,还是需要建立对象和类之间的关联。让类顺着这个关联,还可以访问到原来属于它那部分的数据和功能。
只要基于类加括号就会创造一个对象,并且会将类和对象之间建好关联。调好之后就会返回值就是一个英雄的对象。
类加括号的调用,并不会触发类子代码的运行。类的子代码在定义阶段就已经执行过了。调用的时候只会帮我们造好一个对象,然后建立好类与对象之间的关联,并不会执行类的子代码。调用一次就会帮我们创建一个对象。
对象里面也存在一个__dict__,里面存放的就是对象的属性。现在三个字典里面都是空的。因为我们只是基于类创建了对象,并没有往对象里面添加内容。但是并不意味着这些对象里面没有任何属性,因为类里面有。
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)
问题:这样就出现了一个问题,给对象添加属性的过程都是一样的并且重复的,太过烦琐了。
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)
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 类的函数属性
类的数据属性是共享给对象使用的,只要类的数据属性发生变化,对象是能够立马感知到的
对于类的函数属性,类本身是可以使用的.通过类来调用,要严格按照函数的用法来,有几个参数传递几个参数.
3.6.2 对象调用类的函数属性
类里面定义的函数,主要是给对象去用的,而且是绑定给对象使用的,虽然所有的对象都是指向相同的功能,但是绑定到不同的对象,就会变成不同的绑定方法
类的函数属性绑定给对象使用之后,就不再是普通的函数,而是绑定方法.我们使用谁来调用绑定方法,绑定方法就会把水作为第一个参数传递进去.我们在实例化对象的时候,自动触发的也是对象的init绑定方法,所以也不需要传递对象本身.
3.6.3 类函数的形参个数
正是因为有绑定方法的存在,所以我们在类里面定义函数的时候,就至少需要一个形参.即便你的函数里面不需要任何参数.因为我们在通过对象调用这个函数的时候,绑定方法会将对象传递进来,所以需要一个形参来接收绑定方法传进来的对象.
3.6.4 类函数的self
从规范上面来说,第一个参数我们应该写成self,这个self没有任何特殊的地方,仅仅是一个变量名罢了
3.6.5 快捷键
快捷键:alt+j,按一次就会选中一个相同变量名
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)
3.6.7 python一切皆对象
基本数据类型也是类
我们定义好一个字符串,一个列表,字典的时候,其实就是在造对象.我们通过造出来的对象.他们的方法,其实就是对象在调用它们的绑定方法
都是存在self的
x= "aaa",其实背后是触发了一个功能叫做x = str("aaa"),这其实就是类的实例化的过程,x就是通过str实例化出来的一个对象.只不过是python给我们优化了基本数据类型的实例化过程.直接使用"",就可以造出字符串对象.
4.面向对象三大属性
面向对象的三大属性,封装,继承和多态
4.1 封装
封装是面向对象最核心的一个特性,封装就是将一堆东西放到容器里面,然后封起来,也就是前面一直在说的整合
4.1.1 隐藏属性
特点:
- 隐藏的本质,只是一种改名操作
- 对外不对内
- 这个改名操作,只会在类的定义阶段检查类的子代码的时候执行一次,之后定义的__开头的属性都不会改名
我们封装的时候可以将属性隐藏起来,让使用者没有办法直接使用,而不是让使用者不能使用。
我们无论是隐藏数据属性还是函数属性,直接在变量名前面加__就可以实现。
class Test:
__x = 10
def __f1(self):
print("f1")
print(Test.x)
我们只要在属性前面加上__,在类的定义阶段,__开头的属性名字就会被加上前缀_
所以__x =》_Test__x,__f1=>_Test_f1.所以pyhton的这种隐藏机制,并没有做到真正意义上的隐藏,其实就是一种改名操作,我们其实只要知道了类名和属性名,就可以拼接出变形之后的名字,然后对她进行访问
对外不对内的含义就是,我们在类的外部通过__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()
为什么在类的外部不可以访问隐藏属性,在类的内部却是可以访问的呢?
在类的定义阶段,检查类子代码语法的时候,对类内部__开头的名字统一进行了改名操作。在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__)
基于对象的属性隐藏
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()
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)
- 隐藏函数属性
隐藏函数属性可以隔离复杂度,让使用者更加方便的使用设计者设计的类。
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()
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)
装饰器如此使用还是太麻烦了,所以还是得使用语法糖
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)
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__)
在python2里面存在新式类和经典类的区分。
新式类:继承了object类的子类(object类是python的内置类),以及继承了这个类的子子孙孙类。
经典类:没有继承了object类的子类(object类是python的内置类),以及继承了这个类的子子孙孙类。
所以pyhton2里面默认都是经典类,只有继承了object才会变为新式类。
在python3里面默认继承的就是object,python3里面所有的类都是新式类。
如果想让自己的代码可以兼容python2的话,就可以让类继承object.这样我们的代码在python2中运行,它依然还是新式类。
4.2.2 继承的特性
继承的特性是遗传
子类会遗传父类的属性:子类继承父类之后,父类有的属性,子类都可以访问的到。儿子自己有就用自己的,儿子没有的就找老爸要。
继承就是用来解决代码冗余问题的:可以将多个类的共同的部分提取到一个父类里面,这样共同的属性,只是需要存储一份即可。
- 类是为了解决对象有共同属性的问题。
- 继承是为了解决类有共同属性的问题
object是python的内置类,类存储的是数据和功能,这些数据和功能都是python是认为比较有用的。python将其封装到了object类里面,让所有的新式类全部都来继承它。
在python里面所有的新式类,只要往上找,最后一定是继承了object,所以这些新式类,一定都有object提供的数据和功能。比如:__dict__
4.2.3 多继承的优缺点
优点:
- 一个子类可以遗传多个父类的属性
缺点:
- 多继承违背了人的思维习惯
- 多继承会让代码的可读性变差
如果必须要使用多继承时,可以使用MixIns机制(它是一种编程规范,本质上还是多继承),使用它可以使用多继承的优点,也可以避免代码可读性变差的问题。MixIns才是pyhton多继承的正确打开方式。
4.2.4 继承的实现
抽象:就是抽取它们共同的部分
#!/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__
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()
如果想要打印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()
4.2.7 多继承属性查找
4.2.7.1 菱形问题(钻石问题)
对象访问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列表
4.2.7.2 非菱形查找
- 对于菱形继承来说,新式类和经典类的属性查找顺序是不一样的。
- 但是对于非菱形继承来说,新式类的经典类的属性查找顺序都是一样的
- python2里面也没有提供mro这个属性
- 在代码里面要么全是新式类,要么全是经典类,不能一部分是新式类,一部分是经典类
属性查找顺序:
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列表
4.2.7.3 菱形查找
对于非菱形继承,新式类和经典类的属性查找是一样的,但是对于菱形继承.属性的查找方式就不一样了.
- 经典类:深度优先查找,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()
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__方法。
- super是严格依赖继承关系,实现的重用父类的代码
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())
- 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__)
新式类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__)
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())
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)
在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}))
但是在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()
目的:
让父类成为一个规范标准的作用,所有继承我这个抽象基类的子类。必须定义抽象基类实现的规定的方法。
注意:
抽象基类不能直接用于实例化,它的作用只是用来规范标准的。
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__)
5.2 静态方法
类里面的函数有两种用途,一种是绑定给对象使用的,一种是绑定给类使用的。如果函数子代码需要使用到对象,这个函数就需要绑定给对象。如果函数子代码需要使用到类,这个函数就需要绑定给类。
还有一种方法不需要用到类,也不要用到对象。它就是一个独立的功能。就是静态方法,对象和类都是可以调用它的,只是没有自动传参的功能。
在函数上面加上 @staticmethod
6.反射机制
6.1 何为反射机制
python是一门强类型动态解释型语言,反射机制则是动态语言的关键。
动态语言,在程序里面定义变量的时候,不需要指定数据类型。只有执行代码赋值的时候,才识别它的数据类型。
静态语言,在定义变量的时候,就要指定它的数据类型。
只要是动态语言就一定会有反射机制
定义:在程序运行过程中,动态获取对象信息,以及动态调用对象方法的功能。
程序只要开始运行,就是计算机来识别代码,和程序员就没有关系了。这时候程序获取到一个对象,程序如何知道有哪些属性??如果程序不知道对象存在哪些属性,就没有办法调用。所以需要让程序在运行过程中,自己能动态获取到对象属性和方法
我们 可以通过__dict__来判断对象有没有属性,但是有局限性,不是所有的对象都是存在__dict__的。
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))
列表里面的都是字符串类型的属性,所以需要通过字符串反射到真正的属性上面,从而得到属性的值
提供了四个函数通过字符串来操作属性
这里的obj不仅可以是对象,也可以是类,当然后面放置的就要是类的属性了。
反射就是在我们不知道对象是什么的情况下,让我们的程序程序能动态的分析出来我们的对象里面有什么 ,反射是一种动态分析的能力。
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>
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))
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)
class关键字产生的名称空间,比exec执行类的子代码产生的名称空间,多了更多的内置属性。我们创建的名字空间,只存在类子代码里面的名字
- 4.调用元类 ,元类是可以指定的,我们可以自定义元类。如果没有自定义元类,使用的就是内置元类type.调用type将前面散步创建的参数传递进去。
Human不是类,type的返回值才是类。他的类名则是字符串格式的叫做class_name = 'Human'。前面使用class定义的Human,也只是变量名而已。变量名指向的值才是我们的类。
# # 4、调用元类
Human = type(class_name, class_bases, class_dic)
print(Human)
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()
通过调用元类最后得到一个类,这个类就是元类实例化的对象。清楚了这个步骤之后,我们以后就可以针对性的对这些步骤做出定制化的操作.主要是第四步。在调用元类进行实例化类的时候,可以进行定制化的操作,比如将名称空间里面的属性隐藏掉,要求类名不能有下划线。这些都是要求我们自定义元类。
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