面向对象
面向对象引入
以小游戏人狗大战为例
步骤一:定义人与狗的数据
person = { 'name': '钢弹', 'age': 18, 'gender': 'male', 'p_type': '猛男', 'attack_val': 800, 'life_val': 99999 dog = { 'name': '龙傲天', 'd_type': '马犬', 'attack_val': 500, 'life_val': 100000 } """ 通过字典的形式,我们可以定义出所需要的人与狗的数据,但是如果我们这个游戏是回合制游戏,需要多个角色数据时呢 我们需要多次复制同样的数据,来满足游戏对角色的需求,那么以我们目前所学的知识,我们可以封装函数来完成 """
步骤二:封装函数产生数据
2.1 产生数据的函数
def create_person(name, age, gender, p_type, attack_val, life_val): person_dict = { 'name': name, 'age': age, 'gender': gender, 'p_type': p_type, 'attack_val': attack_val, 'life_val': life_val } return person_dict def create_dog(name, d_type, attack_val, life_val): dog_dict = { 'name': name, 'd_type': d_type, 'attack_val': attack_val, 'life_val': life_val } return dog_dict p1 = create_person('jason', 18, 'male', '猛男', 8000, 99999999) p2 = create_person('kevin', 28, 'female', '淑女', 100, 800) d1 = create_dog('小黑', '恶霸', 800, 900000) d2 = create_dog('小白', '泰迪', 100, 800000) print(p1, p2) print(d1, d2) """ 这样我们通过函数就完成了多个数据的产生,那么游戏就可以开始进行了,但在此之前,我们需要先定好游戏的规则,以代码的形式完成游戏流程 """
2.2 调用数据完成游戏流程的函数
def person_attack(person_dict, dog_dict): # 人攻击狗的函数 print(f"人:{person_dict.get('name')}准备揍狗:{dog_dict.get('name')}") dog_dict['life_val'] -= person_dict.get('attack_val') print(f"人揍了狗一拳 狗掉血:{person_dict.get('attack_val')} 狗剩余血量:{dog_dict.get('life_val')}") def dog_attack(dog_dict, person_dict): # 狗攻击人的函数 print(f"狗:{dog_dict.get('name')}准备咬人:{person_dict.get('name')}") person_dict['life_val'] -= dog_dict.get('attack_val') print(f"狗咬了人一口 人掉血:{dog_dict.get('attack_val')} 人剩余血量:{person_dict.get('life_val')}") person_attack(p1, d1) # 运行游戏 dog_attack(d2, p2) """ 人:jason准备揍狗:小黑 人揍了狗一拳 狗掉血:8000 狗剩余血量:892000 狗:小白准备咬人:kevin 狗咬了人一口 人掉血:100 人剩余血量:700 这么看来我们的游戏流程已经完成了但是... """ person_attack(d1, p1) dog_attack(p1, d2) """ 人:小黑准备揍狗:jason 人揍了狗一拳 狗掉血:800 狗剩余血量:99999199 狗:jason准备咬人:小白 狗咬了人一口 人掉血:8000 人剩余血量:792000 当我们反过来输入信息后,狗变成了人,人变成了狗,数据发生了混乱 函数的调用并不明确那个函数是对应哪些数据才可以调用,这时候就要步入新的阶段 >>>:面向对象 """
面向对象核心思路
那么我们如何来实现人只可以使用人的攻击函数,狗只可以使用狗的攻击函数呢?
——数据与函数绑定
def get_person(name, age, gender, p_type, attack_val, life_val): # 产生人的函数(功能) def person_attack(person_dict, dog_dict): print(f"人:{person_dict.get('name')}准备揍狗:{dog_dict.get('name')}") dog_dict['life_val'] -= person_dict.get('attack_val') print(f"人揍了狗一拳 狗掉血:{person_dict.get('attack_val')} 狗剩余血量:{dog_dict.get('life_val')}") # 表示人的信息(数据) person_dict = { 'name': name, 'age': age, 'gender': gender, 'p_type': p_type, 'attack_val': attack_val, 'life_val': life_val, 'person_attack': person_attack } return person_dict def get_dog(name, d_type, attack_val, life_val): def dog_attack(dog_dict, person_dict): print(f"狗:{dog_dict.get('name')}准备咬人:{person_dict.get('name')}") person_dict['life_val'] -= dog_dict.get('attack_val') print(f"狗咬了人一口 人掉血:{dog_dict.get('attack_val')} 人剩余血量:{person_dict.get('life_val')}") dog_dict = { 'name': name, 'd_type': d_type, 'attack_val': attack_val, 'life_val': life_val, 'dog_attack': dog_attack } return dog_dict person1 = get_person('jason', 18, 'male', '猛男', 8000, 99999999) dog1 = get_dog('小黑', '恶霸', 800, 900000) person1.get('person_attack')(person1, dog1) """ 人:jason准备揍狗:小黑 人揍了狗一拳 狗掉血:8000 狗剩余血量:892000 我们通过第一层函数,实现了角色信息的生成,由于函数内部的名称只能在内部调用,所以我们将人与狗各自的攻击函数放入到第一层函数中,并把函数名作为键值对的值存放在用户的字典中,那么当我们得到返回的字典时,就只需要将对应的攻击函数取出来即可实现函数调用,同时我们也实现了功能与数据的绑定,这也就是面向对象的核心 """
编程思想
所谓的编程思想,或者说编程方式,只有两种——面向过程与面向对象
面向过程,顾名思义,更注重过程,放到编程来讲,面向过程是当我们看到需求时,要将需求拆分成各个组成部分,就以我们之前写的ATM为例,当我们需要登录时,我们需要先去校验用户是否存在,然后验证用户输入的密码是否正确,需要一步步完成各个组成部分,将每部分功能都写出来,主要的侧重点在于拆分与完成各组成部分的过程,并且随着每个部分的深入,问题的解决越来越简单
而面向对象,则需要引用到python中一切皆对象的概念,对象即容器,可以存放数据与方法的容器,我们可以理解为容器中既有数据值,又有函数,就像我们常用的列表,字典,他们既有数据值,也有内置方法,这就是容器,面向对象编程时,我们不需要再去一步步拆分,只需要创建好对象,任由其在被需要的时候调用,至于怎么发展就不是我们需要考虑的事情了
面向对象之类与对象
对象就是数据和方法的结合体,而类是在多个对象需要被定义时,在这些对象中包含的相同的数据和方法,我们就可以用类一次性接收,不需要在每个对象中重复编写
任何事物只有在数量达到一定程度后,根据他们相同的特征划分为相同的相同的类,比如人类,犬类,类是一个宽泛的概念,代表着这个类中每个个体所具有的共性
在现实生活中,只有当个体的对象达到一定数量后才会产生类的概念,而在编程中,如果需要产生对象时,我们需要先定义出类,才可以从这个类中产生我们需要的对象
类与对象的创建
面向对象并不是真正意义上的新技术,而是为了作为区分设计的新的语法结构,在python中,一定要先定义类,才可以用类产生对象
类的语法结构
class 类名: '''代码注释''' 对象公共的数据 对象公共的功能 """ 1.class是定义类的关键字 2.类名的命名与变量名几乎一致,需要注意的时候首字母推荐大写用于区分 3.数据:变量名与数据值的绑定,功能(方法)其实就是函数 4.类与对象,对象才是关键,这也是面向对象编程的关键所在 """
类的定义与调用
类在定义阶段会执行类体代码,但是产生对象仅仅属于类的局部名称空间,外界无法直接调用
以定义选课系统为例
# 需求:清华大学学生选课系统 # 定义类 class Student: # 对象公共的数据 school_name = '清华大学' # 对象公共的功能 def choice_course(self): # self 代表类的实例,self在定义类的方法时是必须有的,虽然在调用时不必传入相应的参数,在某种意义上,self即类中产生的对象本身 print('学生选课功能') # 查看名称空间 print(Student.__dict__) # __dict__只是python提供的一种查看名称空间的方式,并不意味着类就是字典,只不过是这个名称空间以字典的形式呈现 print(Student.__dict__.get('school_name')) print(Student.__dict__.get('choice_course')) '''在面向对象中 类和对象访问数据或者功能 可以统一采用句点符''' print(Student.school_name) print(Student.choice_course) # 类只有在调用时才会产生对象 '''类名加括号就会产生对象,并且每执行一次都会产生一个全新的对象''' obj1 = Student() # 变量名obj1接收类名加括号之后的返回值(结果) obj2 = Student() obj3 = Student() print(obj1, obj2, obj3) print(obj1.__dict__) # 在没有定义属性前,类的名称空间中是空的 print(obj2.__dict__) print(obj3.__dict__) print(obj1.school_name) # 当我们通过.的方式调用类中的公共变量名时,会直接输出该变量名绑定的数据值 print(obj2.school_name) print(obj3.school_name) Student.school_name = '家里蹲大学' # 我们也可以通过按照字典改值的方式修改类中的公共数据 print(obj1.school_name) print(obj2.school_name) print(obj3.school_name) '''数据和功能,也可以统称为属性,数据就是类中存放的绑定给对象的属性名,功能就类中存放的可供对象使用的方法'''
对象独有的数据
就像人类同样是在人这一个大类中,个体却各有差异一样,类中的对象也有其独有的数据
以选课系统为例
class Student: # 对象公共的数据 school_name = '清华大学' # 对象公共的功能 def choice_course(self): print('学生选课功能') obj1 = Student() obj2 = Student() '''推导流程1:每个对象手动添加独有的数据''' print(obj1.__dict__) obj1.__dict__['name'] = 'jason' obj1.__dict__['age'] = 18 obj1.__dict__['hobby'] = 'study' print(obj1.__dict__) print(obj1.name) print(obj1.age) print(obj1.hobby) print(obj2.__dict__) obj2.__dict__['name'] = 'kevin' obj2.__dict__['age'] = 28 obj2.__dict__['hobby'] = 'music' print(obj2.__dict__) print(obj2.name) print(obj2.age) print(obj2.hobby) # 当我们使用类中自带的方法一个一个添加数据时,代码需要不断地重复,这种情况下,我们就需要用到函数 '''推导流程2:将添加对象独有数据的代码封装成函数''' def init(obj, name, age, hobby): obj.__dict__['name'] = name obj.__dict__['age'] = age obj.__dict__['hobby'] = hobby stu1 = Student() stu2 = Student() init(stu1, 'jason', 18, 'music') init(stu2, 'kevin', 29, 'read') print(stu1.__dict__) print(stu2.__dict__) # 当我们函数封装完毕后,我们在添加数据时,这个函数不只有现在的学生对象可以用 # 其他类产生的对象也可以用,那么为了实现只有当前类产生的对象可以采用这种方法添加数据 # 我们只能将这个函数放到类中 '''推导流程3:给学生对象添加独有数据的函数只有学生对象有资格调用''' class Student: # 对象公共的数据 school_name = '清华大学' # 专门给学生添加独有数据的功能 def init(obj, name, age, hobby): obj.__dict__['name'] = name obj.__dict__['age'] = age obj.__dict__['hobby'] = hobby # # 对象公共的功能 def choice_course(self): print('学生选课功能') stu1 = Student() Student.init(stu1, 'jason', 18, 'music') stu2 = Student() Student.init(stu2, 'kevin', 29, 'read') print(stu1.__dict__, stu2.__dict__) # 当我们将函数放到类中之后,每次添加数据还需要再调用init函数,那么是不是可以更加简便 '''推导步骤4:init方法变形''' class Student: # 对象公共的数据 school_name = '清华大学' # 专门给学生添加独有数据的功能 __init__在类产生对象的过程中自动触发 def __init__(obj, name, age, hobby): obj.__dict__['name'] = name obj.__dict__['age'] = age obj.__dict__['hobby'] = hobby # 对象公共的功能 def choice_course(self): print('学生选课功能') stu1 = Student('jason', 18, 'read') print(stu1.__dict__) print(stu1.name) print(stu1.school_name) # 当这一步完成后,我们可以省去调用函数这一步,但是还是需要在类中使用obj.__dict__[]的方法来添加数据,那么这个时候,self就派上用场了,在这个时候,self代表的就是类产生的对象本身 '''推导步骤5:变量名修改''' class Student: # 对象公共的数据 school_name = '清华大学' # 专门给学生添加独有数据的功能 类产生对象的过程中自动触发 def __init__(self, name, age, hobby): self.name = name # self.__dict__['name'] = name self.age = age self.hobby = hobby # 对象公共的功能 def choice_course(self): print('学生选课功能') stu1 = Student('jason', 18, 'read') print(stu1.name) print(stu1.school_name) # 当self产生作用后,那么我们也就可以用self加句点符来添加相应的数据,类体代码的兼容性也会更强
对象独有的功能
参照上述方法,我们也可以得出,对象也可以有自己独有的功能或函数
class Student: # 对象公共的数据 school_name = '清华大学' # 专门给学生添加独有数据的功能 类产生对象的过程中自动触发 def __init__(self, name, age, hobby): self.name = name # self.__dict__['name'] = name self.age = age self.hobby = hobby # 对象公共的功能 def choice_course(self): print(f'学生{self.name}正在选课') stu1 = Student('jason', 18, 'music') stu2 = Student('kevin', 28, 'read') # 1.直接在全局定义功能 该函数就不是学生对象独有的了 def eat(): print('吃东西') stu1.eat = eat print(stu1.__dict__) stu1.eat() # 2.只能将函数放在类中 但是类中的函数又是对象公共的 '''定义在类中的功能,默认就是绑定给对象使用的,哪个对象来调用,哪个对象就是主导''' Student.choice_course(123) # 类调用需要自己传参数 stu1.choice_course() # choice_course(stu1) 对象调用会自动将对象当做第一个参数传入 stu1.choice_course() stu2.choice_course() # 当self产生作用时,对象本身就会被当作参数传递到该共功能中 # 对象修改数据值 stu1.name = 'tony' # 当点的名字已经存在的情况下 则修改对应的值 # 对象新增数据值 stu1.pwd = 123 # 当点的名字不存在的情况下 则新增数据 print(stu1.__dict__)
PS:面向对象编程就像为我们搭建了一个工具箱,而其中不同的函数就相当于不同类型的工具,我们在取用工具时,我们就是不同的对象,当我们使用工具时,工具会与我们绑定,这个时候这个工具就是我们的专用工具,也就是对象的独有功能,当我们使用完工具放回工具箱后,谁再来用,怎么用跟我们就关系不大了,这样的好处就是工具不会丢,这样理解,类与对象的存在也正是为了程序有更好的可拓展性和更高的兼容性
面向对象方法的种类
定义在类中的函数大致可以分为动态与静态两种,这种情况下,我们一般选择采用不同的调用条件作为区分
- 动态方法
动态方法分为两种调用方式,一种就是我们之前学的,在对象调用函数时,默认该函数为提供给对象使用借助于self将对象自身作为第一个参数传递给该方法,如果有我们提前定义好的参数,也需要一并传入
另一种方法就是在类调用该方法时,也是默认将类作为第一个参数传递,前提是需要使用@classmethod将该方法与类绑定,这样就可以修改为该方法默认提供给类使用,即使借助对象来调用,也依旧会将类作为第一个默认参数传入
我们也可以理解为这个方法是在造工具时,默认只要有人来使用,就可以给他用,但是在加上@classmethod进行绑定后,就相当于贴上了一个标签,这种工具每次在使用时,都需要我们厂里的质检工人先使用一次
- 静态方法就相当于普通函数,用@staticmethod绑定后,每次使用都需要手动传入参数,不论是谁调用都是如此,就像工具虽然制造出来了,但是每次使用前都需要添加几个关键零件,不论是厂子里的人还是买工具的都是如此
class Student: school_name = '摆烂大学' # 1.类中直接定义函数,默认绑定给对象,类调用有几个参数传几个,对象调用第一个参数就是对象自身 def func1(self): print('看谁最能摆烂 真的好棒棒!!!') # 2.被@classmethod修饰的函数,默认绑定给类,类调用第一个参数就是类自身,对象也可以调用并且会自动将产生该对象的类当做第一个参数传入 @classmethod def func2(cls): print('嘿嘿嘿 猜猜我是干嘛滴', cls) """ 动态方法 """ # 3.普普通通的函数 无论是类还是对象调用 都必须自己手动传参 @staticmethod def func3(a): print('哈哈哈 猜猜我又是什么', a) """ 静态方法 """ obj = Student() # 1.绑定给对象的方法 # obj.func1() # Student.func1(123) # 2.绑定给类的方法 Student.func2() # fun2(Student) obj.func2() # func2(Student) # 3.静态方法 # Student.func3(123) # obj.func3(321)
面向对象继承理论
面对对象编程的三大特性就是封装,继承,多态
一、封装(隐藏)
隐藏对象的属性和实现细节,只对外提供必要的方法。相当于将"细节封装起来",只对外暴露“相关调用方法”。通过私有属性、私有方法的方式实现封装
Python追求简洁的语法,没有严格的语法级别的"访问控制符",更多的是依靠程序员的自觉实现。
二、继承:
如果一个新类继承自一个设计好的类,就直接具备了已有类的特征,就大大降低了工作难度,已有的类,我们称为"父类或基类",新的类,我们称为“子类或派生类”
继承可以让子类具有父类的特性,提高了代码的重用性
三、多态:
多态(polymorphism)是指同一个方法调用,由于对象不同可能会产生不同的行为。
注意:
1.多态是方法的多态,属性没有多态
2.多态的存在有两个必要条件:继承、方法重写
根据以上的定义,我们会发现继承是三者中用到最多的,是实操最多,体验感最强的
1.继承的含义
在现实生活中表示人与人或者事物之间的从属关系,例如子承父业,但是在编程中继承用来表示类与类之间的从属关系A类继承B类
2.继承的目的
在编程时,继承是为了让继承的类拥有被继承的类的数据与方法的使用权限,A继承B,那么A就具有了使用B所包含的所有数据与方法的使用权限
实操
class Son(Father): pass 1.在定义类的时候类名后面可以加括号填写其他类名,意味着继承其他类 2.在python中支持多继承,括号内填写多个类名彼此逗号隔开即可 class Son(F1, F2, F3): pass """ 1.继承其他类的类 Son 我们称之为子类、派生类 2.被继承的类 Father F1 F2 F3 我们称之为父类、基类、超类 ps:我们最常用的是子类和父类 """
继承的本质
对象:数据与功能的结合体
类(子类):多个对象相同数据和功能的结合体
父类:多个类(子类)相同数据和功能结合体
ps:类与父类本质都是为了节省代码
继承本质应该分为两部分
抽象:将多个类相同的东西抽出去形成一个新的类
继承:将多个类继承刚刚抽取出来的新的类
⭐对象查找名称的顺序
对象查找名称的数据需要分为三种情况来看
- 不继承的情况下
class C1: name = 'jason' def func(self): print('from func') obj = C1() # print(C1.name) # 当类中存在名称时,类肯定优先查找自己的名称空间 obj.name = '你迷了吗' # 由于对象原本没有name属性,该语法会在对象名称空间中创建一个新的'键值对' print(obj.__dict__) print(obj.name) # 当对象名称空间中有了name属性后,对象会优先查找冰雕用自己名称空间的name,输出你迷了吗 print(C1.name) """ 对象查找名字的顺序 1.先从自己的名称空间中查找 2.自己没有再去产生该对象的类中查找 3.如果类中也没有,那么直接报错 对象自身 >>> 产生对象的类 """
- 单继承情况下名字的查找顺序
class F1: name = 'jason' class S1(F1): name = 'kevin' obj = S1() obj.name = 'oscar' print(obj.name) ''' 对象自身 >>> 产生对象的类 >>> 父类 ''' class F3: name = 'jerry' pass class F2(F3): name = 'tony' pass class F1(F2): name = 'jason' pass class S1(F1): name = 'kevin' pass obj1 = S1() obj1.name = '嘿嘿嘿' print(obj1.name) """ 当对象中存在有我们所要调用的名称时,优先查找并输出对象名称空间中的名称,当对象名称空间中不存在时,会去产生该对象的类中去查找,如果这个类中没有,那么会逐级向上查找每一级子类所继承的父类中查找,如果最终这个名称不存在的话,那么就会报错 """
- 多继承情况下查找顺序
# 非菱形继承 深度优先 class G: name = 'from G' pass class A: # name = 'from A' pass class B: # name = 'from B' pass class C: # name = 'from C' pass class D(A): # name = 'from D' pass class E(B): # name = 'from E' pass class F(C): # name = 'from F' pass class S1(D,E,F): pass obj = S1() # print(obj.name) """ 在上述情况下,如果对象名称空间及产生该对象的类中都没有该名称,那么会按照父类作为参数传入的顺序逐级向上,例如,如果s1中没有这个名称,那么就会查找D的名称空间,如果D的名称空间中也没有,就会查找A的名称空间,A中也没有,才会到E中查找,并重复上述查找顺序,如果最终在C没有找到,就会报错 """
# 菱形继承 广度优先 class G: name = 'from G' pass class A(G): # name = 'from A' pass class B(G): # name = 'from B' pass class C(G): # name = 'from C' pass class D(A): # name = 'from D' pass class E(B): # name = 'from E' pass class F(C): # name = 'from F' pass class S1(D,E,F): pass obj = S1() # print(obj.name) """ 菱形继承不会直接找到最后一个父类,而是会切换到下一个继承的类查找,最后才会查找闭环的点 """
经典类与新式类
object可以认为是所有类的顶端
经典类:不继承object或者其子类的类
新式类:继承object或者其子类的类
在python2中有经典类和新式类
在python3中只有新式类(所有类默认都继承object)
class Student(object):pass
ps:以后我们在定义类的时候 如果没有其他明确的父类 也可能习惯写object兼容
⭐基于继承的派生方法
可以理解为子类继承了父类的数据与方法后,子类基于父类某个方法做了扩展
class Person: def __init__(self, name, age, gender): self.name = name self.age = age self.gender = gender class Student(Person): def __init__(self, name, age, gender, sid): super().__init__(name, age, gender) # 子类调用父类的方法 self.sid = sid class Teacher(Person): def __init__(self, name, age, gender, level): super().__init__(name, age, gender) self.level = level stu1 = Student('jason', 18, 'male', 666) print(stu1.__dict__) tea1 = Teacher('tony', 28, 'female', 99) print(tea1.__dict__) class MyList(list): def append(self, values): # 定义父类中原有的方法作为子类的方法 if values == 'jason': # 添加判断 print('jason不能尾部追加') return super().append(values) # 符合条件后执行父类原有的方法 """ 本质上还是父类的方法,只不过在子类继承添加操作后完成派生,成了子类的方法 """ obj = MyList() print(obj, type(obj)) obj.append(111) obj.append(222) obj.append(333) obj.append('jason') print(obj)
派生方法实战演练
import json
import datetime
d = {
't1': datetime.date.today(),
't2': datetime.datetime.today(),
't3': 'jason'
} # 定义字典
res = json.dumps(d) # 使用json模块进项序列化
print(res) # 打印序列化后的结果
"""
序列化报错
raise TypeError(f'Object of type {o.__class__.__name__} '
TypeError: Object of type date is not JSON serializable
date对象不可被序列化
"""
"""
能够被序列化的数据是有限的>>>:里里外外都必须是下列左边的类型
+-------------------+---------------+
| Python | JSON |
+===================+===============+
| dict | object |
+-------------------+---------------+
| list, tuple | array |
+-------------------+---------------+
| str | string |
+-------------------+---------------+
| int, float | number |
+-------------------+---------------+
| True | true |
+-------------------+---------------+
| False | false |
+-------------------+---------------+
| None | null |
+-------------------+---------------+
"""
"""
查看dumps源码 注意cls参数 默认传JsonEncoder
查看该类的源码 发现default方法是报错的发起者
编写类继承JsonEncoder并重写default方法 之后调用dumps手动传cls=我们自己写的类
"""
# 1.转换方式1:手动转类型(简单粗暴)
d = {
't1': str(datetime.date.today()),
't2': str(datetime.datetime.today())
}
res = json.dumps(d)
print(res)
# 2.转换方式2:派生方法(儒雅高端)
class MyJsonEncoder(json.JSONEncoder):
def default(self, o): # o即不可被序列化的数据
"""
:param o: 接收无法被序列化的数据
:return: 返回可以被序列化的数据
"""
if isinstance(o, datetime.datetime): # 判断是否是datetime类型 如果是则处理成可以被序列化的类型
return o.strftime('%Y-%m-%d %X')
elif isinstance(o, datetime.date):
return o.strftime('%Y-%m-%d')
return super().default(o) # 最后还是调用原来的方法 防止有一些额外操作没有做
res = json.dumps(d, cls=MyJsonEncoder)
print(res)
面向对象三大特性之封装
封装:就是将数据和功能'封装'起来,可以理解为将数据放在一个闭环中,但是有特定的接口可以打开,并取用这些数据和功能
隐藏:将数据和功能隐藏起来不让用户直接调用,而是开放一些接口间接调用从而可以在接口内添加额外的操作,相当于在闭环与外界两者之间建立过渡区,通过过渡区,外界可以取得数据,调用功能,但是不可以直接访问闭环内部,即便是修改数据也是由过渡区进行传递
伪装:将类里面的方法伪装成类里面的数据
隐藏
class C:
def func(self):pass
obj = C()
obj.func()
'''经过伪装'''
obj.func
class MyClass:
school_name = '老女孩大学'
_ = '嘿嘿嘿'
_name = 'tony'
'''类在定义阶段,名字前面有两个下划线,
那么该名字会被隐藏起来,无法直接访问'''
__age = 18
"""在python中其实没有真正意义上的隐藏
仅仅是换了个名字而已 _类名,__名字"""
def __choice_course(self):
print('老北鼻正在选课')
print(MyClass.school_name)
obj = MyClass()
print(obj.school_name)
print(MyClass._)
print(MyClass._name)
MyClass.__hobby = 'JDB' # 无法隐藏
print(MyClass.__hobby)
obj = MyClass()
obj.__addr = '派出所'
print(obj.__addr)
print(MyClass.__dict__)
print(MyClass._MyClass__age)
"""
隐藏的操作只能在类的定义阶段完成,
一旦类中的闭环形成,即使是借助于过渡区也无法隐藏数据与方法
__名称的方法可以帮我们查看隐藏的数据及名称
但是在python中,一般不建议去这么做
"""
class Person:
def __init__(self, name, age, hobby):
self.__name = name # 对象也可以拥有隐藏的属性
self.__age = age
self.__hobby = hobby
def get_info(self):
# 类体代码中,是可以直接使用隐藏的名字
print(f"""
姓名:{self.__name}
年龄:{self.__age}
爱好:{self.__hobby}
""")
# 隐藏的属性开放修改的接口,可以自定义很多功能
def set_name(self, new_name):
if len(new_name) == 0:
raise ValueError('你好歹写点东西')
if new_name.isdigit():
raise ValueError('名字不能是数字')
self.__name = new_name
obj = Person('jason', 18, 'read')
obj.get_info()
obj.set_name('tony老师')
obj.get_info()
obj.set_name('')
"""
以后我们在编写面向对象代码类的定义时,也会看到很多单下划线开头的名字
当我们以后遇到这类方法时,不建议直接访问,
可以用这种方法去查找一下可能提供的接口
"""
伪装
@property关键字,可以帮我们将类中的方法伪装成类中的名称,这样就不需要在调用完函数后输入括号进行调用,直接就可以获得函数的运行结果,但是在进行伪装后,就无法再通过加括号的方式调用
BMI指数:衡量一个人的体重与身高对健康影响的一个指标
体质指数(BMI)=体重(kg)÷身高^2(m)
EX:70kg÷(1.75×1.75)=22.86
class Person(object):
def __init__(self, name, height, weight):
self.name = name
self.height = height
self.weight = weight
@property
def BMI(self):
return self.weight / (self.height ** 2)
p1 = Person('jason', 1.83, 78)
p1.BMI() # BMI应该作为人的基本数据而不是方法
print(p1.BMI) # 利用装饰器伪装成数据
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')
f = Foo('jason')
print(f.name)
f.name = 'jason123'
print(f.name)
del f.name
f.name = 'jason'
# 触发name.setter装饰器对应的函数name(f,’jason')
f.name = 123
# 触发name.setter对应的的函数name(f,123),抛出异常TypeError
del f.name # 触发name.deleter对应的函数name(f),抛出异常PermissionError
面向对象三大特性之多态
面向对象中多态意思是 ,一种事物可以有多种形态但是针对相同的功能应该定义相同的方法,这样无论我们拿到的是哪个具体的事物,都可以通过相同的方法调用功能
class Animal:
def spark(self):
'''叫的方法'''
pass
class Cat(Animal):
# def miao(self):
# print('喵喵喵')
def spark(self):
print('喵喵喵')
class Dog(Animal):
# def wang(self):
# print('汪汪汪')
def spark(self):
print('汪汪汪')
class Pig(Animal):
# def heng(self):
# print('哼哼哼')
def spark(self):
print('哼哼哼')
"""
以上代码中,猫,狗,猪都属于动物类,
那么按照函数中的思路,所有的动物都会发声
虽然他们叫出来的声音各不相同
但是他们都具有发声的这一功能
所以我们就可以用统一的spark来代替他们不同的发声功能
那么这就叫做多态,如果多个类继承了同一个父类
这些子类中一定会用父类中的统一功能
来声明各自与父类功能类似的自有功能
"""
"""
鸭子类型:只要你看上去像鸭子,走路像鸭子
说话像鸭子,那么你就是鸭子
"""
# linux系统
"""
文件 能够读取数据也能够保存数据
内存 能够读取数据也能够保存数据
硬盘 能够读取数据也能够保存数据
......
一切皆文件
在Linux系统中,凡是具有文件特性的都称之为文件
"""
class File:
def read(self): pass
def write(self): pass
class Memory:
def read(self): pass
def write(self): pass
class Disk:
def read(self): pass
def write(self): pass
'''
python永远提倡自由简介大方,不约束程序员行为
但是多态提供了约束的方法
'''
import abc
# 指定metaclass属性将类设置为抽象类,抽象类本身只是用来约束子类的,不能被实例化
class Animal(metaclass=abc.ABCMeta):
@abc.abstractmethod
# 该装饰器限制子类必须定义有一个名为talk的方法
def talk(self):
# 抽象方法中无需实现具体的功能
pass
class Cat(Animal): # 但凡继承Animal的子类都必须遵循Animal规定的标准
def talk(self):
pass
cat = Cat() # 若子类中没有一个名为talk的方法则会抛出异常TypeError,无法实例化
面向对象之反射
反射就是利用字符串操作对象的数据和方法
1.hasattr()⭐
判断对象是否含有某个字符串对应的属性名或方法名class C1: school_name = '小姐姐学院' def choice_course(self): print('大宝贝们正在选课') obj = C1() '''判断某个名字对象是否可以使用(存在)''' # 推导思路 try: obj.xxx except AttributeError: print('你木有这个名字') '''判断用户随意指定的名字对象是否可以使用(存在)''' target_name = input('请输入对象可能使用的名字>>>:').strip() try: obj.target_name except AttributeError: print('你木有这个名字') """ 字符串的名字跟变量名区别大不大 'school_name' school_name 非常大 完全不一样 """ """ 按照之前学的知识,我们可以用异常捕获进行处理 但是如果是与用户输入结合的话 用户输入的只有字符串类型,字符串并不是对象或者类中的名称 这种时候就需要借助hasattr方法 """ print(hasattr(obj, 'school_name')) # True # 我们可以理解为该方法自动将我们输入的字符串 # 转换格式并进行检验
2.getattr()⭐
根据字符串获取对象对应的属性名(值)或方法名(函数体代码)
print(getattr(obj, 'school_name')) # 小姐姐学院 print(getattr(obj, 'choice_course')) # <bound method """ getattr方法可以帮我们将字符串转换格式后 获得类或者对象中对应的名称绑定的值或方法 """ class C1: school_name = '小姐姐学院' def choice_course(self): print('大宝贝们正在选课') obj = C1() while True: target_name = input('请输入您想要操作的名字>>>:') if hasattr(obj, target_name): print('恭喜您 系统中有该名字') # 获取该名字对应的数据(值 函数) data_or_func = getattr(obj, target_name) if callable(data_or_func): print('您本次使用的是系统中的某个方法') data_or_func() else: print('您本次使用的是系统中的某个数据') print(data_or_func) else: print('很抱歉 系统中没有该名字')
3.setattr()
根据字符串给对象设置或者修改数据
4.delattr()
根据字符串删除对象里面的名字
反射的实战案例
# 模拟cmd终端
class WinCmd:
def tasklist(self):
print("""
1.学习编程
2.学习python
3.学习英语
""")
def ipconfig(self):
print("""
地址:127.0.0.1
地址:上海浦东新区
""")
def get(self, target_file):
print('获取指定文件',target_file)
def put(self, target_file):
print('上传指定文件',target_file)
def server_run(self):
print('欢迎进入简易版本cmd终端')
while True:
target_cmd = input('请输入您的指令>>>:')
res = target_cmd.split(' ')
# cmd中的命令以空格为间隔输入,所以按照空格进行切割
if len(res) == 1:
if hasattr(self, res[0]):
getattr(self, res[0])()
else:
print(f'{res[0]}不是内部或者外部命令')
elif len(res) == 2:
if hasattr(self, res[0]):
getattr(self, res[0])(res[1])
else:
print(f'{res[0]}不是内部或者外部命令')
obj = WinCmd()
obj.server_run()
# 一切皆对象
# 利用反射保留某个py文件中所有的大写变量名及对应的数据值
import settings
print(dir(settings)) # dir列举对象可以使用的名字
useful_dict = {}
for name in dir(settings):
if name.isupper():
useful_dict[name] = getattr(settings, name)
print(useful_dict)
while True:
target_name = input('请输入某个名字')
if hasattr(settings, target_name):
print(getattr(settings, target_name))
else:
print('该模块文件中没有该名字')
面向对象之魔法方法
魔法方法就是我们之前遇到的,类中带有双下的方法,它的特点就是不需要程序员人为的调用,而是在某些特定条件下自动执行
1.双下init
# __init__ 对象添加独有数据的时候自动触发
class UserInfo:
def __init__(self, name, age, pwd, salary):
self.name = name
self.age = age
self.pwd = pwd
self.salary = salary
obj = UserInfo('tom', 18, 123, 15000)
print(obj.__dict__)
# {'name': 'tom', 'age': 18, 'pwd': 123, 'salary': 15000}
"""
当我们通过传参给对象添加独有数据时,__init__会自动运行,将参数传递给对应形参
"""
2.双下str
# __str__ 对象被执行打印操作的时候自动触发
class UserInfo:
def __init__(self, name, age, pwd, salary):
self.name = name
self.age = age
self.pwd = pwd
self.salary = salary
def __str__(self):
print('__str__触发')
return f'{self.name}'
obj = UserInfo('tom', 18, 123, 15000)
print(obj)
"""
__str__触发
tom
"""
3.双下call
# __call__对象加括号调用的时候自动触发
class UserInfo:
def __init__(self, name, age, pwd, salary):
self.name = name
self.age = age
self.pwd = pwd
self.salary = salary
def __call__(self):
print('__call__触发')
obj = UserInfo('tom', 18, 123, 15000)
obj()
"""
__call__触发
"""
4.双下getattr
# __getattr__ 对象点不存在的名字的时候自动触发
class UserInfo:
def __init__(self, name, age, pwd, salary):
self.name = name
self.age = age
self.pwd = pwd
self.salary = salary
def __getattr__(self, item):
print('该名称不存在哦')
obj = UserInfo('tom', 18, 123, 15000)
obj.hobby
"""
该名称不存在哦
"""
5.双下getattribute
# __getattribute 对象点名字就会自动触发,当它存在时就不会执行__getattr__
class UserInfo:
def __init__(self, name, age, pwd, salary):
self.name = name
self.age = age
self.pwd = pwd
self.salary = salary
def __getattribute__(self, item):
print(item)
# item就是我们用对象点出来的属性
return '哈哈哈'
obj = UserInfo('tom', 18, 123, 15000)
print(obj.name)
"""
name
哈哈哈
"""
6.双下setattr
# __setattr__ 以对象.名字 = 值的形式编写代码给对象添加或者修改数据的时候自动触发
class UserInfo:
def __init__(self, name, age, pwd, salary):
self.name = name
self.age = age
self.pwd = pwd
self.salary = salary
def __setattr__(self, key, value):
print(key, value)
# key就是我们所点的名称,value就是名称对应的值
return '哈哈哈'
obj = UserInfo('tom', 18, 123, 15000)
obj.name = 'jerry'
"""
name tom
age 18
pwd 123
salary 15000
name jerry
"""
7.双下enter
# __enter__ 当对象被当做with上下文管理操作的开始自动触发,并且该方法返回什么值,as后面的变量名就会接收到什么值
class UserInfo:
def __init__(self, name, age, pwd, salary):
self.name = name
self.age = age
self.pwd = pwd
self.salary = salary
def __enter__(self):
return '哈哈哈'
obj = UserInfo('tom', 18, 123, 15000)
with obj as f:
print(f)
"""
Traceback (most recent call last):
File "D:\PycharmProjects\pythonProject\test.py", line 13, in <module>
with obj as f:
AttributeError: __exit__
单独使用__enter__会报错,就像文件操作中文件打开后需要关闭一样
也是我们接下来要讲的__exit__
"""
8.双下exit
# __exit__ with上下文管理语法运行完毕之后自动触发(子代码结束)
class UserInfo:
def __init__(self, name, age, pwd, salary):
self.name = name
self.age = age
self.pwd = pwd
self.salary = salary
def __enter__(self):
return '哈哈哈'
def __exit__(self, exc_type, exc_val, exc_tb):
print(self, exc_type, exc_val, exc_tb)
obj = UserInfo('tom', 18, 123, 15000)
with obj as f:
print(f)
"""
哈哈哈
<__main__.UserInfo object at 0x0000026B164F5B50> None None None
在使用__enter__时必须搭配__exit__
"""
魔法方法笔试题
# 1.补全下列代码使得运行不报错即可
class Context:
pass
with Context() as f:
f.do_something()
"""
根据上面所学知识,我们可以看得出
代码中缺少了__enter与__exit__
那么我们首先需要补全这两个功能
然后在代码中还在用对象点了名称后进行了调用
所以代码中还需要有一个do_something函数
"""
class Context:
def do_something(self):
pass
def __enter__(self):
return self
# 在class Context:
def do_something(self):
print('我是do_something')
def __enter__(self):
return self
# 因为最后要进行调用,所以我们需要用self将函数名返回出去
def __exit__(self, exc_type, exc_val, exc_tb):
pass
with Context() as f:
f.do_something()
def __exit__(self, exc_type, exc_val, exc_tb):
pass
with Context() as f:
f.do_something()
"""
我是do_something
"""
2.自定义字典类型并让字典能够通过句点符的方式操作键值对
class MyDict(dict):
# 继承dict原有的属性,重写功能
def __setattr__(self, key, value):
# __setattr__ 在新建或修改值的时候触发
self[key] = value
# 在得到键与值的时候,新建键值对
def __getattr__(self, item):
return self.get(item)
# 返回项目中名称对应的值
obj = MyDict()
obj.name = 'jason'
obj.pwd = 18
obj.hobby = 'read'
print(obj)
# {'name': 'jason', 'pwd': 18, 'hobby': 'read'}
print(obj.name) # Jason
print(obj.pwd) # 18
print(obj.hobby) # read
print(obj)
# {'name': 'jason', 'pwd': 18, 'hobby': 'read'}
print(obj) # 字典存储的数据 {}
print(obj.__dict__) # 字典对象名称空间 {'name': 'jason'}
"""
相当于将本应写入名称空间中的数据进行拦截,写入到了字典中,并以这种方式让字典实现使用句点符添加与修改键值对
"""
元类
元类简介
"""推导步骤1:如何查看数据的数据类型"""
s1 = 'hello world' # str()
l1 = [11, 22, 33, 44] # list()
d1 = {'name': 'jason', 'pwd': 123} # dict()
t1 = (11, 22, 33, 44) # tuple()
print(type(s1)) # <class 'str'>
print(type(l1)) # <class 'list'>
print(type(d1)) # <class 'dict'>
print(type(t1)) # <class 'tuple'>
"""推导步骤2:其实type方法是用来查看产生对象的类名"""
class Student:
pass
obj = Student()
print(type(obj)) # <class '__main__.Student'>
"""推导步骤3:python中一切皆对象 我们好奇type查看类名显示的是什么"""
class Student:
pass
obj = Student()
print(type(obj)) # <class '__main__.Student'>
print(type(Student)) # <class 'type'>
class A:pass
class B:pass
print(type(A), type(B))
"""
结论:我们定义的类其实都是由type类产生的
那么可以产生类的类就叫做元类
"""
创建类的两种方式
# 方式1:使用关键字class
class Teacher:
school_name = '老女儿'
def func1(self):pass
print(Teacher)
print(Teacher.__dict__)
# 方式2:利用元类type type(类名,类的父类,类的名称空间)
cls = type('Student', (object,), {'name':'jason'})
print(cls)
print(cls.__dict__)
"""
了解知识:名称空间的产生
1.手动写键值对
针对绑定方法不好定义
2.内置方法exec
能够运行字符串类型的代码并产生名称空间
"""
元类定制类的产生行为(在类产生时添加限制)
当我们知道了类由type元类产生后,我们是否可以制定一些条件来限制类的产生,答案是肯定的,我们可以以类名首字母大写为例
"""
推导
对象是由类名加括号产生的 __init__
类是由元类加括号产生的 __init__
"""
"""所有的类必须首字母大写,否则无法产生"""
# 1.自定义元类:继承type的类也称之为元类
class MyMetaClass(type):
def __init__(self, what, bases=None, dict=None):
print('what', what)
# 通过打印各项参数,我们得知what为类名
print('bases', bases)
print('dict', dict)
if not what.istitle():
# 那么我们就可以使用what作为判定条件
# 来限制类名首字母必须大写
raise TypeError('你是不是python程序员,懂不懂规矩,类名首字母应该大写啊!!!')
# 不符合限定条件的主动报错
super().__init__(what, bases, dict)
# 符合判断条件的则继续执行原有功能
# 2.指定类的元类:利用关键字metaclass指定类的元类
class myclass(metaclass=MyMetaClass):
desc = '元类其实很有趣 就是有点绕'
class Student(metaclass=MyMetaClass):
info = '我是学生 我很听话'
print(Student)
print(Student.__dict__)
"""
这样我们就实现了对类名首字母大写的限制
"""
"""
推导
对象加括号会执行产生该对象类里面的 __call__
类加括号会执行产生该类的类里面的 __call__
"""
"""给对象添加独有数据的时候,必须采用关键字参数传参"""
class MyMetaClass(type):
def __call__(self, *args, **kwargs):
# 1.产生一个空对象(骨架)
# 2.调用__init__给对象添加独有的数据(血肉)
# 3.返回创建好的对象
print(args) # ('jason', 18, 'male')
print(kwargs) # {}
# args接受所有位置参数,kwargs接受所有关键字参数
if args:
raise TypeError("你怎么回事,Jason要求对象的独有数据必须按照关键字参数传参,我看你是不想干了!!!")
# 利用args是否有值进行判断
return super().__call__(*args, **kwargs)
# 如果都是关键字参数,则继续执行原功能
class Student(metaclass=MyMetaClass):
def __init__(self, name, age, gender):
# print('__init__')
self.name = name
self.age = age
self.gender = gender
# obj = Student('jason', 18, 'male')
obj = Student(name='jason',age= 18,gender= 'male')
print(obj.__dict__)
魔法方法之双下new
class MyMetaClass(type):
def __call__(self, *args, **kwargs):
# 1.产生一个空对象(骨架)
obj = self.__new__(self)
# 2.调用__init__给对象添加独有的数据(血肉)
self.__init__(obj,*args, **kwargs)
# 3.返回创建好的对象
return obj
class Student(metaclass=MyMetaClass):
def __init__(self, name):
self.name = name
obj = Student('jason')
print(obj.name)
"""
__new__可以产生空对象
"""
设计模式简介
1.设计模式
前人通过大量的验证创建出来解决一些问题的固定高效方法
2.IT行业
23种
创建型
结构型
行为型
3.单例模式
类加括号无论执行多少次永远只会产生一个对象
目的:
当类中有很多非常强大的方法,我们在程序中很多地方都需要使用
如果不做单例,会产生很多无用的对象浪费存储空间
我们想着使用单例模式,整个程序就用一个对象
单例模式实现方式
绑定类的方法
class C1: __instance = None def __init__(self, name, age): self.name = name self.age = age @classmethod # 定义绑定给类的方法 def singleton(cls): if not cls.__instance: # 如果隐藏属性没有值的话,那么就生成新的对象 cls.__instance = cls('jason', 18) return cls.__instance # 如果隐藏属性有值,那么证明对象已经存在,直接返回该对象 obj1 = C1.singleton() """ 当使用该方法产生对象后 对象存在的话,得到的始终只有该对象 """ obj2 = C1.singleton() obj3 = C1.singleton() print(id(obj1), id(obj2), id(obj3)) obj4 = C1('kevin', 28) """ 这个方法并不影响正常情况下对象的产生 """ obj5 = C1('tony', 38) print(id(obj4), id(obj5))
使用元类方法进行限制
class Mymeta(type): def __init__(self, name, bases, dic): # 定义类Mysql时就触发 # 事先先从配置文件中取配置来造一个Mysql的实例出来 self.__instance = object.__new__(self) # 产生对象 self.__init__(self.__instance, 'jason', 18) # 初始化对象 # 上述两步可以合成下面一步 self.__instance=super().__call__(*args,**kwargs) super().__init__(name, bases, dic) def __call__(self, *args, **kwargs): # Mysql(...)时触发 if args or kwargs: # 判断args或kwargs内是否有值 # 有值则说明对象已存在 obj = object.__new__(self) # 对象存在的话,新建一个空对象进行对象生成 self.__init__(obj, *args, **kwargs) return obj # 将新生成的对象返回 return self.__instance # 如果对象不存在则返回后执行原本的操作 class Mysql(metaclass=Mymeta): def __init__(self, name, age): self.name = name self.age = age obj1 = Mysql() obj2 = Mysql() print(id(obj1), id(obj2)) obj3 = Mysql('tony', 321)、 # 该方法同样不影响使用正常方法生成对象 obj4 = Mysql('kevin', 222) print(id(obj3), id(obj4))
基于模块的单例模式
主要原理是在模块导入时,只会进行一次导入操作,所以使用导入的类产生对象时,也只会产生一个对象
class C1: def __init__(self, name): self.name = name obj = C1('jason') # 定义装饰器 def outer(cls): _instance = cls('jason', 18) # 定义一个隐藏对象 def inner(*args, **kwargs): if args or kwargs: # 当有参数传进来时 obj = cls(*args, **kwargs) # 创建类 return obj # 返回创建的类 return _instance # 如果没有参数传入,返回隐藏的对象 return inner # 将类名传递到装饰器 @outer # Mysql=outer(Mysql) class Mysql: def __init__(self, host, port): self.host = host self.port = port obj1 = Mysql() obj2 = Mysql() obj3 = Mysql() print(obj1 is obj2 is obj3) # True obj4 = Mysql('1.1.1.3', 3307) obj5 = Mysql('1.1.1.4', 3308) print(obj3 is obj4) # False
pickle序列化模块
优势:能够序列化python中所有的类型
缺陷:只能够在python中使用,无法跨语言传输
需求:产生一个对象并保存到文件中,取出来还是一个对象pickle模块可以实现python数据与二进制数据之间的交换,执行读写
class C1: def __init__(self, name, age): self.name = name self.age = age def func1(self): print('from func1') def func2(self): print('from func2') obj = C1('jason', 18) # import json # with open(r'a.txt','w',encoding='utf8') as f: # json.dump(obj, f) # 使用json模块无法完成对象序列化 import pickle with open(r'a.txt', 'wb') as f: pickle.dump(obj, f) with open(r'a.txt','rb') as f: data = pickle.load(f) print(data) data.func1() data.func2() print(data.name)