面向对象
一、面向对象推导
1.案例:人狗大战
1.1.推导步骤1: 直接手写字典模拟人和狗
human1 = { # 使用字典模拟人 'name': 'jason', 'p_type': '猛男', 'attack_val': 800, 'life_val': 2000 dog1 = { # 使用字典模拟狗 'name': '小白', 'd_type': '阿拉斯加', 'attack_val': 1000, 'life_val': 2000 }
1.2.推导步骤2:由于定义人和狗的字典基本不变 但是在很多地方又需要反复使用 所以封装成函数
def get_human(name, gender, age, h_type, attack_val, life_val): human_obj = { 'name': name, 'gender': gender, 'age': age, 'p_type': h_type, 'attack_val': attack_val, 'life_val': life_val } return human_obj human = get_human('jason', 'male', 28, '猛男', 800, 2000) def get_dog(name, d_type, attack_val, life_val): dog_obj = { 'name': name, 'd_type': d_type, 'attack_val': attack_val, 'life_val': life_val } return dog_obj dog = get_dog('小白狗', '阿拉斯加', 1000, 2000)
1.3.推导步骤3:让人和狗具备攻击的能力 本质其实就是定义两个函数供人和狗调用
def human_attack(human_obj, dog_obj): print('即将被攻击的狗:%s 当前血量:%s' % (dog_obj.get('name'), dog_obj.get('life_val'))) # 先展示当前狗的状态 dog_obj['life_val'] -= human_obj.get('attack_val') # 人锤狗 直接用狗的生命值减去人的攻击力 print('人:%s 锤了 狗:%s 狗掉血:%s 剩余血量:%s' % ( human_obj.get('name'), dog_obj.get('name'), human_obj.get('attack_val'), dog_obj.get('life_val'))) def dog_attack(dog_obj, human_obj): print('即将被攻击的人:%s 当前血量:%s' % (human_obj.get('name'), human_obj.get('life_val'))) # 先展示当前人的状态 human_obj['life_val'] -= dog_obj.get('attack_val') # 狗咬人 直接用人的生命值减去狗的攻击力 print('狗:%s 咬了 人:%s 人掉血:%s 剩余血量:%s' % ( dog_obj.get('name'), human_obj.get('name'), dog_obj.get('attack_val'), human_obj.get('life_val'))) # 调用产生人和狗的函数 human1 = get_human('jason', 'male', 28, '猛男', 800, 2000) human2 = get_human('tom', 'male', 18, '弱男', 100, 1200) dog1 = get_dog('小白狗', '阿拉斯加', 1000, 2000) dog2 = get_dog('小黑狗', '藏獒', 2000, 8000) # 调用攻击彼此的函数 human_attack(human1, dog1) # 即将被攻击的狗:小白狗 当前血量:2000 # 人:jason 锤了 狗:小白狗 狗掉血:800 剩余血量:1200 dog_attack(dog2, human2) # 即将被攻击的人:tom 当前血量:1200 # 狗:小黑狗 咬了 人:tom 人掉血:2000 剩余血量:-800
1.4.推导步骤4: 人和狗攻击乱套(人和狗攻击的函数,可以被任意调用)
人可以调用狗攻击的功能,狗可以调用人攻击的功能
human_attack(dog2, human1) # 即将被攻击的狗:jason 当前血量:2000 # 人:小黑狗 锤了 狗:jason 狗掉血:2000 剩余血量:0 dog_attack(human2, dog1) # 即将被攻击的人:小白狗 当前血量:2000 # 狗:tom 咬了 人:小白狗 人掉血:100 剩余血量:1900
1.5.推导步骤5: 人跟人攻击狗的函数绑定,狗跟狗攻击人的函数绑定
我们定义的函数默认情况下都是可以被任意调用的 但是现在我们想实现定义的函数只有特定的东西才可以调用
def get_human(name, gender, age, h_type, attack_val, life_val): def human_attack(human_obj, dog_obj): """ 专用提供给人调用 攻击狗 :param human_obj: 传人数据(字典) :param dog_obj: 传狗数据(字典) """ print('即将被攻击的狗:%s 当前血量:%s' % (dog_obj.get('name'), dog_obj.get('life_val'))) # 先展示当前狗的状态 dog_obj['life_val'] -= human_obj.get('attack_val') # 人锤狗 直接用狗的生命值减去人的攻击力 print('人:%s 锤了 狗:%s 狗掉血:%s 剩余血量:%s' % ( human_obj.get('name'), dog_obj.get('name'), human_obj.get('attack_val'), dog_obj.get('life_val'))) human_obj = { 'name': name, 'gender': gender, 'age': age, 'h_type': h_type, 'attack_val': attack_val, 'life_val': life_val, 'human_attack': human_attack } return human_obj def get_dog(name, d_type, attack_val, life_val): def dog_attack(dog_obj, human_obj): """ 专用提供给狗调用 攻击人 :param dog_obj: 传狗数据(字典) :param human_obj: 传人数据(字典) """ print('即将被攻击的人:%s 当前血量:%s' % (human_obj.get('name'), human_obj.get('life_val'))) # 先展示当前人的状态 human_obj['life_val'] -= dog_obj.get('attack_val') # 狗咬人 直接用人的生命值减去狗的攻击力 print('狗:%s 咬了 人:%s 人掉血:%s 剩余血量:%s' % ( dog_obj.get('name'), human_obj.get('name'), dog_obj.get('attack_val'), human_obj.get('life_val'))) dog_obj = { 'name': name, 'd_type': d_type, 'attack_val': attack_val, 'life_val': life_val, 'dog_attack': dog_attack } return dog_obj dog1 = get_dog('小白狗', '阿拉斯加', 1000, 2000) human1 = get_human('jason', 'male', 28, '猛男', 800, 2000) human1.get('human_attack')(human1, dog1) # 即将被攻击的狗:小白狗 当前血量:2000 # 人:jason 锤了 狗:小白狗 狗掉血:800 剩余血量:1200
2.推导总结
- 将人的数据跟人的功能绑定到一起,只有人可以调用人的功能
- 将狗的数据跟狗的功能绑定到一起,只有狗可以调用狗的功能
我们将数据与功能绑定到一起的操作起名为:面向对象编程
本质:将特定的数据与特定的功能绑定到一起,将来只能彼此相互使用
二、编程思想
1.面向过程编程
面向过程编程:执行一系列的流程,按照指定的步骤依次执行,最终得到想要的结果,相当于给出一个问题的具体解决方案
2.面向对象编程
核心:对象
概念:对象其实就是一个容器,将数据和功能绑定到了一起,相当于让创造出一些事物之后不用管
本质:将特定的数据与特定的功能绑定到一起,将来只能彼此相互使用
注意:面向过程编程和面向对象编程没有优劣之分,只是使用场景不同,很多时候还是两者混用
三、对象与类
1.对象与类简介
1.1.对象
概念:数据与功能的结合体
作用:用于记录多个对象不同的数据和功能
1.2.类
概念:多个对象相同的数据和功能的结合体
作用:用于记录多个对象相同的数据和功能
注意:类或者对象句点符后面的东西称为属性名或者方法名
2.对象与类的创建
在现实生活中理论是应该先有一个个的个体(对象)再有一个个的群体(类),在编程世界中必须要先有类才能产生对象
2.1.语法
class People: # 公共的数据 # 公共的方法
- class:定义类的关键字
- People:类名
- 类名的命名跟变量名一致,首字母大写(为了更好的区分)
- 类体代码(公共的数据\方法)
- 类体代码在类定义阶段就会执行
2.2.查看名称空间的方法
# 学生类 class Student: # 学生对象公共的数据 school = '清华大学' # 学生对象公共的方法 def choice_course(self): print('正在选课') print(Student.__dict__) # 使用该方法查看名称空间 可以看成是一个字典 print(Student.__dict__['school']) # 使用字典的取值方式获取名字 print(Student.__dict__.get('choice_course')) # 使用字典的取值方式获取名字
在面向对象编程中,获取名称空间中的名字,可以采用句点符和字典的取值两种方式方式
print(Student.school) print(Student.choice_course)
2.3.类实例化产生对象
类名+()
class Student: # 学生对象公共的数据 school = '清华大学' # 学生对象公共的方法 def choice_course(self): print('正在选课') stu1 = Student() stu2 = Student() print(stu1.school) # 清华大学 print(stu2.school) # 清华大学 print(stu1) # <__main__.Student object at 0x000001D923B04A60> print(stu2) # <__main__.Student object at 0x0000025E8A48F130> print(stu1.__dict__, stu2.__dict__) # {} {} print(stu1.choice_course) # <bound method Student.choice_course of <__main__.Student object at 0x000001C85CB9C208>> print(stu2.choice_course) # <bound method Student.choice_course of <__main__.Student object at 0x000001C85CB9C208>>
2.4.修改属性值
class Student: # 学生对象公共的数据 school = '清华大学' Student.school = '北京大学' # 修改school键对应的值 print(stu1.school) # 北京大学 print(stu2.school) # 北京大学
四、对象独有的数据
1.推导思路1: 直接利用__dict__方法朝字典添加键值对
# 学生类 class Student: # 学生对象公共的数据 school = '清华大学' # 学生对象公共的方法 def choice_course(self): print('正在选课') obj1 = Student() obj1.__dict__['name'] = 'jason' # 等价于 obj1.name = 'jason' obj1.__dict__['age'] = 18 # 等价于 obj1.age = 18 obj1.__dict__['gender'] = 'male' # ... print(obj1.name) # jason print(obj1.age) # 18 print(obj1.gender) # male print(obj1.school) # 清华大学
2.推导思路2: 将添加独有数据的代码封装成函数
# 学生类 class Student: # 学生对象公共的数据 school = '清华大学' # 学生对象公共的方法 def choice_course(self): print('正在选课') def init(obj, name, age, gender): obj.__dict__['name'] = name obj.__dict__['age'] = age obj.__dict__['gender'] = gender stu1 = Student() stu2 = Student() init(stu1, 'jason', 18, 'male') # {'name': 'jason', 'age': 18, 'gender': 'male'} init(stu2, 'kevin', 28, 'female') # {'name': 'kevin', 'age': 28, 'gender': 'female'} print(stu1.__dict__) # <__main__.Student object at 0x0000029E0DAB9358> print(stu2.__dict__) # <__main__.Student object at 0x0000029E0DAB92B0>
3.推导思路3: init函数是专用给学生对象创建独有的数据
将函数封装到学生类中,这样只有学生类产生的对象才有资格访问
-
先产生一个空对象
-
自动调用类里面的__init__方法 将产生的空对象当成第一个参数传入
-
将产生的对象返回出去
# 学生类 class Student: def __init__(self, name, age, gender): self.name = name # obj.__dict__['name'] = name self.age = age # obj.__dict__['age'] = age self.gender = gender # obj.__dict__['gender'] = gender # 左右两边的名字虽然一样 但是意思不一样 左边的其实是字典的键 右边的其实是实参 # 学生对象公共的数据 school = '清华大学' # 学生对象公共的方法 def choice_course(self): print('正在选课') stu1 = Student('jason', 18, 'male') print(stu1) # <__main__.Student object at 0x000001FEF89FACF8> stu2 = Student('kevin', 28, 'female') print(stu2) # <__main__.Student object at 0x000001FEF89FAAC8>
五、对象独有的功能
class Person: h_type = '人类' def __init__(self, name): # 让对象拥有独有的数据 self.name = name # 定义在类中的函数 我们称之为方法 def eat(self): # 是多个对象公共的方法 也算多个对象独有的方法 对象来调用就会将对象当做第一个参数传入 print('%s正在干饭' % self.name) def others(self, a, b): print('others哈哈哈') p1 = Person('jason') p1.eat() # eat(p1) # jason正在干饭 p2 = Person('kevin') p2.eat() # eat(p2) # kevin正在干饭 # 如何理解绑定二字 p3 = Person('oscar') Person.eat(p3) # oscar正在干饭 p1 = Person('jason') p1.others(1, 2) # others哈哈哈 Person.others(p1, 1, 2) # others哈哈哈
针对对象独有的方法 我们无法真正实现
- 如果在全局则不是独有的
- 如果在类中则是公共的
python解释器针对上述问题添加了一个非常牛的特性
定义在类中的函数默认是绑定给对象的(相当于是对象独有的方法) 调用时需要传入一个对象
默认情况下,对象调用方法会自动将自身传入一个参数中
六、动静态方法
专门针对在类体代码中编写的函数
1.绑定给对象的方法
-
直接在类体代码中编写即可
-
对象调用会自动将对象当做第一个参数传入
-
类调用则有几个形参就传几个实参
class Student: # 绑定给对象的方法 def run(self): # self用于接收对象 print('丧尸在跟后面,快跑!!!!', self) stu1 = Student() stu1.run() # 丧尸在跟后面,快跑!!!! <__main__.Student object at 0x00000188E3B67438>
2.绑定给类的方法
class Student: @classmethod # 绑定给类的方法 def eat(cls): # cls用于接收类 print('你可真有才', cls) Student.eat() # 类调用会自动将类当做第一个参数传入 eat(Student) 你可真有才 <class '__main__.Student'> stu1.eat() # 对象调用会自动将产生该对象的类当做第一个参数传入 eat(Student) 你可真有才 <class '__main__.Student'>
3.静态方法(普通的函数)
class Student: @staticmethod # 静态方法 def sleep(a, b): # 无论谁来调用都必须按照普普通通的函数传参方式 print('快去睡觉吧') Student.sleep(1, 2) # 快去睡觉吧 stu1.sleep(1, 2) # 快去睡觉吧
七、面向对象三大特性
面向对象三大特性分别是:继承、封装、多态
1.继承
1.1.简介
继承:用来描述类与类之间数据的关系
继承的目的:节省代码编写
1.2.继承的操作
1.2.1.语法结构
Class 父类类名(): pass Class 子类类名(父类类名): pass Class 子类类名(父类类名1, 父类类名2, 父类类名3): pass
被继承的类称之为: 父类或基类或超类
继承类的类称之为: 子类或派生类
- 定义类的时候在类名后加括号
- 括号内填写你需要继承的类名
- 括号内可以填写多个父类,用逗号隔开
- 平时最常用的就是父类和子类
1.2.2.代码示例
class Father: money = 1000000000000000000 def run(self): print('富婆带你飞') class Son(Father): pass print(Son.money) # 1000000000000000000 Son.run(1) # 富婆带你飞 class F1: name = 'from f1' pass class F2: name = 'from f2' pass class F3: name = 'from f3' pass class MyClass(F1, F2, F3): name = 'jason' print(MyClass.name) # jason
1.2.继承的本质
1.2.1.简介
名词 | 描述 |
---|---|
抽象 | 将多个类共同的数据或功能抽取出来形成一个基类 |
继承 | 从上往下白嫖各个基类里面的资源 |
对象 | 数据和功能的结合体 |
类 | 多个对象相同的数据和功能的结合体 |
父类 | 多个类相同数据和功能的结合体 |
类和父类,最主要的功能其实就是节省代码
1.2.2.代码示例
class Person: def __init__(self, name, age, gender): self.name = name self.age = age self.gender = gender class Teacher(Person): # def __init__(self, name, age, gender): # self.name = name # self.age = age # self.gender = gender def teach_course(self): print('老师正在上课') class Student(Teacher): # def __init__(self, name, age, gender): # self.name = name # self.age = age # self.gender = gender def choice_course(self): print('学生正在选课') stu1 = Student('jason', 18, 'male') print(stu1.__dict__) # {'name': 'jason', 'age': 18, 'gender': 'male'}
2.封装
2.1.简介
封装:将数据或者功能隐藏起来(包起来,装起来)
隐藏的目的:给这些隐藏的数据开设特定的接口,让用户使用接口才可以去使用,我们在接口中添加一些额外的操作
在类定义阶段使用双下划线开头的名字都是隐藏的属性,后续类和对象都无法直接获取
在python中不会真正的限制任何代码,访问隐藏的属性需要做变形处理
2.2.代码示例
class Student(object): __school = '清华大学' def __init__(self, name, age): self.__name = name self.__age = age # 专门开设一个访问学生数据的通道(接口) def check_info(self): print(""" 学生姓名:%s 学生年龄:%s """ % (self.__name, self.__age)) # 专门开设一个修改学生数据的通道(接口) def set_info(self,name,age): if len(name) == 0: print('用户名不能为空') return if not isinstance(age,int): print('年龄必须是数字') return self.__name = name self.__age = age stu1 = Student('jason', 18) stu1.set_info('','我很大') # 编写python很多时候都是大家墨守成规的东西,不需要真正的限制 class A: __school = '清华大学' def _choice_course(self): pass
3.多态
3.1.简介
多态:一种事物的多种形态
- 水:液态、气态、固态
- 动物:猫、狗、猪
3.2.代码示例
一种事物有多种形态,相同的功能应该有相同的名字,直接调用相同的功能即可
class Animal: # 同一类事物: 动物 def talk(self): pass class Cat(Animal): # 动物的形态之一: 猫 def talk(self): print('喵喵喵') class Dog(Animal): # 动物的形态之二: 狗 def talk(self): print('汪汪汪') class Pig(Animal): # 动物的形态之三: 猪 def talk(self): print('哼哼哼') # 实例化得到三个对象 cat = Cat() dog = Dog() pig = Pig() cat.talk() # 喵喵喵 dog.talk() # 汪汪汪 pig.talk() # 哼哼哼 def Talk(animal): animal.talk() Talk(cat) # 喵喵喵 Talk(dog) # 汪汪汪 Talk(pig) # 哼哼哼
python也提供了一种强制性的操作(了解即可),自觉遵守
import abc # 指定metaclass属性将类设置为抽象类,抽象类本身只是用来约束子类的,不能被实例化 class Animal(metaclass=abc.ABCMeta): @abc.abstractmethod # 该装饰器限制子类必须定义有一个名为talk的方法 def talk(self): # 抽象方法中无需实现具体的功能 pass class Person(Animal): # 但凡继承Animal的子类都必须遵循Animal规定的标准 def talk(self): pass def run(self): pass obj = Person()
八、名字查找顺序
1.不继承的情况下名字的查找顺序
顺序:对象→类
1.1.先从对象自身查找,没有的话,再去产生该对象的类中查找
class Student: school = '清华大学' def choice_course(self): print('正在选课') stu1 = Student() print(stu1.school) # 对象查找school 自身名称空间没有 所以查找的是类的 清华大学 stu1.school = '北京大学' # 在自身的名称空间中产生了新的school """对象点名字并写了赋值符号和数据值 那么操作的肯定是自己的名称空间""" print(stu1.school) # 北京大学 print(Student.school) # 清华大学
1.2.对象点名字并写了赋值符号和数据值,那么操作的是自己的名称空间
class Student: school = '清华大学' def choice_course(self): print('正在选课') """对象点名字并写了赋值符号和数据值 那么操作的肯定是自己的名称空间""" stu1.school = '北京大学' # 在自身的名称空间中产生了新的school print(stu1.school) # 北京大学 print(Student.school) # 清华大学
2.单继承的情况下的名字的查找顺序
顺序:对象→类→父类
先从对象自身查找,然后是产生该对象的类,然后是一个个父类
class A: name = 'from A' pass class B: # name = 'from B' pass class C: # name = 'from C' pass class MyClass(A, B, C): # name = 'from MyClass' pass obj = MyClass() # obj.name = '困傻了' print(obj.name) # from A
只要涉及到对象查找名字,几乎要回到最开始的位置依次查找
class A1: def func1(self): print('from A1 func1') def func2(self): print('from A1 func2') self.func1() # obj.func1() class MyClass(A1): def func1(self): print('from MyClass func1') obj = MyClass() obj.func2()
3.多继承的情况下名字的查找顺序
3.1.非菱形继承:最后不会归总到一个我们自定义类上
深度优先(每个分支都走到底 再切换)
顺序:A→D→B→E→C→F
class D: # name = 'from D' pass class E: # name = 'from E' pass class F: # name = 'from F' pass class A(D): # name = 'from A' pass class B(E): name = 'from B' pass class C(F): name = 'from C' pass class MyClass: name = 'from MyClass' pass print(MyClass.mro()) print(MyClass.__bases__)
3.2.菱形继承:最后归总到一个我们自定义类上
广度优先(前面几个分支都不会走最后一个类,最后一个分支才会走)
顺序:A→D→B→E→C→F→G
class G: name = 'from G' pass class D(G): # name = 'from D' pass class E(G): # name = 'from E' pass class F(G): # name = 'from F' pass class A(D): # name = 'from A' pass class B(E): # name = 'from B' pass class C(F): # name = 'from C' pass class MyClass(A, B, C): # name = 'from MyClass' pass obj = MyClass print(obj.name)
九、经典类与新式类
1.经典类
不继承object或其子类的类(什么都不继承)
2.新式类
继承了object或其子类的类
3.python2和python3的区别
3.1.在python3中所有的类默认都会继承object
也就意味着python3里面只有新式类
3.2.在python2中有经典类和新式类
由于经典类没有核心的功能,所以python3直接砍掉了
注意:以后在定义类的时候,如果没有想要继承的父类,一般推荐以下写法
class MyClass(object): pass
目的是为了兼容python2
十、派生方法
1.简介
派生:子类中定义类与父类一模一样的方法并且扩展了该功能
class Person: def __init__(self, name, age, gender): self.name = name self.age = age self.gender = gender class Teacher(Person): def __init__(self, name, age, gender, level): # Person.__init__(self,name,age,gender) # 先调用父类的方法 super().__init__(name, age, gender) # super专门用于子类调用父类的方法 self.level = level class MyClass(list): def append(self, value): super().append(value) obj = MyClass() obj.append(111) obj.append(222) obj.append('jason') print(obj)
2.派生方法实战演练(重要)
import datetime import json d = { 't1': datetime.datetime.today(), 't2': datetime.date.today() } res = json.dumps(d) print(res)
上述代码报错,无法正常序列化,这是因为json序列化python数据类型是有限制的,不是所有类型都可以,即将被序列化的数据,内外都必须是一下类型才可以
2.1.解决方式一:手动将不符合数据类型要求的数据转成符合要求的
import datetime import json d = { 't1': str(datetime.datetime.today()), 't2': str(datetime.date.today()) } res = json.dumps(d) print(res) # {"t1": "2022-07-28 15:00:15.153789", "t2": "2022-07-28"}
2.2.解决方式二:派生方法
class JSONEncoder: pass dumps(obj,cls=None): if cls == None: cls = JSONEncoder return cls(...) # JSONEncoder()
查看JSONEncoder源码发现序列化报错是由default方法触发的,我们如果想要避免报错 那么肯定需要对default方法做修改(派生)
import datetime import json d = { 't1': datetime.datetime.today(), 't2': datetime.date.today() # {"t1": "2022-07-28 15:10:25", "t2": "2022-07-28"} } class MyJsonEncode(json.JSONEncoder): def default(self, o): ''' o就是json即将要序列化的数据 ''' if isinstance(o, datetime.datetime): return o.strftime('%Y-%m-%d %H:%M:%S') elif isinstance(o, datetime.date): return o.strftime('%Y-%m-%d') # 如果是可以序列化的类型 那么不做任何处理 直接让它序列化即可 return super().default(o) res = json.dumps(d, cls=MyJsonEncode) print(res) # {"t1": "2022-07-28 15:10:25", "t2": "2022-07-28"}
十一、property伪装属性
1.简介
property伪装属性:将方法伪装成数据,伪装之后可以将func方法伪装成数据(obj.func)
obj.name # 数据只需要点名字 obj.func() # 方法还要加括号
2.代码示例
BMI指数是用来衡量一个人的体重与身高对健康影响的一个指标,计算公式为
体质指数(BMI)=体重(kg)÷身高^2(m)
身高或体重是不断变化的,因而每次想查看BMI值都需要通过计算才能得到,但很明显BMI听起来更像是一个特征而非功能,为此Python专门提供了一个装饰器property,可以将类中的函数“伪装成”对象的数据属性,对象在访问该特殊属性时会触发功能的执行,然后将返回值作为本次访问的结果,例如
class Person: def __init__(self, name, weight, height): self.name = name self.weight = weight self.height = height @property def BMI(self): return self.weight / (self.height ** 2) p = Person('jason', 78, 1.83) res = p.BMI() print(res)
使用property有效地保证了属性访问的一致性。另外property还提供设置和删除属性的功能,如下
class Foo: def __init__(self, val): self.__NAME = val # 将属性隐藏起来 @property def name(self): return self.__NAME @name.setter def name(self, value): if not isinstance(value, str): # 在设定值之前进行类型检查 raise TypeError('%s must be str' % value) self.__NAME = value # 通过类型检查后,将值value存放到真实的位置self.__NAME @name.deleter def name(self): raise PermissionError('Can not delete') obj = Foo('jason') # print(obj.name) # obj.name = 666 # print(obj.name) del obj.name
十二、反射
1.简介
反射:通过字符串来操作对象的数据方法
2.反射的四个主要方法
方法 | 描述 |
---|---|
hasattr() | 判断对象是否含有某个字符串对应的属性 |
getattr() | 获取对象字符串对应的属性 |
setattr() | 根据字符串给对象设置属性 |
delattr() | 根据字符串给对象删除属性 |
class Teacher: def __init__(self,full_name): self.full_name =full_name t=Teacher('jason Lin') # hasattr(object,'name') hasattr(t,'full_name') # 按字符串'full_name'判断有无属性t.full_name # getattr(object, 'name', default=None) getattr(t,'full_name',None) # 等同于t.full_name,不存在该属性则返回默认值None # setattr(x, 'y', v) setattr(t,'age',18) # 等同于t.age=18 # delattr(x, 'y') delattr(t,'age') # 等同于del t.age
3.代码示例
需求:判断用户提供的名字在不在对象可以使用的范围内
class Student: school = '清华大学' def choice_course(self): print('选课') stu = Student()
3.1.方式1:异常处理(过于繁琐)
try: if stu.school: print(f"True{stu.school}") except Exception: print("没有属性")
变量名school(stu.school)与字符串school(stu.‘school’)区别:两者虽然只差了引号,但是本质是完全是不一样的
3.2.方式2:获取用户输入的名字,判断改名字有没有对象
异常处理不好实现,我们需要使用反射
while True: target_name = input('请输入您想要核查的名字>>>:').strip() '''上面的异常更加不好实现 需要用反射''' # print(hasattr(stu, target_name)) # print(getattr(stu, target_name)) if hasattr(stu, target_name): # print(getattr(stu, target_name)) res = getattr(stu, target_name) if callable(res): print('拿到的名字是一个函数', res()) else: print('拿到的名字是一个数据', res) else: print('不好意思 您想要查找的名字 对象没有') print(stu.__dict__) stu.name = 'jason' stu.age = 18 print(stu.__dict__) setattr(stu, 'gender', 'male') setattr(stu, 'hobby', 'read') print(stu.__dict__) del stu.name print(stu.__dict__) delattr(stu, 'age') print(stu.__dict__)
3.3.总结
以后只要在需求中看到了关键字....对象....字符串,那么肯定需要使用反射
4.反射实战演练
class FtpServer: def serve_forever(self): while True: inp = input('input your cmd>>: ').strip() cmd, file = inp.split() if hasattr(self, cmd): # 根据用户输入的cmd,判断对象self有无对应的方法属性 func = getattr(self, cmd) # 根据字符串cmd,获取对象self对应的方法属性 func(file) def get(self, file): print('Downloading %s...' % file) def put(self, file): print('Uploading %s...' % file) obj = FtpServer() obj.serve_forever()
十三、面向对象的魔法方法
1.简介
魔法方法其实就是类中定义的双下方法,这些方法都是到达某个条件自动触发,无需调用
eg:__ init__方法在给对象设置独有数据的时候自动触发(实例化)
2.常用魔法方法
魔法方法 | 功能、触发条件 |
---|---|
__init__ |
实例化对象的时候自动触发 |
__str__ |
对象被执行打印操作的时候会自动触发 |
__call__ |
对象加括号调用时 自动触发该方法 |
__getattr__ |
当对象获取一个不存在的属性名自动触发 |
__setattr__ |
当对象操作属性值的时候自动触发 |
__del__ |
当对象在被删除(主动 被动)的时候自动触发 |
__getattribute__ |
无论这个属性存不存在,对象获取属性的时候自动触发 |
__enter__ |
对象被with 语法执行上下文操作的时候自动触发 |
__exit__ |
对象被with语法执行并运行完with子代码后 自动触发 |
__new__ |
产生对象时自动触发,在双下init之前 |
3.代码示例
3.1.实例化对象时自动触发
class MyClass(object): def __init__(self, name): """实例化对象的时候自动触发""" # print('__init__方法') # pass self.name = name
3.2.对象被执行打印操作时自动触发
该方法必须返回一个字符串,返回什么字符串打印对象之后就展示什么字符串
class MyClass(object): # print('__str__方法') # print('这是类:%s 产生的一个对象') # return '对象:%s'%self return '对象:%s'%self.name
3.3.对象加括号自动触发
class MyClass(object): def __call__(self, *args, **kwargs): """对象加括号调用 自动触发该方法""" print('__call__方法') print(args) print(kwargs)
3.4.当对象获取一个不存在的属性名时自动触发
该方法返回什么,对象获取不存在的属性名就会得到什么,形参item就是对象想要获取的不存在的属性名
class MyClass(object): ef __getattr__(self, item): print('__getattr__', item) return '您想要获取的属性名:%s不存在'%item
3.5.对象操作属性值的时候自动触发
对象.属性名=属性值
class MyClass(object): def __setattr__(self, key, value): # print("__setattr__") # print(key) # print(value) super().__setattr__(key, value)
3.6.对象在被删除时自动触发
对象在被主动删除或被动删除时自动触发
class MyClass(object): def __del__(self): # print('__del__') pass
3.7.对象获取属性时自动触发
无论这个属性存不存在,对象获取属性时自动触发,当类中既有__ getattr__ 又有__ getattribute__的时候 只会走后者
class MyClass(object): def __getattribute__(self, item): # print('__getattribute__') # return super(MyClass, self).__getattribute__(item) 复杂写法 return super().__getattribute__(item) # 简便写法
3.8.对象被with语法执行时自动触发
该方法返回什么 as关键字后面的变量名就能得到什么
class MyClass(object): def __enter__(self): print('__enter__')
3.9.对象被with语法执行并运行完with子代码之后自动触发
class MyClass(object): def __exit__(self, exc_type, exc_val, exc_tb): print('__exit__')
4.魔法方法实践
"""补全以下代码 执行之后不报错""" class Context: def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): pass def do_something(self): pass with Context() as f: f.do_something()
十四、元类
Python中,一切皆对象,我们定义的数字、字符串、函数、列表等都是对象,对象是类(class)的是实例,而类(class)其实也是对象,是type的实例。这个type就是Python中的元类(metaclass)。所谓元类就是用于创建所有类型的类,Python中的所有新式类以及Python3中的所有类都是type元类的实例
1.元类简介
1.1.推导过程
基础阶段我们使用type来查找数据的数据类型
# s1 = '哈哈哈 今天下午终于可以敲代码了!!!' # l2 = [60, 80, 100, 120, 150, 200] # d = {'name': '死给我看', 'age': 18} # print(type(s1)) # <class 'str'> # print(type(l2)) # <class 'list'> # print(type(d)) # <class 'dict'>
学了面向对象之后,发现查看的不是数据类型而是数据所属的类
我们定义的数据类型,其实本质还是通过各个类产生了对象
class str: pass h = 'hello' str('hello')
我们也可以理解为type用于查看产生当前对象的类是谁
class MyClass: pass obj = MyClass() print(type(obj)) # 查看产生对象obj的类:<class '__main__.MyClass'> print(type(MyClass)) # 查看产生对象MyClass的类:<class 'type'>
1.2.结论
自定义的类都是由type类产生的,我们将产生类的类称之为元类
2.创建类的两种方式
学习元类其实就是掌握了类的产生过程 我们就可以在类的产生过程中高度定制化类的行为
- 类名必须首字母大写
上述需求就需要使用元类来控制类的产生过程,在过程中校验
2.1.class关键字
class aaa: pass print(aaa)
2.2.利用元类type
type(类名,类的父类,类的名称
3.元类的基本应用
3.1.只有继承了type的类才可以称之为是元类
class MyMetaClass(type): pass
3.2.代码示例
class MyMetaClass(type): def __init__(self,what, bases=None, dict=None): # print('别晕') # print('what', what) 类名 # print('bases', bases) 类的父类 # print('dict', dict) 类的名称空间 if not what.istitle(): # print('首字母必须大写 你会不会写python 面向对象学过吗 lowB') raise Exception('首字母必须大写 你会不会写python 面向对象学过吗 lowB') super().__init__(what, bases, dict)
3.3.切换产生类的元类不能使用继承,必须使用关键字metaclass声明
class aaa(metaclass=MyMetaClass): pass
4.元类进阶
元类不单单可以控制类的产生过程,也可以控制对象
- 如果我们想高度定制对象的产生过程,可以操作元类里面的__ call__
- 如果我们想高度定制类的产生过程,可以操作元类里面的__ init__
4.1.对象加括号执行产生该对象类里面的__ call__
class MyMetaClass(type): def __call__(self, *args, **kwargs): print('__call__') if args: raise Exception('必须用关键字参数传参') super().__call__(*args, **kwargs)
4.2.类加括号执行产生该类的元类里面的双下__ init__
class MyClass(metaclass=MyMetaClass): def __init__(self, name, age): self.name = name self.age = age print('__init__') # 需求:实例化对象 所有的参数都必须采用关键字参数的形式 obj = MyClass('jason', 18) # obj = MyClass(name='jason', age=18)
5.元类的双下new方法
-
__ new __方法专门用于产生空对象
-
__ init__方法专门用于给对象添加属性
-
类产生对象的步骤:
- 产生一个空对象
- 自动触发__ init__方法实例化对象
- 返回实例化好的对象
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)