• 面向对象进阶


    1.1 面向对象编程

    面向过程编程:类似于工厂的流水线

    • 优点:逻辑清晰
    • 缺点:扩展性差

    面向对象编程:核心是对象二字,对象属性和方法的集合体,面向对象编程就是一堆对象交互

    • 优点:扩展性强
    • 缺点:逻辑非常乱

    1.2 类与对象

    • 对象:属性和方法的集合体
    • 类:一系列相同属性和方法的集合体

    现实世界中先有对象后有类,python中先有类,再实例化出对象

    一、什么是继承

    • 继承是一种新建类的方式,新建的类称为子类,被继承的类称为父类
    • 继承的特性是:子类会遗传父类的属性
    • 继承是类与类之间的关系

    89-类的继承-继承.jpg?x-oss-process=style/watermark

    二、为什么用继承

    • 使用继承可以减少代码的冗余

    三、对象的继承

    • Python中支持一个类同时继承多个父类
    class Parent1:
        pass
    
    
    class Parent2:
        pass
    
    
    class Sub1(Parent1, Parent2):
        pass
    
    • 使用__bases__方法可以获取对象继承的类
    print(Sub1.__bases__)
    (<class '__main__.Parent1'>, <class '__main__.Parent2'>)
    
    • 在Python3中如果一个类没有继承任何类,则默认继承object类
    • 在Python2中如果一个类没有继承任何类,不会继承object类
    print(Parent1.__bases__)
    (<class 'object'>,)
    

    四、类的分类

    • 后面会详细解释类的分类,目前仅做了解

    4.1 新式类

    • 继承了object的类以及该类的子类,都是新式类
    • Python3中所有的类都是新式类

    4.2 经典类

    • 没有继承object的类以及该类的子类,都是经典类
    • 只有Python2中才有经典类

    五、继承与抽象

    继承描述的是子类与父类之间的关系,是一种什么是什么的关系。要找出这种关系,必须先抽象再继承,抽象即抽取类似或者说比较像的部分。

    抽象分成两个层次:

    1. 将奥巴马和梅西这俩对象比较像的部分抽取成类;
    2. 将人,猪,狗这三个类比较像的部分抽取成父类。

    抽象最主要的作用是划分类别(可以隔离关注点,降低复杂度),如下图所示:

    89-类的继承-抽象图.png?x-oss-process=style/watermark

    继承:基于抽象的结果,通过编程语言去实现它,肯定是先经历抽象这个过程,才能通过继承的方式去表达出抽象的结构。

    抽象只是分析和设计的过程中,一个动作或者说一种技巧,通过抽象可以得到类,如下图所示:

    89-类的继承-继承图.png?x-oss-process=style/watermark

    六、继承的应用

    • 牢记对象是特征与功能的集合体,我们可以拿选课系统举例
    class OldboyPeople:
        """由于学生和老师都是人,因此人都有姓名、年龄、性别"""
        school = 'oldboy'
    
        def __init__(self, name, age, gender):
            self.name = name
            self.age = age
            self.gender = gender
    
    
    class OldboyStudent(OldboyPeople):
        def choose_course(self):
            print('%s is choosing course' % self.name)
    
    
    class OldboyTeacher(OldboyPeople):
        def score(self, stu_obj, num):
            print('%s is scoring' % self.name)
            stu_obj.score = num
    
    
    stu1 = OldboyStudent('tank', 18, 'male')
    tea1 = OldboyTeacher('nick', 18, 'male')
    
    • 对象查找属性的顺序:对象自己-》对象的类-》父类-》父类。。。

    89-类的继承-查找.jpg?x-oss-process=style/watermark

    print(stu1.school)
    oldboy
    print(tea1.school)
    oldboy
    print(stu1.__dict__)
    {'name': 'tank', 'age': 18, 'gender': 'male'}
    tea1.score(stu1, 99)
    nick is scoring
    print(stu1.__dict__)
    {'name': 'tank', 'age': 18, 'gender': 'male', 'score': 99}
    

    6.1 属性查找练习

    class Foo:
        def f1(self):
            print('Foo.f1')
    
        def f2(self):
            print('Foo.f2')
            self.f1()
    
    
    class Bar(Foo):
        def f1(self):
            print('Bar.f1')
    
    
    # 对象查找属性的顺序:对象自己-》对象的类-》父类-》父类。。。
    obj = Bar()  # self是obj本身,即找到Bar的f1()
    obj.f2()
    Foo.f2
    Bar.f1
    

    一、类的分类

    1.1 新式类

    • 继承了object的类以及该类的子类,都是新式类
    • Python3中所有的类都是新式类

    1.2 经典类

    • 没有继承object的类以及该类的子类,都是经典类
    • 只有Python2中才有经典类

    二、菱形继承问题

    92-菱形继承问题-继承关机.jpg?x-oss-process=style/watermark

    在Java和C#中子类只能继承一个父类,而Python中子类可以同时继承多个父类,如A(B,C,D)

    如果继承关系为非菱形结构,则会按照先找B这一条分支,然后再找C这一条分支,最后找D这一条分支的顺序直到找到我们想要的属性

    如果继承关系为菱形结构,即子类的父类最后继承了同一个类,那么属性的查找方式有两种:

    • 经典类下:深度优先
    • 广度优先:广度优先
    • 经典类:一条路走到黑,深度优先

    92-菱形继承问题-经典类.png?x-oss-process=style/watermark

    • 新式类:不找多各类最后继承的同一个类,直接去找下一个父类,广度优先

    92-菱形继承问题-新式类.png?x-oss-process=style/watermark

    class G(object):
        # def test(self):
        #     print('from G')
        pass
    
    
    print(G.__bases__)
    
    
    class E(G):
        # def test(self):
        #     print('from E')
        pass
    
    
    class B(E):
        # def test(self):
        #     print('from B')
        pass
    
    
    class F(G):
        # def test(self):
        #     print('from F')
        pass
    
    
    class C(F):
        # def test(self):
        #     print('from C')
        pass
    
    
    class D(G):
        # def test(self):
        #     print('from D')
        pass
    
    
    class A(B, C, D):
        def test(self):
            print('from A')
    
    
    obj = A()
    (<class 'object'>,)
    obj.test()  # A->B->E-C-F-D->G-object
    from A
    

    三、C3算法与mro()方法介绍

    92-菱形继承问题-飞船原理.jpg?x-oss-process=style/watermark

    python到底是如何实现继承的,对于你定义的每一个类,python会计算出一个方法解析顺序(MRO)列表,这个MRO列表就是一个简单的所有基类的线性顺序列表,如:

    print(A.mro())  # A.__mro__
    [<class '__main__.A'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.F'>, <class '__main__.D'>, <class '__main__.G'>, <class 'object'>]
    for i in A.mro():
        print(i)
    <class '__main__.A'>
    <class '__main__.B'>
    <class '__main__.E'>
    <class '__main__.C'>
    <class '__main__.F'>
    <class '__main__.D'>
    <class '__main__.G'>
    <class 'object'>
    

    为了实现继承,python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。

    而这个MRO列表的构造是通过一个C3线性化算法来实现的。我们不去深究这个算法的数学原理,它实际上就是合并所有父类的MRO列表并遵循如下三条准则:

    1. 子类会先于父类被检查
    2. 多个父类会根据它们在列表中的顺序被检查
    3. 如果对下一个类存在两个合法的选择,选择第一个父类

    1.3 对象的属性的查找顺序

    先对象本身-->类-->父类-->父类的父类-->object-->自己定制的元类-->type

    1.4 给对象定制独有属性

    class People:
        pass
    
    p1 = Peolple()
    p1.name = 'nick'
    
    p2 = People()
    p2.name = 'tank'
    
    

    1.5 对象的绑定方法

    class People:
        def eat(self):
            print(self, 'eat....')
    
    p1 = Peolple()
    p1.eat() 
    p1.name = 'nick'
    
    p2 = People()
    p2.eat()
    p2.name = 'tank'
    
    

    一、绑定方法

    1.1 对象的绑定方法

    在类中没有被任何装饰器修饰的方法就是 绑定到对象的方法,这类方法专门为对象定制。

    class Person:
        country = "China"
    
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
        def speak(self):
            print(self.name + ', ' + str(self.age))
    
    
    p = Person('Kitty', 18)
    print(p.__dict__)
    {'name': 'Kitty', 'age': 18}
    print(Person.__dict__['speak'])
    <function Person.speak at 0x10f0dd268>
    
    

    speak即为绑定到对象的方法,这个方法不在对象的名称空间中,而是在类的名称空间中。

    通过对象调用绑定到对象的方法,会有一个自动传值的过程,即自动将当前对象传递给方法的第一个参数(self,一般都叫self,也可以写成别的名称);若是使用类调用,则第一个参数需要手动传值。

    p = Person('Kitty', 18)
    p.speak()  # 通过对象调用
    Kitty, 18
    Person.speak(p)  # 通过类调用
    Kitty, 18
    
    

    1.2 类的绑定方法

    类中使用 @classmethod 修饰的方法就是绑定到类的方法。这类方法专门为类定制。通过类名调用绑定到类的方法时,会将类本身当做参数传给类方法的第一个参数。

    class Operate_database():
        host = '192.168.0.5'
        port = '3306'
        user = 'abc'
        password = '123456'
    
        @classmethod
        def connect(cls):  # 约定俗成第一个参数名为cls,也可以定义为其他参数名
            print(cls)
            print(cls.host + ':' + cls.port + ' ' + cls.user + '/' + cls.password)
    
    
    Operate_database.connect()
    <class '__main__.Operate_database'>
    192.168.0.5:3306 abc/123456
    
    

    通过对象也可以调用,只是默认传递的第一个参数还是这个对象对应的类。

    Operate_database().connect()  # 输出结果一致
    <class '__main__.Operate_database'>
    192.168.0.5:3306 abc/123456
    
    

    二、非绑定方法

    在类内部使用 @staticmethod 修饰的方法即为非绑定方法,这类方法和普通定义的函数没有区别,不与类或对象绑定,谁都可以调用,且没有自动传值的效果。

    import hashlib
    
    
    class Operate_database():
        def __init__(self, host, port, user, password):
            self.host = host
            self.port = port
            self.user = user
            self.password = password
    
        @staticmethod
        def get_passwrod(salt, password):
            m = hashlib.md5(salt.encode('utf-8'))  # 加盐处理
            m.update(password.encode('utf-8'))
            return m.hexdigest()
    
    
    hash_password = Operate_database.get_passwrod('lala', '123456')  # 通过类来调用
    print(hash_password)
    f7a1cc409ed6f51058c2b4a94a7e1956
    p = Operate_database('192.168.0.5', '3306', 'abc', '123456')
    hash_password = p.get_passwrod(p.user, p.password)  # 也可以通过对象调用
    print(hash_password)
    0659c7992e268962384eb17fafe88364
    
    

    简而言之,非绑定方法就是将普通方法放到了类的内部。

    三、练习

    假设我们现在有一个需求,需要让Mysql实例化出的对象可以从文件settings.py中读取数据。

    # settings.py
    IP = '1.1.1.10'
    PORT = 3306
    NET = 27
    # test.py
    import uuid
    
    
    class Mysql:
        def __init__(self, ip, port, net):
            self.uid = self.create_uid()
            self.ip = ip
            self.port = port
            self.net = net
    
        def tell_info(self):
            """查看ip地址和端口号"""
            print('%s:%s' % (self.ip, self.port))
    
        @classmethod
        def from_conf(cls):
            return cls(IP, NET, PORT)
    
        @staticmethod
        def func(x, y):
            print('不与任何人绑定')
    
        @staticmethod
        def create_uid():
            """随机生成一个字符串"""
            return uuid.uuid1()
    
    
    # 默认的实例化方式:类名()
    obj = Mysql('10.10.0.9', 3307, 27)
    obj.tell_info()
    10.10.0.9:3307
    
    

    3.1 绑定方法小结

    如果函数体代码需要用外部传入的类,则应该将该函数定义成绑定给类的方法

    如果函数体代码需要用外部传入的对象,则应该将该函数定义成绑定给对象的方法

    # 一种新的实例化方式:从配置文件中读取配置完成实例化
    obj1 = Mysql.from_conf()
    obj1.tell_info()
    1.1.1.10:27
    print(obj.tell_info)
    <bound method Mysql.tell_info of <__main__.Mysql object at 0x10f469240>>
    print(obj.from_conf)
    <bound method Mysql.from_conf of <class '__main__.Mysql'>>
    
    

    3.2 非绑定方法小结

    如果函数体代码既不需要外部传入的类也不需要外部传入的对象,则应该将该函数定义成非绑定方法/普通函数

    obj.func(1, 2)
    不与任何人绑定
    Mysql.func(3, 4)
    不与任何人绑定
    print(obj.func)
    <function Mysql.func at 0x10f10e620>
    print(Mysql.func)
    <function Mysql.func at 0x10f10e620>
    print(obj.uid)
    a78489ec-92a3-11e9-b4d7-acde48001122
    
    

    1.6 类与数据类型

    lis = [1,2,3]  # lis = list([1,2,3])
    
    class foo:
        def __init__(self,name):
            self.name = name
    
    f = foo('name')
    
    lis.append(4)  # 对象调对象绑定的方法,会自动传参
    list.append(lis,4)  # 类调用对象绑定的方法,必须得传参
    
    

    一、多态

    多态指的是一类事物有多种形态,(一个抽象类有多个子类,因而多态的概念依赖于继承)

    1. 序列数据类型有多种形态:字符串,列表,元组
    2. 动物有多种形态:人,狗,猪

    1.1 动物的多种形态

    # 动物有多种形态:人类、猪、狗
    class Animal:
        def run(self):  # 子类约定俗称的必须实现这个方法
            raise AttributeError('子类必须实现这个方法')
    
    
    class People(Animal):
        def run(self):
            print('人正在走')
    
    
    class Pig(Animal):
        def run(self):
            print('pig is walking')
    
    
    class Dog(Animal):
        def run(self):
            print('dog is running')
    
    
    peo1 = People()
    pig1 = Pig()
    d1 = Dog()
    
    peo1.run()
    pig1.run()
    d1.run()
    人正在走
    pig is walking
    dog is running
    import abc
    
    
    class Animal(metaclass=abc.ABCMeta):  # 同一类事物:动物
        @abc.abstractmethod  # 上述代码子类是约定俗称的实现这个方法,加上@abc.abstractmethod装饰器后严格控制子类必须实现这个方法
        def talk(self):
            raise AttributeError('子类必须实现这个方法')
    
    
    class People(Animal):  # 动物的形态之一:人
        def talk(self):
            print('say hello')
    
    
    class Dog(Animal):  # 动物的形态之二:狗
        def talk(self):
            print('say wangwang')
    
    
    class Pig(Animal):  # 动物的形态之三:猪
        def talk(self):
            print('say aoao')
    
    
    peo2 = People()
    pig2 = Pig()
    d2 = Dog()
    
    peo2.talk()
    pig2.talk()
    d2.talk()
    say hello
    say aoao
    say wangwang
    
    

    1.2 文件的多种形态

    # 文件有多种形态:文件、文本文件、可执行文件
    import abc
    
    
    class File(metaclass=abc.ABCMeta):  # 同一类事物:文件
        @abc.abstractmethod
        def click(self):
            pass
    
    
    class Text(File):  # 文件的形态之一:文本文件
        def click(self):
            print('open file')
    
    
    class ExeFile(File):  # 文件的形态之二:可执行文件
        def click(self):
            print('execute file')
    
    
    text = Text()
    exe_file = ExeFile()
    
    text.click()
    exe_file.click()
    open file
    execute file
    
    

    二、多态性

    注意:多态与多态性是两种概念

    多态性是指具有不同功能的函数可以使用相同的函数名,这样就可以用一个函数名调用不同内容的函数。在面向对象方法中一般是这样表述多态性:向不同的对象发送同一条消息,不同的对象在接收时会产生不同的行为(即方法)。也就是说,每个对象可以用自己的方式去响应共同的消息。所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数。

    2.1 动物形态多态性的使用

    # 多态性:一种调用方式,不同的执行效果(多态性)
    def func(obj):
        obj.run()
    
    
    func(peo1)
    func(pig1)
    func(d1)
    人正在走
    pig is walking
    dog is running
    # 多态性依赖于:继承
    # 多态性:定义统一的接口
    def func(obj):  # obj这个参数没有类型限制,可以传入不同类型的值
        obj.talk()  # 调用的逻辑都一样,执行的结果却不一样
    
    
    func(peo2)
    func(pig2)
    func(d2)
    say hello
    say aoao
    say wangwang
    
    

    2.2 文件形态多态性的使用

    def func(obj):
        obj.click()
    
    
    func(text)
    func(exe_file)
    open file
    execute file
    
    

    2.3 序列数据类型多态性的使用

    def func(obj):
        print(len(obj))
    
    
    func('hello')
    func([1, 2, 3])
    func((1, 2, 3))
    5
    3
    3
    
    

    综上可以说,多态性是一个接口(函数func)的多种实现(如obj.run(),obj.talk(),obj.click(),len(obj))

    三、多态性的好处

    其实大家从上面多态性的例子可以看出,我们并没有增加新的知识,也就是说Python本身就是支持多态性的,这么做的好处是什么呢?

    1. 增加了程序的灵活性:以不变应万变,不论对象千变万化,使用者都是同一种形式去调用,如func(animal)
    2. 增加了程序额可扩展性:通过继承Animal类创建了一个新的类,使用者无需更改自己的代码,还是用func(animal)去调用
    class Cat(Animal):  # 属于动物的另外一种形态:猫
        def talk(self):
            print('say miao')
    
    
    def func(animal):  # 对于使用者来说,自己的代码根本无需改动
        animal.talk()
    
    
    cat1 = Cat()  # 实例出一只猫
    func(cat1)  # 甚至连调用方式也无需改变,就能调用猫的talk功能
    say miao
    
    
    • 上述代码我们新增了一个形态Cat,由Cat类产生的实例cat1,使用者可以在完全不需要修改自己代码的情况下。使用和人、狗、猪一样的方式调用cat1的talk方法,即func(cat1)
  • 相关阅读:
    oracle笔记
    log4j配置
    前段页面性能标准
    递归多叉树遍历
    // 获取元素拒顶部高度
    window.parent
    webpack打包
    vue源码解析推荐文章
    在vue项目中。artTemplate引入失败问题,修改源码
    webpack打包css前缀自动取消,以及样式冲突问题
  • 原文地址:https://www.cnblogs.com/zhouxuchong/p/11564853.html
Copyright © 2020-2023  润新知