• 面向对象编程(进阶)


    类的继承

    什么是继承

    • 继承是一种新建类的方式,新建的类称为子类,被继承的类称为父亲

    • 继承的特性是:子类会遗传父亲的属性

    • 继承是类与类之间的关系

    为什么用继承

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

    对象的继承

    • 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'>,)
    

    类的分类

    新式类

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

    • Python3中所有的类都是新式类

    经典类

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

    • 只有Python2中才有经典类

    继承与抽象

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

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

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

    继承的应用

    • 牢记对象是特征与功能的集合体
    class PekingPeople:
        '''由于学生和老师都是人,因此人都有姓名、年龄、性别'''
        school = 'peking'
    
        def __init__(self, name, age, gender):
            self.name = name
            self.age = age
            self.gender = gender
    
    
    class PekingStudent(PekingPeople):
        def choose_course(self):
            print(f'{self.name} is choosing course')
    
    
    class PekingTeacher(PekingPeople):
        def score(self, stu_obj, num):
            print(f'{self.name} is scoring')
            stu_obj.score = num
    
    
    stu1 = PekingStudent('tom', 18, 'male')
    tea1 = PekingTeacher('jerry', 18, 'male')
    
    • 对象查找属性的顺序:对象自己 --> 对象的类 --> 父类 --> 父类。。。
    print(stu1.school)
    
    peking
    
    print(tea1.school)
    
    peking
    
    print(stu1.__dict__)
    
    {'name': 'tom', 'age': 18, 'gender': 'male'}
    
    tea1.score(stu1, 99)
    
    jerry is scoring
    
    print(stu1.__dict__)
    
    {'name': 'tom', 'age': 18, 'gender': 'male', 'score': 99}
    

    属性查找练习

    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')
    
    
    # 对象查找属性的顺序:对象自己 $rightarrow$ 对象的类 --> 父类 --> 父类。。。
    obj = Bar()   # self是obj本身,即找到Bar的f1()
    obj.f2()
    
    Foo.f2
    Bar.f1
    

    类的派生

    派生

    • 派生:子类中新定义的属性的这个过程叫做派生,并且需要记住子类在使用派生的属性时始终以自己的为准

    派生方法一

    • 指名道姓访问某一个类的函数:该方式与继承无关
    class PekingPeople:
        '''由于学生和老师都是人,因此人都有姓名、年龄、性别'''
        school = 'peking'
    
        def __init__(self, name, age, gender):
            self.name = name
            self.age = age
            self.gender = gender
    
    
    class PekingStudent(PekingPeople):
        '''由于学生类没有独自的__init__()方法,因此不需要声明继承父类的__init__()方法,会自动继承'''
    
        def choose_course(self):
            print(f'{self.name} is choosing course')
    
    
    class PekingTeacher(PekingPeople):
        '''由于老师类有独自的__init__()方法,因此需要声明继承父类的__init__()'''
    
        def __init__(self, name, age, gender, level):
            PekingPeople.__init__(self, name, age, gender)
            self.level = level  # 派生
    
        def score(self, stu_obj, num):
            print(f'{self.name} is scoring')
            stu_obj.score = num
    
    
    stu1 = PekingStudent('tom', 18, 'male')
    tea1 = PekingTeacher('jerry', 18, 'male', 10)
    
    
    print(stu1.__dict__)
    
    
    {'name': 'tom', 'age': 18, 'gender': 'male'}
    
    
    print(tea1.__dict__)
    
    
    {'name': 'jerry', 'age': 18, 'gender': 'male', 'level': 10}
    
    

    派生方法二

    • 严格以继承属性查找关系

    • super()会得到一个特殊的对象,该对象就是专门用来访问父类中的属性的(按照继承的关系)

    • super().__init__(不用为self传值)

    • super的完整用法是super(自己的类名,self),在Python2中需要写完整,而Python3中可以简写为super()

    class PekingPeople:
        school = 'peking'
    
        def __init__(self, name, age, gender):
            self.name = name
            self.age = age
            self.gender = gender
    
    
    class PekingStudent(PekingPeople):
        def __init__(self, name, age, gender, stu_id):
    
            super().__init__(name, age, gender)
            self.stu_id = stu_id
    
        def choose_course(self):
            print(f'{self.name} is choosing course')
    
    
    stu1 = PekingStudent('tom', 19, 'male', 1)
    
    
    print(stu1.__dict__)
    
    
    {'name': 'tom', 'age': 19, 'gender': 'male', 'stu_id': 1}
    
    

    类的组合

    什么是组合

    • 组合就是一个类的对象具备某一个属性,该属性的值是指向另外一个类的对象

    为什么用组合

    • 组合是用来解决类与类之间代码冗余的问题
    class PekingPeople:
        school = 'peking'
    
        def __init__(self, name, age, gender):
            self.name = name
            self.age = age
            self.gender = gender
    
    
    class PekingStudent(PekingPeople):
        def __init__(self, name, age, gender, stu_id):
            PekingPeople.__init__(self, name, age, gender)
            self.stu_id = stu_id
    
        def choose_course(self):
            print(f'{self.name} is choosing course')
    
    
    class PekingTeacher(PekingPeople):
        def __init__(self, name, age, gender, level):
            PekingPeople.__init__(self, name, age, gender)
            self.level = level
    
        def score(self, stu, num):
            stu.score = num
            print(f'老师{self.name}为学生{stu.name}打分{num}')
    
    
    stu1 = PekingStudent('tom', 19, 'male', 1)
    tea1 = PekingTeacher('jerry', 18, 'male', 10)
    
    
    stu1.choose_course()
    
    
    tom is choosing course
    
    
    tea1.score(stu1, 100)
    
    
    老师jerry为学生tom打分100
    
    
    print(stu1.__dict__)
    
    
    {'name': 'tom', 'age': 19, 'gender': 'male', 'stu_id': 1, 'score': 100}
    
    
    • 上面这个选课系统需要修改、扩展,我们需要修改上述的代码

    如何用组合

    • 需求:假如我们需要给学生增添课程属性,但是又不是所有学生一进学校就有课程属性,课程属性时学生来学校后选出来的,也就是说课程需要后期学生们添加进去的

    • 实现思路:如果直接在学生中添加课程属性,那么学生刚被定义就需要添加课程属性,这就不符合我们的要求,因此我们可以使用组合能让学生未来添加课程属性

    class Course:
        def __init__(self, name, period, price):
            self.name = name
            self.period = period
            self.price = price
    
        def tell_info(self):
            msg = f'''
            课程名:{self.name}
            课程周期:{self.period}
            课程价钱:{self.price}
            '''
            print(msg)
    
    
    class PekingPeople:
        school = 'peking'
    
        def __init__(self, name, age, gender):
            self.name = name
            self.age = age
            self.gender = gender
    
    
    class PekingStudent(PekingPeople):
        def __init__(self, name, age, gender, stu_id):
            PekingPeople.__init__(self, name, age, gender)
            self.stu_id = stu_id
    
        def choose_course(self):
            print(f'{self.name} is choosing course')
    
    
    class PekingTeacher(PekingPeople):
        def __init__(self, name, age, gender, level):
            PekingPeople.__init__(self, name, age, gender)
            self.level = level
    
        def score(self, stu, num):
            stu.score = num
            print(f'老师{self.name}为学生{stu.name}打分{num}')
    
    
    # 创造课程
    python = Course('python全栈开发', '5mons', 3000)
    python.tell_info()
    
    
            课程名:python全栈开发
            课程周期:5mons
            课程价钱:3000
    
    

    linux = Course('linux运维', '5mons', 800)
    linux.tell_info()
    
    
            课程名:linux运维
            课程周期:5mons
            课程价钱:800
    
    

    # 创造学生与老师
    stu1 = PekingStudent('tom', 19, 'male', 1)
    tea1 = PekingTeacher('jerry', 18, 'male', 10)
    
    
    • 组合
    # 将学生、老师与课程对象关联/组合
    stu1.course = python
    tea1.course = linux
    
    
    stu1.course.tell_info()
    
    
            课程名:python全栈开发
            课程周期:5mons
            课程价钱:3000
    
    

    tea1.course.tell_info()
    
    
            课程名:linux运维
            课程周期:5mons
            课程价钱:800
    
    

    • 组合可以理解成多个人去造一个机器人,有的人造头、有的人造脚、有的人造手、有的人造驱赶,大家都完工后,造躯干的人把头、脚、手拼接到自己的躯干上,因此一个机器人便造出来了

    菱形继承问题

    类的分类

    新式类

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

    • Python3中所有的类都是新式类

    经典类

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

    • 只有Python2中才有经典类

    菱形继承问题

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

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

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

    • 经典类:深度优先

    • 新式类:广度优先

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

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

    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()方法介绍

    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. 如果对下一个类存在两个合法的选择,选择第一个父类

    类的多态与多态性

    多态

    • 多态是指同一种事物的多种形态(一个抽象类有多个子类,因而多态的概念依赖于继承)
    * 水 --> 冰、水蒸气、液态水
    
    * 序列数据类型有多种形态:字符串,列表,元组
    
    * 动物 --> 人、狗、猪
    
    

    动物的多种形态

    # 动物有多种形态:人类、猪、狗(在定义角度)
    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()
    dog1 = Dog()
    
    
    peo1.run()
    pig1.run()
    dog1.run()
    
    
    人在走
    pig is walking
    dog is running
    
    
    import abc
    
    
    class Animal(metaclass=abc.ABCMeta):   # 同一类事物:动物
    
        @abc.abstractmethod  # 上述代码子类是约定俗成的实现这个方法, 加上@abc.abstractmethod装饰器后严格控制子类必须实现这个方法
        def speak(self):
            raise AttributeError('子类必须实现这个方法')
    
    
    class People(Animal):    # 动物的形态之一:人
        def speak(self):
            print('say hello')
    
    
    class Dog(Animal):
        def speak(self):
            print('say wangwangwang')
    
    
    class Pig(Animal):
        def speak(self):
            print('say aoao')
    
    
    peo2 = People()
    pig2 = Pig()
    dog2 = Dog()
    
    
    peo2.speak()
    pig2.speak()
    dog2.speak()
    
    
    say hello
    say aoao
    say wangwangwang
    
    

    文件的多种形态

    # 文件有多种形态:文件、文本文件、可执行文件(在定义角度)
    
    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
    
    

    多态性

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

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

    动物形态多态性的使用

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

    文件形态多态性的使用

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

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

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

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

    多态性的好处

    Python本身就是支持多态性的,这么做的好处是什么:

    1. 增加了程序的灵活性:以不变应万变,不论对象千变万化,使用者都是同一种形式去调用,如func(animal)

    2. 增加了程序的可扩展性:通过继承Animal类创建了一个新的类,使用者无需更改自己的代码,还是用func(animal)去调用

    class Cat(Animal):   # 属于动物的另一种形态:猫
        def speak(self):
            print('say miao')
    
    
    def func(animal):   # 对于使用者来说,自己的代码根本无需改动
        animal.speak()
    
    
    cat1 = Cat()  # 实例出一只猫
    func(cat1)   # 甚至连调用方式也无需改变,就能调用猫的speak功能
    
    
    say miao
    
    
    • 上述代码新增了一个形态Cat,由Cat类产生的实例cat1,使用者可以在完全不需要修改自己代码的情况下。使用和人、狗、猪一样的方式调用cat1的speak方法,即func(cat1)

    小结

    多态:同一种事物的多种形态,动物分为人类,猪类(在定义角度)
    多态性:一种调用方式,不同的执行效果(多态性)

    为什么用多态

    • 多态性:
    * 继承同一个类的多个子类中有相同的方法名
    
    * 那么子类产生的对象就可以不用考虑具体的类型而直接调用功能
    
    

    多态的应用

    • Animal() --> 强调是用来指定标准的,不能被实例化,也就是说父类中被@abc.abcstractmethod装饰的方法,子类也必须要有,如果没有的话则会报错,这个拥有被装饰的方法的类称为抽象类

    • 由于动物都叫,如果人的叫使用speak();狗的叫使用bark();也就是说对于不同的动物有不同的叫方法,那么对于使用者来说,使用起来非常麻烦。因此我们可以规定人、狗的叫都为speak(),那么后面使用某个动物叫的方法,只需要调用speak即可

    import abc
    
    
    class Animal(metaclass=abc.ABCMeta):   # 同一类事物:动物
        @abc.abstractmethod
        def speak(self):
            print('1111')
    
        @abc.abstractmethod
        def eat(self):
            pass
    
    
    class People(Animal):
        def speak(self):
            print('say hello')
    
        def eat(self):
            pass
    
    
    class Dog(Animal):
        def speak(self):
            print('汪汪汪')
    
        def eat(self):
            pass
    
    
    class Pig(Animal):
        def speak(self):
            print('哼哼哼')
    
        def eat(self):
            pass
    
    
    peo = People()
    dog = Dog()
    pig = Pig()
    
    
    peo.speak()
    
    
    say hello
    
    
    dog.speak()
    
    
    汪汪汪
    
    
    pig.speak()
    
    
    哼哼哼
    
    
    def my_speak(animal):
        animal.speak()
    
    
    my_speak(peo)
    my_speak(dog)
    my_speak(pig)
    
    
    say hello
    汪汪汪
    哼哼哼
    
    

    数据类型中多态的应用

    • 我们一一直在使用多态,列表、元组、字符串的len()方法其实就是一种多态的应用,我们不需要关心len()的数据类型,我们只要记住如果需要求一个容器类型的长度,使用len()方法就行了
    l = [1, 2, 3]
    s = 'hello'
    t = (1, 2, 3)
    
    
    print(l.__len__())
    print(s.__len__())
    print(t.__len__())
    
    
    # 列表、字符串、元组规定了计算这些数据类型的长度就必须使用len()方法
    # def len(obj):
    #     return obj.__len__()
    
    
    3
    5
    3
    
    
    print(len(l))
    print(len(s))
    print(len(t))
    
    
    3
    5
    3
    
    

    鸭子类型

    • Python推崇的是鸭子类型,只要你叫声像鸭子,并且你走路的样子像鸭子,那么你就是鸭子

    • Linux中一切皆文件,只要我们规定硬盘、进程、文件都是文件,都拥有读read()写write()方法,那我们就没必要定义一个抽象文件类,更没必要使用@abc.abstractmethod装饰器规定文件需要拥有这些方法,因为这不符合Python的风格

    class Disk:
        '''硬盘'''
    
        def read(self):
            print('disk read')
    
        def write(self):
            print('disk write')
    
    
    class Process:
        '''进程'''
    
        def read(self):
            print('process read')
    
        def write(self):
            print('process write')
    
    
    class File:
        '''文件'''
    
        def read(self):
            print('file read')
    
        def write(self):
            print('file write')
    
    
    obj1 = Disk()
    obj2 = Process()
    obj = File()
    
    
    obj1.read()
    obj1.write()
    
    
    disk read
    disk write
    
    

    类的封装

    什么是封装

    • 封:类的属性对外是隐藏的,但是对内是开放的,类似于一个封闭的容器

    • 装:定义类时会申请一个名称空间,往里装入一系列名字/属性

    封装什么

    • 你钱包的有什么钱(数据的封装)

    • 你吃饭具体怎么实现的(方法的封装)

    为什么要封装

    封装数据的主要原因是:保护隐私

    封装方法的主要原因是:隔离复杂度

    提示:在编程语言里,对外提供的接口(接口可理解为了一个入口),就是函数,称为接口函数,这与接口的概念还不一样,接口代表一组接口函数的集合体。

    两个层面的封装

    封装其实分为两个层面,但无论哪种层面的封装,都要对外界提供访问你内部隐藏内容的接口(接口可以理解为入口,有了这个入口,使用者无需且不能够直接访问到内部隐藏的细节,只能走接口,并且我们可以在接口的实现上附加更多的处理逻辑,从而严格控制使用者的访问)

    第一个层面

    第一个层面的封装(什么都不用做):创建类和对象会分别创建二者的名称空间,我们只能用类名,或者obj.的方法去访问里面的名字,这本身就是一种封装

    注意:对于这一层面的封装(隐藏),类名和实例名就是访问隐藏属性的接口

    第二个层面

    第二个层面的封装:类中把某些属性和方法隐藏起来(或者说定义成私有的),只在类的内部使用、外部无法访问,或者留下少量接口(函数)供外部访问。

    在python中用双下划线的方式实现隐藏属性(设置成私有的)

    类中所有双下划线开头的名称如__x都会自动变形成:_类名__x的形式:

    class A:
        __N = 0   # 类的数据属性就应该是共享的,但是语法上是可以把类的数据属性设置成私有的如__N,会变形成_A__N
    
        def __init__(self):
            self.__X = 10  # 变形为self._A__X
    
        def __foo(self):  # 变形为_A__foo
            print('form A')
    
        def bar(self):
            self.__foo()  # 只有在类内部才可以通过__foo的形式访问到
    
    

    这种自动变形的特点:

    1. 类中定义的__x只能在内部使用,如self.__x,引用的就是变形的结果。
    2. 这种变形其实正是针对内部的变形,在外部是无法通过__x这个名字访问到。
    3. 在子类定义的__x不会覆盖在父类定义的__x,因为子类中变形成了:_子类名__x,而父类中形成了:_父类名__x,即双下划线开头的属性在继承给子类时,子类是无法覆盖的。

    注意:对于这一层面的封装(隐藏),我们需要在类中定义一个函数(接口函数)在它内部访问被隐藏的属性,然后外部就可以使用了

    这种变形需要注意的问题是:

    • 这种机制也并没有真正意义上限制我们从外部直接访问属性,知道了类属性名就可以拼出名字:_类名__属性,然后就可以访问了,如a._A__N
    a = A()
    print(a._A__N)
    
    
    0
    
    
    print(a._A__X)
    
    
    10
    
    
    print(A._A__N)
    
    
    0
    
    
    • 变形的过程只在类的定义时发生一次,在定义后的赋值操作,不会变形
    a = A()
    print(a.__dict__)
    
    
    {'_A__X': 10}
    
    
    a.__Y = 1
    print(a.__dict__)
    
    
    {'_A__X': 10, '__Y': 1}
    
    
    • 在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的
    # 正常情况
    class A:
        def fa(self):
            print('from A')
    
        def test(self):
            self.fa()
    
    
    class B(A):
        def fa(self):
            print('from B')
    
    
    b = B()
    b.test()
    
    
    from B
    
    
    # 把fa定义成私有的,即__fa
    class A:
        def __fa(self):   # 在定义时就变形为_A__fa
            print('from A')
    
        def test(self):
            self.__fa()  # 只会与自己所在的类为准,即调用_A__fa
    
    
    class B(A):
        def __fa(self):
            print('from B')
    
    
    b = B()
    b.test()
    
    
    from A
    
    

    私有模块

    python并不会真的阻止你访问私有的属性,模块也遵循这种约定,如果模块中的变量名_private_module以单下划线开头,那么from module import *时不能被导入该变量,但是你from module import _private_module依然是可以导入该变量的

    其实很多时候你去调用一个模块的功能时会遇到单下划线开头的socket._socket,sys._home,sys._clear_type_cache,这些都是私有的,原则上是供内部调用的,作为外部的你,一意孤行也是可以用的

    python要想与其他编程语言一样,严格控制属性的访问权限,只能借助内置方法如__getattr__

    注:__名字,这种语法只在定义的时候才有变形的效果,如果类或对象已经产生了,就不会有变形得效果了。

    类的property特性

    什么是property特性

    • property装饰器用于将被装饰的方法伪装成一个数据属性,在使用时可以不用加括号而直接使用
    ####### 定义 #######
    class Foo:
        def func(self):
            pass
    
        # 定义property属性
    
        @property
        def prop(self):
            pass
    
    
    #######  调用 #######
    foo_obj = Foo()
    foo_obj.func()   # 调用实例方法
    foo_obj.prop  # 调用property属性
    
    

    如下的例子用于说明如何定一个简单的property属性:

    class Goods(object):
    
        @property
        def size(self):
            return 100
    
    
    g = Goods()
    print(g.size)
    
    
    100
    
    

    property属性的定义和调用要注意一下几点:

    1. 定义时,在实例方法的基础上添加@property装饰器,并且仅有一个self参数
    2. 调用时,无需括号

    简单示例

    对于京东商城中显示电脑主机的列表页面,每次请求不可能把数据库中的所有内容都显示到页面上,而是通过分页的功能局部显示,所以在向数据库中请求数据时就要显示的指定获取从第m条到第n条的所有数据 这个分页的功能包括:

    1. 根据用户请求的当前页和总数据条数计算出 m 和 n
    2. 根据m 和 n 去数据库中请求数据
    ############  定义 ##########
    class Pager:
        def __init__(self, current_page):
    
            # 用户当前请求的页码(第一页、第二页...)
            self.current_page = current_page
    
            # 每页默认显示10条数据
            self.per_items = 10
    
        @property
        def start(self):
            val = (self.current_page - 1)*self.per_items
            return val
    
        @property
        def end(self):
            val = self.current_page * self.per_items
            return val
    
    
    ########### 调用  #############
    p = Pager(1)
    p.start   # 就是起始值,即:m
    p.end  # 就是结束值,即:n
    
    
    10
    
    

    从上述可见Python的property属性的功能是:property属性内部进行一系列的逻辑计算,最终将计算结果返回。

    property属性的两种方式

    1. 装饰器 即:在方法上应用装饰器(推荐使用)
    2. 类属性 即:在类中定义值为property对象的类属性(Python2历史遗留)

    装饰器

    在类的实例化方法上应用@property装饰器

    Python中的类有经典类和新式类,新式类的属性比经典类的属性丰富。(如果类继承object,那么该类是新式类)

    经典类,具有一种@property装饰器:

    ##########  定义  ##########
    class Goods:
        @property
        def price(self):
            return 'laowang'
    
    
    ##########  调用  ########
    obj = Goods()
    result = obj.price  # 自动执行 @property 修饰的price方法,并获取方法的返回值
    print(result)
    
    
    laowang
    
    

    新式类,具有三种@property装饰器:

    # coding = utf-8
    ############ 定义 ##########
    
    
    class Goods:
        '''
        python3中默认继承object类
        以Python2,3执行此程序的结果不同,因为只有在Python3中才有
        @xxx.setter  @ xxx.deleter
        '''
    
        @property
        def price(self):
            print('@property')
    
        @price.setter
        def price(self, value):
            print('@price.setter')
    
        @price.deleter
        def price(self):
            print('@price.deleter')
    
    
    ########## 调用 ############
    obj = Goods()
    obj.price       # 自动执行@property修饰price方法,并获取方法的返回值
    obj.price = 123  # 自动执行@price.setter修饰的price方法,并将123赋值给方法的参数
    del obj.price   # 自动执行@price.deleter修饰的price方法
    
    
    @property
    @price.setter
    @price.deleter
    
    

    注意:

    • 经典类中的属性只有一种访问方式,其对应被@property修饰的方法

    • 新式类中的属性有三种访问方式,并分别对应了三个被@property、@方法名.setter、@方法名.deleter修饰的方法

    由于新式类中具有三种访问方式,我们可以根据它们几个属性访问特点,分别将三个方法定义为对同一个属性:获取、修改、删除

    class Goods(object):
        def __init__(self):
    
            self.original_price = 100
    
            self.discount = 0.8
    
        @property
        def price(self):
    
            new_price = self.original_price * self.discount
            return new_price
    
        @price.setter
        def price(self, value):
            self.original_price = value
    
        @price.deleter
        def price(self):
            del self.original_price
    
    
    obj = Goods()
    obj.price
    obj.price = 200
    del obj.price
    
    

    类属性方式

    创建值为property对象的类属性

    注意:当使用类属性的方式创建property属性时,经典类和新式类无区别

    class Foo:
        def get_bar(self):
            return 'laowang'
    
        BAR = property(get_bar)
    
    
    obj = Foo()
    result = obj.BAR   # 自动调用get_bar方法,并获取方法的返回值
    print(result)
    
    
    laowang
    
    

    property方法中有四个参数

    1. 第一个参数是方法名,调用 对象.属性 时自动触发执行方法

    2. 第二个参数是方法名,调用 对象.属性 = xxx时自动触发执行方法

    3. 第三个参数是方法名,调用 del 对象.属性时自动触发执行方法

    4. 第四个参数是字符串,调用 对象.属性.__doc__,此参数是属性的描述信息

    # coding = utf-8
    class Foo(object):
        def get_bar(self):
            print('getter...')
            return 'laowang'
    
        def set_bar(self, value):
            '''必须两个参数'''
            print('setter...')
            return 'set value' + value
    
        def del_bar(self):
            print('deleter...')
            return 'laowang'
    
        BAR = property(get_bar, set_bar, del_bar, 'description...')
    
    
    obj = Foo()
    
    obj.BAR
    obj.BAR = 'alex'
    desc = Foo.BAR.__doc__
    print(desc)
    del obj.BAR
    
    
    getter...
    setter...
    description...
    deleter...
    
    
    • 定义property属性共有两种方式,分别是【装饰器】和【类属性】,而【装饰器】方法针对经典类和新式类又有所不同。

    • 通过使用property属性,能够简化调用者在获取数据的流程

    property + 类的属性

    class People:
        def __init__(self, name):
            self.__name = name
    
        @property  # 查看obj.name
        def name(self):
            return F'<名字是{self.__name}>'
    
    
    peo1 = People('tom')
    print(peo1.name)
    
    
    <名字是tom>
    
    
    try:
        peo1.name = 'jerry'
    except Exception as e:
        print(e)
    
    
    can't set attribute
    
    

    应用

    私有属性添加getter和setter方法

    class Money(object):
        def __init__(self):
            self.__money = 0
    
        def getMoney(self):
            return self.__money
    
        def setMoney(self, value):
            if isinstance(value, int):
                self.__money = value
            else:
                print('error:不是整型数据')
    
    

    使用property升级getter和setter方法

    class Money(object):
        def __init__(self):
            self.__money = 0
    
        def getMoney(self):
            return self.__money
    
        def setMoney(self, value):
            if isinstance(value, int):
                self.__money = value
            else:
                print('error:不是整型数据')
    
        # 定义一个数据,当对这个money设置值时调用setMoney,当获取值时调用getMoney
        money = property(getMoney, setMoney)
    
    
    a = Money()
    a.money = 100   # 调用setMoney方法
    print(a.money)    # 调用getMoney方法
    
    
    100
    
    

    使用property取代getter和setter方法

    重新实现一个属性的设置和读取方法,可做边界判定

    class Money(object):
        def __init__(self):
            self.__money = 0
    
        # 使用装饰器对money进行装饰,那么会自动添加一个叫money的属性,当调用获取money的值时,调用装饰的方法
        @property
        def money(self):
            return self.__money
    
        # 使用装饰器对money进行装饰,当对money设置值时,调用装饰的方法
        @money.setter
        def money(self, value):
            if isinstance(value, int):
                self.__money = value
            else:
                print("error:不是整型数字")
    
    
    a = Money()
    a.money = 100
    print(a.money)
    
    
    100
    
    

    练习

    计算圆的周长和面积

    import math
    
    
    class Circle:
        def __init__(self, radius):  # 圆的半径radius
            self.radius = radius
    
        @property
        def area(self):
            return math.pi * self.radius**2
    
        @property
        def perimeter(self):
            return 2 * math.pi * self.radius
    
    
    c = Circle(10)
    
    
    print(c.radius)
    
    
    10
    
    
    print(c.area)   # 可以向访问数据属性一样去访问area,会触发一个函数的执行,动态计算出一个值
    
    
    314.1592653589793
    
    
    print(c.perimeter)
    
    
    62.83185307179586
    
    

    类与对象的绑定方法和非绑定方法

    类中定义的方法大致可以分为两类:绑定方法和非绑定方法。其中绑定方法又可以分为绑定到对象的方法和绑定到类的方法。

    绑定方法

    对象的绑定方法

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

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

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

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

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

    类的绑定方法

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

    class Operate_database():
        host = '192.168.0.5'
        port = '3306'
        user = 'abc'
        password = '123456'
    
        @classmethod
        def connect(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_password(salt, password):
            m = hashlib.md5(salt.encode('utf8'))  # 加盐处理
            m.update(password.encode('utf8'))
            return m.hexdigest()
    
    
    hash_password = Operate_database.get_password('lala', '123456')  # 通过类调用
    print(hash_password)
    
    
    f7a1cc409ed6f51058c2b4a94a7e1956
    
    
    p = Operate_database('192.168.0.5', '3306', 'abc', '123456')
    hash_password = p.get_password(p.user, p.password)  # 也可以通过对象调用
    print(hash_password)
    
    
    0659c7992e268962384eb17fafe88364
    
    

    非绑定方法就是将普通方法放到了类的内部。

    练习

    假设我们现在有一个需求,需要让Mysql实例化的对象可以从文件setting.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
    
    

    绑定方法小结

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

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

    # 一种新的实例化方式:从配置文件中读取配置完成实例化
    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 0x000001C6F686E080>>
    
    
    print(obj.from_conf)
    
    
    <bound method Mysql.from_conf of <class '__main__.Mysql'>>
    
    

    非绑定方法小结

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

    obj.func(1, 2)
    
    
    不与任何人绑定
    
    
    Mysql.func(3, 4)
    
    
    不与任何人绑定
    
    
    print(obj.func)
    
    
    <function Mysql.func at 0x000001C6F672D598>
    
    
    print(Mysql.func)
    
    
    <function Mysql.func at 0x000001C6F672D598>
    
    
    print(obj.uid)
    
    
    05a7846c-94bc-11e9-8895-20898491368f
    
    
  • 相关阅读:
    How to determine proper SQL Server configuration settings [ZT from MS]
    How to Create a Performance Monitor Log for NT Troubleshooting [ZTfrom MS]
    HOW TO: Troubleshoot Application Performance with SQL Server[ZTfrom MS]
    INF: How to Monitor SQL Server 7.0 Blocking [ZT from MS]
    ADO.Net基础复习(一)
    JS实现跟随鼠标的魔法文字
    ADO.NET基础复习(二)
    SQL 基础复习
    关于Visual Studio无法连接到Visual Studio 的Localhost Web服务器问题
    计算机十二种常用密码破解法
  • 原文地址:https://www.cnblogs.com/WilliamKong94/p/11123185.html
Copyright © 2020-2023  润新知