目录

面向对象

一、面向对象推导

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:
    # 公共的数据
    # 公共的方法
  1. class:定义类的关键字
  2. People:类名
    • 类名的命名跟变量名一致,首字母大写(为了更好的区分)
  3. 类体代码(公共的数据\方法)
    • 类体代码在类定义阶段就会执行

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函数是专用给学生对象创建独有的数据

将函数封装到学生类中,这样只有学生类产生的对象才有资格访问

  1. 先产生一个空对象

  2. 自动调用类里面的__init__方法 将产生的空对象当成第一个参数传入

  3. 将产生的对象返回出去

# 学生类
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哈哈哈

针对对象独有的方法 我们无法真正实现

  1. 如果在全局则不是独有的
  2. 如果在类中则是公共的

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. 括号内填写你需要继承的类名
  3. 括号内可以填写多个父类,用逗号隔开
  4. 平时最常用的就是父类和子类
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__方法专门用于给对象添加属性

  • 类产生对象的步骤:

  1. 产生一个空对象
  2. 自动触发__ init__方法实例化对象
  3. 返回实例化好的对象
 posted on 2022-07-31 19:36  念白SAMA  阅读(17)  评论(0编辑  收藏  举报