内容概要
面向对象
面向对象前戏 对象与类的创建 对象独有的数据 对象独有的功能 动静态方法 面向对象三大特性之继承 面向对象三大特性之封装 property伪装属性 面向对象三大特性之多态 面向对象之反射 面向对象魔法方法 元类简介
人狗大战 # 1.推导步骤1:想办法描述出人和狗>>>:字典 # p1 = { # 'name': 'jason', # 't_type': '猛男', # 'attack_val': 800, # 'life_val': 2000 # } # d1 = { # 'name': '小黑', # 't_type': '田园犬', # 'attack_val': 200, # 'life_val': 8000 # } # 2.推导步骤2:将表示人和狗的代码封装成函数 避免重复编写>>>:函数 # def get_person(name, p_type, attack_val, life_val): # '''专门产生人''' # person = { # 'name': name, # 'p_type': p_type, # 'attack_val': attack_val, # 'life_val': life_val # } # return person # def get_dog(name, d_type, attack_val, life_val): # '''专门产生狗''' # dog = { # 'name': name, # 'd_type': d_type, # 'attack_val': attack_val, # 'life_val': life_val # } # return dog # 3.定义人和狗彼此攻击的功能 # def person_attack(person_dict, dog_dict): # '''人打狗''' # print('当前狗的基本信息:%s' % dog_dict) # dog_dict['life_val'] -= person_dict.get('attack_val') # 简单模拟 # print( # f"人:{person_dict.get('name')} 打了狗:{dog_dict.get('name')} 掉血:{person_dict.get('attack_val')} 狗剩余血量为:{dog_dict.get('life_val')}") # def dog_attack(dog_dict, person_dict): # '''狗咬人''' # print('当前人的基本信息:%s' % person_dict) # person_dict['life_val'] -= dog_dict.get('attack_val') # 简单模拟 # print( # f"狗:{dog_dict.get('name')} 咬了人:{person_dict.get('name')} 掉血:{dog_dict.get('attack_val')} 人剩余血量为:{person_dict.get('life_val')}") # 4.人狗大战 # p1 = get_person('jason', '猛男', 8000, 10000) # p2 = get_person('kevin', '菜鸡', 100, 1000) # d1 = get_dog('小黑', '恶霸犬', 8888, 100000) # d2 = get_dog('小黄', '泰迪犬', 100, 500) # person_attack(p1, d1) # 人打狗 # print(d1) # dog_attack(d2, p2) # 狗咬人 # print(p2) # 5.核心矛盾(乱套) # person_attack(d1, p1) # 狗调用了人的攻击方法 # dog_attack(p1, d2) # 人调用了狗的攻击方法 # 6.数据与功能相互绑定>>>:人打狗的动作只能人来调 ... def get_person(name, p_type, attack_val, life_val): '''专门产生人''' # 人的功能 def person_attack(person_dict, dog_dict): '''人打狗''' print('当前狗的基本信息:%s' % dog_dict) dog_dict['life_val'] -= person_dict.get('attack_val') # 简单模拟 print( f"人:{person_dict.get('name')} 打了狗:{dog_dict.get('name')} 掉血:{person_dict.get('attack_val')} 狗剩余血量为:{dog_dict.get('life_val')}") # 人的数据 person = { 'name': name, 'p_type': p_type, 'attack_val': attack_val, 'life_val': life_val, 'attack': person_attack } return person def get_dog(name, d_type, attack_val, life_val): '''专门产生狗''' # 狗的功能 def dog_attack(dog_dict, person_dict): '''狗咬人''' print('当前人的基本信息:%s' % person_dict) person_dict['life_val'] -= dog_dict.get('attack_val') # 简单模拟 print( f"狗:{dog_dict.get('name')} 咬了人:{person_dict.get('name')} 掉血:{dog_dict.get('attack_val')} 人剩余血量为:{person_dict.get('life_val')}") # 狗的数据 dog = { 'name': name, 'd_type': d_type, 'attack_val': attack_val, 'life_val': life_val, 'attack': dog_attack } return dog p1 = get_person('jason', '猛男', 8000, 10000) p2 = get_person('kevin', '菜鸡', 100, 1000) d1 = get_dog('小黑', '恶霸犬', 8888, 100000) d2 = get_dog('小黄', '泰迪犬', 100, 500) p1.get('attack')(p1, d2) d2.get('attack')(d2, p2) """ 我们的目的是想让数据和功能有关系 """ 总结 面向对象其实就是上述推导过程中的思路>>>:将数据和功能绑定到一起
将人的数据跟人的功能绑定到一起 只有人可以调用人的功能 将狗的数据跟狗的功能绑定到一起 只有狗可以调用狗的功能 我们将数据与功能绑定到一起的操作起名为:'面向对象编程' 本质:将特定的数据与特定的功能绑定到一起 将来只能彼此相互使用
面向过程编程 截止昨天 我们所编写的代码都是面向过程编程 过程其实就是流程 面向过程编程其实就是在执行一系列的流程 eg: 注册功能 登录功能 冻结账户 ... 就是按照指定的步骤依次执行 最终就可以得到想要的结果 面向对象编程 核心就是'对象'二字 对象其实就是一个容器 里面将数据和功能绑定到了一起 eg: 游戏人物 ... 只负责创造出该人物以及该人物具备的功能 至于后续战绩如何无人知晓 """ 面向过程编程相当于让你给出一个问题的具体解决方案 面向对象编程相当于让你创造出一些事物之后不用你管 """ 上述两种编程思想没有优劣之分 仅仅是使用场景不同 甚至很多时候是两者混合使用
对象:数据与功能的结合体 类:多个对象相同的数据和功能的结合体 """ 类比学习法 一个人 对象 多个人 人类 一条狗 对象 多条狗 犬类 """ 类主要用于记录多个对象相同的数据和功能 对象则用于记录多个对象不同的数据和功能 ps:在面向对象编程中 类仅仅是用于节省代码 对象才是核心
在现实生活中理论是应该先有一个个的个体(对象)再有一个个的群体(类) 在编程世界中必须要先有类才能产生对象 面向对象编程本质就是将数据和功能绑定到一起 但是为了突出面向对象编程的形式 python特地开发了一套语法专门用于面向对象编程的操作 创建类的完整语法 class People: # 学生对象公共的数据 # 学生对象公共的方法 1.class是定义类的关键字 2.People是类的名字 类名的命名跟变量名一致 并且推荐首字母大写(为了更好的区分) 3.类体代码 公共的数据\公共的方法 ps:类体代码在类定义阶段就会执行!!! # 查看名称空间的方法 # print(Student.__dict__) # 使用该方法查看名称空间 可以看成是一个字典 # print(Student.__dict__['school']) # 使用字典的取值方式获取名字 # print(Student.__dict__.get('choice_course')) # 使用字典的取值方式获取名字 '''在面向对象编程中 想要获取名称空间中的名字 可以采用句点符''' # print(Student.school) # print(Student.choice_course) '''类实例化产生对象>>>: 类名加括号''' 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.school) # print(stu2.school) # print(stu1.choice_course) # print(stu2.choice_course) Student.school = '北京大学' # 修改school键对应的值 print(stu1.school) print(stu2.school) """ 我们习惯将类或者对象句点符后面的东西称为属性名或者方法名 """
# 学生类 # class Student: # # 学生对象公共的数据 # school = '清华大学' # # # 学生对象公共的方法 # def choice_course(self): # print('正在选课') '''推导思路1: 直接利用__dict__方法朝字典添加键值对''' # obj1 = Student() # obj1.__dict__['name'] = 'jason' # 等价于 obj1.name = 'jason' # obj1.__dict__['age'] = 18 # 等价于 obj1.age = 18 # obj1.__dict__['gender'] = 'male' # ... # print(obj1.name) # print(obj1.age) # print(obj1.gender) # print(obj1.school) # obj2 = Student() # obj2.__dict__['name'] = 'kevin' # obj2.__dict__['age'] = 28 # obj2.__dict__['gender'] = 'female' # print(obj2.name) # print(obj2.age) # print(obj2.gender) # print(obj2.school) '''推导思路2: 将添加独有数据的代码封装成函数''' # 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') # init(stu2, 'kevin',28,'female') # print(stu1.__dict__) # print(stu2.__dict__) '''推导思路3: init函数是专用给学生对象创建独有的数据 其他对象不能调用>>>:面向对象思想 将数据和功能整合到一起 将函数封装到学生类中 这样只有学生类产生的对象才有资格访问 ''' class Student: """ 1.先产生一个空对象 2.自动调用类里面的__init__方法 将产生的空对象当成第一个参数传入 3.将产生的对象返回出去 """ 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() # print(stu1.__dict__) # Student.init(stu1, 'jason', 18, 'male') # print(stu1.__dict__) # print(stu1.name) stu1 = Student('jason', 18, 'male') print(stu1) stu2 = Student('kevin', 28, 'female') print(stu2)
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哈哈哈') ''' 针对对象独有的方法 我们无法真正实现 1.如果在全局则不是独有的 2.如果在类中则是公共的 python解释器针对上述问题添加了一个非常牛的特性 定义在类中的函数默认是绑定给对象的(相当于是对象独有的方法) ''' # p1 = Person('jason') # p1.eat() # eat(p1) # p2 = Person('kevin') # p2.eat() # eat(p2) # 如何理解绑定二字 # p3 = Person('oscar') # Person.eat(p3) p1 = Person('jason') p1.others(1, 2) Person.others(p1,1,2)
专门针对在类体代码中编写的函数 1.绑定给对象的方法 直接在类体代码中编写即可 对象调用会自动将对象当做第一个参数传入 类调用则有几个形参就传几个实参 2.绑定给类的方法 3.静态方法(普普通通的函数) class Student: school = '清华大学' # 绑定给对象的方法 def run(self): # self用于接收对象 print('老六赶紧跑!!!', self) @classmethod # 绑定给类的方法 def eat(cls): # cls用于接收类 print('老六你可真有才', cls) @staticmethod # 静态方法 def sleep(a, b): # 无论谁来调用都必须按照普普通通的函数传参方式 print('老六快去睡觉吧') stu1 = Student() # 调用绑定给类的方法 # Student.eat() # 类调用会自动将类当做第一个参数传入 eat(Student) # stu1.eat() # 对象调用会自动将产生该对象的类当做第一个参数传入 eat(Student) # 调用静态方法 # Student.sleep(1,2) stu1.sleep(1, 2)
""" 面向对象三大特性分别是 继承、封装、多态 """ 1.继承的含义 在现实生活中继承其实就是用来描述人与人之间资源的关系 eg:儿子继承父亲的财产(拥有了父亲所有的资源) 在编程世界里继承其实就是用来描述类与类之间数据的关系 eg:类A继承类B(拥有了类B里面所有的数据和功能) 2.继承的目的 现实生活中继承就是想占有别人的财产 eg:亲身父亲 干爹 干妈 富婆 编程世界里继承就是为了节省代码编写 eg:可以继承一个类 也可以继承多个类 3.继承的操作 class 类名(类名): pass 1.定义类的时候在类名后加括号 2.括号内填写你需要继承的类名 3.括号内可以填写多个父类 逗号隔开即可 """ 我们将被继承的类称之为: 父类或基类或超类 我们将继承类的类称之为: 子类或派生类 ps:平时最常用的就是父类和子类 """ class MyClass(F1,F2,F3): pass ps:目前掌握从左到右查找每个父类中的属性即可
抽象:将多个类共同的数据或功能抽取出来形成一个基类 继承:从上往下白嫖各个基类里面的资源 """ 对象:数据和功能的结合体 类:多个对象相同的数据和功能的结合体 父类:多个类相同的数据和功能的结合体 ps:类和父类最主要的功能其实就是节省代码 """ 一定要掌握继承的本质 这样以后你才会在代码中自己定义出子类父类
1.不继承的情况下名字的查找顺序 先从对象自身查找 没有的话 再去产生该对象的类中查找 class Student: school = '清华大学' def choice_course(self): print('正在选课') stu1 = Student() print(stu1.school) # 对象查找school 自身名称空间没有 所以查找的是类的 清华大学 stu1.school = '北京大学' # 在自身的名称空间中产生了新的school """对象点名字并写了赋值符号和数据值 那么操作的肯定是自己的名称空间""" print(stu1.school) # 北京大学 print(Student.school) # 清华大学 对象 >>> 类 2.单继承的情况下名字的查找顺序 先从对象自身查找 然后是产生该对象的类 然后是一个个父类 class A: # name = 'from A' pass class B(A): # name = 'from B' pass class C(B): # name = 'from C' pass class MyClass(C): # name = 'from MyClass' pass obj = MyClass() # obj.name = '我很困!!!' print(obj.name) 对象 >>> 类 >>> 父类... 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.多继承的情况下名字的查找顺序 非菱形继承(最后不会归总到一个我们自定义类上) 深度优先(每个分支都走到底 再切换) 菱形继承(最后归总到一个我们自定义类上) 广度优先(前面几个分支都不会走最后一个类 最后一个分支才会走) ps:结合群内截图理解即可 也可以使用类点mro()方法查看该类产生的对象名字的查找顺序 ###################################################### 主要涉及到对象查找名字 那么几乎都是 对象自身 类 父类 ######################################################
经典类 不继承object或其子类的类(什么都不继承) 新式类 继承了object或其子类的类 """ 在python3中所有的类默认都会继承object 也就意味着python3里面只有新式类 在python2中有经典类和新式类 由于经典类没有核心的功能 所以到了python3直接砍掉了 以后我们在定义类的时候 如果没有想要继承的父类 一般推荐以下写法 class MyClass(object): pass 目的是为了兼容python2 """ 以后写代码针对object无需关心 知道它的存在即可
子类中定义类与父类一模一样的方法并且扩展了该功能>>>:派生
封装其实就是将数据或者功能隐藏起来(包起来 装起来) 隐藏的目的不是让用户无法使用 而是给这些隐藏的数据开设特定的接口 让用户使用接口才可以去使用 我们在接口中添加一些额外的操作 1.在类定义阶段使用双下划线开头的名字 都是隐藏的属性 后续类和对象都无法直接获取 2.在python中不会真正的限制任何代码 隐藏的属性如果真的需要访问 也可以 只不过需要做变形处理 __变量名 _类名__变量名 ps:既然隐藏了 就不改使用变形之后的名字去访问 这样就失去了隐藏的意义 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 """
可以简单的理解为 将方法伪装成数据 obj.name # 数据只需要点名字 obj.func() # 方法至少还要加括号 伪装之后可以将func方法伪装成数据 obj.func 扩展了解 体质指数(BMI)=体重(kg)÷身高^2(m) # 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) # p1 = Person('jason', 78, 1.83) # res = p1.BMI() # print(res) # p2 = Person('悍匪', 72, 1.73) # res = p2.BMI() # print(res) """BMI虽然需要计算获得 但是更像是人的数据""" # p1 = Person('jason', 78, 1.83) # print(p1.BMI) # print(p1.name) 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
多态:一种事物的多种形态 水:液态 气态 固态 动物:人 狗 猫 猪 class Animal(object): def spark(self): pass class Cat(Animal): def spark(self): print('喵喵喵') class Dog(Animal): def spark(self): print('汪汪汪') class Pig(Animal): def spark(self): print('哼哼哼') # c1 = Cat() # d1 = Dog() # p1 = Pig() # c1.miao() # d1.wang() # p1.heng() """ 一种事物有多种形态 但是相同的功能应该有相同的名字 这样的话 以后我无论拿到哪个具体的动物 都不需要管到底是谁 直接调用相同的功能即可 无论你是鸡 鸭 猫 狗 猪 只要你想叫 你就调固定的叫的功能 """ # c1.spark() # d1.spark() # p1.spark() """ 其实上述多态的概念 我们很早之前就已经解除过 """ # l1 = [11, 22, 33, 44] # d1 = {'name': 'jason', 'pwd': 123, 'hobby': 'raed'} # t1 = (11, 22, 33, 44) # print(len(l1)) # print(len(d1)) # print(len(t1)) """ 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() """ 鸭子类型 只要你长得像鸭子 走路像鸭子 说话像鸭子 那么你就是鸭子 """ # class Teacher: # def run(self):pass # def eat(self):pass # class Student: # def run(self):pass # def eat(self):pass """ 操作系统 linux系统:一切皆文件 只要你能读数据 能写数据 那么你就是文件 内存 硬盘 class Txt: #Txt类有两个与文件类型同名的方法,即read和write def read(self): pass def write(self): pass class Disk: #Disk类也有两个与文件类型同名的方法:read和write def read(self): pass def write(self): pass class Memory: #Memory类也有两个与文件类型同名的方法:read和write def read(self): pass def write(self): pass python:一切皆对象 只要你有数据 有功能 那么你就是对象 文件名 文件对象 模块名 模块对象 """
反射:通过字符串来操作对象的数据或方法 反射主要就四个方法 hasattr():判断对象是否含有某个字符串对应的属性 getattr():获取对象字符串对应的属性 setattr():根据字符串给对象设置属性 delattr():根据字符串给对象删除属性 class Student: school = '清华大学' def choice_course(self): print('选课') stu = Student() # 需求:判断用户提供的名字在不在对象可以使用的范围内 # 方式1:利用异常处理(过于繁琐) # try: # if stu.school: # print(f"True{stu.school}") # except Exception: # print("没有属性") """ 变量名school 与字符串school 区别大不大 stu.school stu.'school' 两者虽然只差了引号 但是本质是完全不一样的 """ # 方式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__) """ 以后只要在需求中看到了关键字 ....对象....字符串 那么肯定需要使用反射 """
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()
魔法方法其实就是类中定义的双下方法 之所以会叫魔法方法原因是这些方法都是到达某个条件自动触发 无需调用 eg: __init__方法在给对象设置独有数据的时候自动触发(实例化) 下列讲解的魔法方法都必须明确的知道的触发的条件 class MyClass(object): def __init__(self, name): """实例化对象的时候自动触发""" # print('__init__方法') # pass self.name = name def __str__(self): """ 对象被执行打印操作的时候会自动触发 该方法必须返回一个字符串 返回什么字符串打印对象之后就展示什么字符串 """ # print('__str__方法') # print('这是类:%s 产生的一个对象') # return '对象:%s'%self return '对象:%s'%self.name def __call__(self, *args, **kwargs): """对象加括号调用 自动触发该方法""" print('__call__方法') print(args) print(kwargs) def __getattr__(self, item): """当对象获取一个不存在的属性名 自动触发 该方法返回什么 对象获取不存在的属性名就会得到什么 形参item就是对象想要获取的不存在的属性名 """ print('__getattr__', item) return '您想要获取的属性名:%s不存在'%item def __setattr__(self, key, value): """对象操作属性值的时候自动触发>>>: 对象.属性名=属性值""" # print("__setattr__") # print(key) # print(value) super().__setattr__(key, value) def __del__(self): """对象在被删除(主动 被动)的时候自动触发""" # print('__del__') pass def __getattribute__(self, item): """对象获取属性的时候自动触发 无论这个属性存不存在 当类中既有__getattr__又有__getattribute__的时候 只会走后者 """ # print('__getattribute__') # return super(MyClass, self).__getattribute__(item) 复杂写法 return super().__getattribute__(item) # 简便写法 def __enter__(self): """对象被with语法执行的时候自动触发 该方法返回什么 as关键字后面的变量名就能得到什么""" print('__enter__') def __exit__(self, exc_type, exc_val, exc_tb): """对象被with语法执行并运行完with子代码之后 自动触发""" print('__exit__')
"""补全以下代码 执行之后不报错""" 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()
# 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'> """ 基础阶段我们使用type来查找数据的数据类型 但是学了面向对象之后 发现查看的不是数据类型 而是数据所属的类 我们定义的数据类型 其实本质还是通过各个类产生了对象 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'> """ 通过上述推导 得出结论 自定义的类都是由type类产生的 我们将产生类的类称之为 '元类' """
1.class关键字 class MyClass: pass 2.利用元类type type(类名,类的父类,类的名称空间) """ 学习元类其实就是掌握了类的产生过程 我们就可以在类的产生过程中高度定制化类的行为 eg: 类名必须首字母大写 上述需求就需要使用元类来控制类的产生过程 在过程中校验 """
class MyMetaClass(type): pass """只有继承了type的类才可以称之为是元类""" class MyClass(metaclass=MyMetaClass): pass """如果想要切换产生类的元类不能使用继承 必须使用关键字metaclass声明""" ''' 思考 类中的__init__用于实例化对象 元类中__init__用于实例化类 ''' 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) """只有继承了type的类才可以称之为是元类""" # class Myclass(metaclass=MyMetaClass): # pass """如果想要切换产生类的元类不能使用继承 必须使用关键字metaclass声明""" class aaa(metaclass=MyMetaClass): pass
"""元类不单单可以控制类的产生过程 其实也可以控制对象的!!!""" 1.对象加括号执行产生该对象类里面的双下call 2.类加括号执行产生该类的元类里面的双下call class MyMetaClass(type): def __call__(self, *args, **kwargs): print('__call__') if args: raise Exception('必须用关键字参数传参') super().__call__(*args, **kwargs) 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) 总结 """ 如果我们想高度定制对象的产生过程 可以操作元类里面的__call__ 如果我们想高度定制类的产生过程 可以操作元类里面的__init__ """