• 面向对象:多态(多态性)、封装(隐藏属性)、绑定方法与非绑定方法


    多态:

      多态指的是一类事物有多种形态;比如 动物有多种形态:人、狗、猪

    如下代码:

    import abc
    class Animal(metaclass=abc.ABCMeta): #同一类事物:动物
        @abc.abstractmethod
        def talk(self):
            pass
    
    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')
    
    """
    多态动态绑定(在继承的背景下使用时,也称为多态性): 多态性是指在不考虑实例(对象)类型的情况下使用实例
    多态性分为静态多态性和动态多态性
    静态多态性:如任何类型都可以用运算符“+”进行计算(站在“+”的角度,无需考虑加的是什么类型的实例)
    动态多态性:如调用方法(功能、函数)
    """
    
    # 多态性:不考虑实例类型的情况下使用实例,如下分析:
    # 不用考虑是人、是狗还是猪,只要是动物,都可以调用 talk()方法
    people = People()
    pig = Pig()
    dog = Dog()
    
    people.talk()
    pig.talk()
    dog.talk()
    
    # 上面调用talk()功能还能进一步优化,如下:
    def func(obj):
        obj.talk()   # 这种方式的好处:只需要写这一个接口就行,想让谁talk()传入谁就行,无需考虑是人、猪、狗(不用再考虑对象的类型)
    
    func(people)

     多态性的好处:

      1. 增加了程序的灵活性: 不变应万变,不论对象千变万化,使用者都是同一种形式去调用,如:func(obj);但需要注意的是,多态性是建立在多态的基础上

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

    class Cat(Animal):
        def talk(self):
            print("say miaomiao")
    
    def func(obj):
        obj.talk()   # 这种方式的好处:只需要写这一个接口就行,想让谁talk()传入谁就行,无需考虑是人、猪、狗(不用再考虑对象的类型)
    
    cat = Cat()
    func(cat)
    
    
    # 运行结果:
    # say miaomiao
    
    """这样我们新增了一个形态Cat,由Cat类产生的实例cat,使用者可以在完全不需要修改自己代码的情况下。使用和人、狗、猪一样的方式调用cat1的talk方法,即func(cat1)"""

    这种方式虽然也增加了程序的可扩展性,但却不是python崇尚的方式;Python崇尚的是鸭子类型,即“如果看起来像、叫声像而且走路像鸭子,那它就是鸭子”

    python程序员通常根据这种行为来编写程序。例如,如果想编写现有对象的自定义版本,可以继承该对象(如上述的Cat类),也可以创建一个外观和行为像、但与它没有任何关系的全新对象;后者通常用于保存程序组合的松耦合度

    鸭子类型:

    """
    class File:
        def read(self):
            pass
    
        def write(self):
            pass
    """
    
    class Disk:  # Disk(硬盘)不是File(文件),但却跟文件的方法很像;不用继承File,创建一个跟File相似的类
        def read(self):
            print("disk read")
    
        def write(self):
            print("disk write")
    
    class Txt:
        def read(self):
            print("txt read")
    
        def write(self):
            print("txt write")
    
    disk = Disk()
    txt = Txt()
    
    disk.read()  #把disk当文件对象去使用
    disk.write()
    txt.read() 
    txt.write()
    # 序列类型:列表list、元祖tuple、字符串str, 它们都有一个统计长度 .__len__() 的方法,其实现原理也是鸭子类型(只要是序列类型,就不用在乎是列表、元祖还是字符串,而且没有继承同一个父类)

     鸭子类型:不需要专门制作父类(或抽象类)来约束子类,只要做的像一点就能调用多态性

    封装:

    隐藏类的属性:

    """在属性前加上两个下划线,就把属性变成了隐藏属性;(属性前后都加两个下划线是python的内置函数)"""
    class A:
        __x = 1  #  通过 A.__dict__ 能够看出,隐藏属性发生了变形:由 __x 变成了 _A__x
    
        def __init__(self,name):
            self.__name = name  # 通过 a.__dict__ 能看出,__name 变成了:_A__name
    
        def __foo(self):   # 同理, _A__foo
            print("run foo")
    
    print(A.__dict__)
    
    # 运行结果:
    # {'__module__': '__main__', '_A__x': 1, '__init__': <function A.__init__ at 0x0000006AD34BB9D8>, '_A__foo': <function A.__foo at 0x0000006AD34BBA60>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None}
    
    a = A("neo")
    print(a.__dict__)
    
    # 运行结果:
    # {'_A__name': 'neo'}

    属性变形的特点:

      1. 在类的外部无法直接调用类的隐藏属性(如 无法通过 A.__x 的方式调用__x)

      2. 在类的内部可以直接调用类的隐藏属性(如 能够通过 .__foo(self)的方式调用 __foo(self)); 原理如下:

    class A:
        """类在定义阶段就会运行"""
        __x = 1  # 运行到这一步的时候把 __x = 1 变成了 _A.__x = 1
        
        def __init__(self,name):  # 运行到这一步的时候,程序不会执行 __init__(self,name) 函数,但会检测 __init__()函数中的语法
            self.__name = name    # 所以,程序检测函数语法的时候,会把 self.__name = name 变成 self._A__name = name
    
        def __foo(self):  # 同理, 经过语法检测, __foo(self) 会变成 _A__foo(self)
            print("run foo")
    
        def bar(self):
            self.__foo()   #  同理,经过语法检测,self.__foo() 也会变成 self._A__foo(), 这个函数名和 __foo(self)变形后的函数名一样的,所以能够调用
            print("from Bar")
    
    a = A("neo")
    a.bar()
    
    # 运行结果:
    # run foo
    # from Bar

       3. 子类无法覆盖父类 __ 开头的属性(隐藏属性)

    """子类无法覆盖父类隐藏的属性的原因"""
    class Foo:
        def __func(self):  # __func 已经变成了 _Foo__func
            print("from Foo")
    
    class Bar(Foo):
        def __func(self):  # __func 变成了 _Bar__func ; 变性后跟Foo中的 __func 名字已经不一样了  
            print("from Bar")

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

      1. 这种隐藏机制并没有真正意义上限制我们从外部直接访问属性,知道了类名和属性就可以拼出名字: _类名__属性,然后就可以直接访问了,如 a._A__x

      2. 变形的过程只在类的定义阶段发生一次,在定义后的赋值操作,不会变形

    class B:
        __x = 1  # 在类的定义阶段发生变形: _B__x = 1
    
        def __init__(self,name):
            self.__name = name
    
    B.__y = 2   # 这已经是在类的定义之后,所以不会再发生变形

      3. 在继承中,父类如果不想让子类覆盖自己的方法(函数等),可以将自己的方法定义为私有的(隐藏属性)

    """情况1:(前面讲过的知识点)"""
    class A:
        def foo(self):
            print("A.foo")
    
        def bar(self):
            print("A.bar")
            self.foo()
    
    class B(A):
        def foo(self):
            print("B.foo")
    
    b = B()
    b.bar()
    
    # 运行结果:
    # A.bar
    # B.foo
    
    """情况2"""
    
    class A:
        def __foo(self):  # 类定义阶段变成了 _A__foo
            print("A.foo")
    
        def bar(self):
            print("A.bar")
            self.__foo()  # 定义阶段变成了 _A__foo, 所以调用的是A的 __foo函数
    
    class B(A):
        def __foo(self): # 定义阶段变成了 _B__foo
            print("B.foo")
    
    b = B()
    b.bar()
    
    # 运行结果:
    # A.bar
    # A.foo

    封装的意义:

      1. 封装数据属性:将数据隐藏起来不是目的。隐藏起来后对外提供该数据的接口,然后我们可以在接口上附加上对该数据操作的限制,以此完成对数据属性操作的严格控制

    """ 能够明确区分类内类外,控制外部对隐藏属性的操作行为 """

    class
    People: def __init__(self,name,age): self.__name = name # 类内能够直接调用,但类外不能 self.__age = age def tell_info(self): # 通过隐藏属性,能够自己建一个接口,外部想调用隐藏的属性,必须调用我的接口; print("Name<%s> Age<%s>"%(self.__name,self.__age)) def set_info(self,name,age): # 而且我还能对这个接口加上自己的逻辑 if not isinstance(name,str): print("名字必须是字符串格式") return if not isinstance(age,int): print("年龄必须是数字") return self.__name = name self.__age = age people = People("neo",18) people.tell_info() # 运行结果: # Name<neo> Age<18> people.set_info(123,"NEO") people.tell_info() # 运行结果: # 名字必须是字符串格式 # Name<neo> Age<18> people.set_info("NEO",28) people.tell_info() # 运行结果: # Name<NEO> Age<28>

       2. 封装函数属性(方法):隔离复杂度

    """
    取款时功能,而这个功能有很多功能组成:插卡、密码认证、输入金额、打印账单、取钱
    对使用者来说,只需要知道取款这个功能即可,其余功能我们都可以隐藏起来,这么做就隔离了复杂度,同时提升了安全性
    """
    class ATM:
    
        def __card(self):
            print("插卡")
        def __auth(self):
            print("用户认证")
        def __input(self):
            print("输入取款金额")
        def __print_bill(self):
            print("打印账单")
        def __take_money(self):
            print("取款")
    
        def withdraw(self):
            self.__card()
            self.__auth()
            self.__input()
            self.__print_bill()
            self.__take_money()
    
    a = ATM()
    a.withdraw()
    
    # 运行结果:
    # 插卡
    # 用户认证
    # 输入取款金额
    # 打印账单
    # 取款

    附:封装(隐藏)起来的属性外部无法直接访问,你需要在内部为其建一个接口(函数)

    封装的扩展性:

    """定义一个房间的类"""
    class Room:
        def __init__(self,name,owner,length,width):
            self.name = name
            self.owner = owner
    
            self.__length = length
            self.__width = width
    
        def tell_area(self):
            return self.__length * self.__width
    
    room = Room("501房间","neo",10,9)
    print(room.tell_area())
    # 运行结果:
    # 90
    
    """现在这个房间想知道它的体积,只需要在__init__中再添加一个参数 height, tell_area 中也添加 height,用户想知道房间的体积继续调用 tell_area就行(即用户不用改变使用功能,直接就能用上扩展后的新功能),这样就实现了封装的扩展"""
    class Room:
        def __init__(self,name,owner,length,width,height):
            self.name = name
            self.owner = owner
    
            self.__length = length
            self.__width = width
            self.__height = height
    
        def tell_area(self):
            return self.__length * self.__width * self.__height
    
    room = Room("501房间","neo",10,9,3)
    print(room.tell_area())
    # 运行结果:
    # 270

    property用法:

      property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值 (一种“伪装”方法,把函数属性伪装成数据属性,并能够用调用数据属性的方式去调用这个函数属性)

    示例1:

    """
    例一:BMI指数(bmi是计算而来的,但很明显它听起来像是一个属性而非方法,如果我们将其做成一个属性,更便于理解)
    体质指数(BMI)=体重(kg)÷身高^2(m)
    """
    """常规做法"""
    class People:
    
        def __init__(self,name,height,weight):
            self.name = name
            self.height = height
            self.weight =weight
    
        def bmi(self):  # 在类中把BMI定义一个BMI的功能
            return self.weight / (self.height ** 2)
    
    people = People("egon",1.8,75)
    print(people.bmi())
    
    # 运行结果:
    # 23.148148148148145
    
    # 想要得到BMI就需要利用 bmi() 这个方法(函数);类中的数据属性的含义是 “什么是什么”,函数属性的含义是“什么去干什么”,
    # 但BMI听起来应该是一个数据属性,而不是一种方法,这样使用者也更容易理解。so 如下修改后的代码:
    
    class People:
    
        def __init__(self,name,height,weight):
            self.name = name
            self.height = height
            self.weight = weight
    
        @property  # property能够让方法当数据属性一样去调用,给使用者的感觉是他在调用一种数据属性而非方法;被property装饰的函数需要有return值
        def bmi(self):
            return self.weight / (self.height ** 2)
    
    people = People("egon",1.8,75)
    print(people.bmi)   # 此时想要调用bmi函数 直接people.bmi就行,跟数据属性的调用方式一样
    
    # 运行结果:
    # 23.148148148148145
    
    """被property装饰的函数的函数名不能再被当做变量去添加到其他对象(如people)的独有属性里面"""
    people.bmi = 24
    print(people.bmi)  # 会报错,因为此时bmi对应的是一种方法
    
    # 运行结果:
    # AttributeError: can't set attribute

    示例2:

    # 以下代码没有实际意义,仅作为分析使用
    class People:
    
        def __init__(self,name):
            self.__name = name  # 把self.name隐藏起来;此时外部无法调用name属性,所以需要再创建一个调用隐藏属性的接口
    
        def get_name(self):
            return self.__name
    
    people = People("egon")
    print(people.get_name())   # .get_name()给人的感觉还是在调用一种方法
    
    # 运行结果:
    # egon
    
    # 为上述代码添加property
    class People:
    
        def __init__(self,name):
            self.__name = name
    
        @property
        def name(self):
            return self.__name
    
    people = People("egon")
    print(people.name)   # 加上property后直接利用 people.name 调用
    
    # 运行结果:
    # egon
    
    """
    people.name = "EGON"  也不能用这种方式去赋值; 如果想对people.name 进行重新赋值,可用 @name.setter 方法, 想删除people.name,可用 @name.deleter 方法(了解性知识)
    """

    绑定方法与非绑定方法:

    在类内部定义的函数,分为两大类:

      一: 绑定方法:绑定给谁,就应该由谁来调用,谁来调用就会把调用者当做第一个参数(self)自动传入

          1. 绑定到对象的方法: 在类内定义的没有被任何装饰器修饰的

          2. 绑定到类的方法: 在类内定义的被装饰器修饰的

      二:非绑定方法: 不与类或者对象绑定;所以就不能自动传入参数,它只是类中定义的一个普通工具而已,并且对象和类都可以使用

    绑定方法示例解析:

    class Foo:
        def __init__(self,name):
            self.name = name
    
        def tell(self):  # 这是绑定方法中的绑定到对象的方法
            print("名字是%s" %self.name)
    
        @classmethod  # 加上 @classmethod  被装饰的函数就变成了绑定到类的方法
        def func(cls):
            print(cls)
    
    f = Foo("neo")
    
    print(Foo.tell)  # <function Foo.tell at 0x0000009F6354BA60>  # Foo.tell是一个函数,既然是函数就需要按照函数的方式执行,你需要自己传参
    Foo.tell(f)  #  手动传入f
    
    print(f.tell)  # <bound method Foo.tell of <__main__.Foo object at 0x0000009F6354AB38>>  # 是一种绑定方法,绑定方法会自动帮你传参
    f.tell()  # 把调用者自动传入self中
    
    print(Foo.func)  # <bound method Foo.func of <class '__main__.Foo'>>  # 加了 @classmethod 后,Foo.func也变成了一种绑定方式
    Foo.func()  # 跟 print(Foo) 效果一样
    
    # 运行结果:
    # <function Foo.tell at 0x0000009F6354BA60>
    # 名字是neo
    # <bound method Foo.tell of <__main__.Foo object at 0x0000009F6354AB38>>
    # 名字是neo
    # <bound method Foo.func of <class '__main__.Foo'>>
    # <class '__main__.Foo'>

    非绑定方法示例解析:

    还拿上面的示例代码分析:

    class Foo:
        def __init__(self,name):
            self.name = name
    
        @staticmethod  # 加上 @staticmethod 被装饰的函数就变成了 非绑定函数;只是类中定义的一个普通工具而已,类和对象都能使用
        def func1(x,y):
            print(x+y)
    
    
    f = Foo("neo")
    
    print(Foo.func1)
    print(f.func1)
    # 运行结果:
    # <function Foo.func1 at 0x00000009A482BA60>
    # <function Foo.func1 at 0x00000009A482BA60>
    
    
    Foo.func1(1,2)  # 跟普通函数的调用传参方式是一样的
    f.func1(1,3)
    # 运行结果:
    # 3
    # 4

    绑定方法和非绑定方法的使用:

      1.  """绑定到对象的使用"""

    class People:
        def __init__(self,name,age,gender):
            self.name = name
            self.age = age
            self.gender = gender
    
        def tell(self):   # 先把函数定义为 tell(),因为这个时候我还不确定需要往 tell里面传入什么参数;我需要根据函数体的功能代码去决定传入什么参数
            """我想实现的功能是 打印对象的name,age,gender 的信息"""
            print("Name:%s  Age:%s  Gender:%s" %(self.name,self.age,self.gender))  # %后面的内容是对象的name、age、gender,对象是有可能发生改变的,所以我需要把对象设置成参数传进来,所以tell内的参数应该是self, 即 tell(self)
    
    p = People("neo",22,"male")
    """绑定给对象,就应该由对象来调用,自动将对象本身当做第一个参数传入"""
    p.tell()
    # 运行结果:
    # Name:neo  Age:22  Gender:male

      2. """绑定给类的使用"""

    # 当前路径下有一个配置文件 setting.py
    # 内容如下:
    # name = "苍老师"
    # age = 22
    # gender = "female"
    
    import setting  # 导入配置文件
    class People:
        def __init__(self,name,age,gender):
            self.name = name
            self.age = age
            self.gender = gender
    
        def tell(self):
            print("Name:%s  Age:%s  Gender:%s" %(self.name,self.age,self.gender))
    
        @classmethod  # 根据功能体的代码分析出应该使用 @classmethod
        def read_config(cls):  # 先不往 read_config 函数里面定义参数,我需要根据函数体的功能目标来决定传入什么参数
            """我想实现的功能是 从配置文件中读取个人信息,然后读取到的name、age、gender传入到People中实例化"""
            obj = cls(
                setting.name,
                setting.age,
                setting.gender
            )   # 现在我需要的是传入到People这个类中进行实例化,假如现在有很多类,有可能需要把setting中的信息传入其他的类中进行实例化;如果现在写上People,就相当于把类写死了;所以应该把类当做变量传进来;而绑定给类的方法能够把类当做第一个参数自动传入,所以应该用 @classmethod 这种方法
            # 实例化这个功能写入了People这个类中成为了People的一种方法
            return obj
    
    # 绑定给类的, 就应该由类来调用,自动将类本身当做第一个参数传入
    people = People.read_config()  # People这个类 直接调用 read_config(),然后把People自己当做第一个参数传入read_config() # 接下来的效果:People实例化了setting中的name、age、gender,并实例化的对象是obj,把obj这个对象返回并赋值给people, 即people成了实例化后的对象
    people.tell()
    
    # 运行结果:
    # Name:苍老师  Age:22  Gender:female

      3. """非绑定方法"""

    import hashlib
    import time
    
    class People:
        def __init__(self,name,age,gender):
            self.name = name
            self.age = age
            self.gender = gender
            self.id = self.create_id()  # 为__init__ 添加一个 id的属性, id属性的值来源于 create_id()函数的返回值
    
        """功能目标:根据时间的不同利用hashlib,给对象自动生成一个id"""
        @staticmethod
        def create_id():  # 同理,create_id函数中先不写参数,后面功能体的代码再决定写入什么参数
            m = hashlib.md5(str(time.time()).encode("utf-8"))  # 再把m hexdigest就得到了想要的结果,可以发现这个函数不需要传参,也不需要对象或者类的自动传入,所以应该用 @staticmethod
            """
            hashlib.md5(str(time.time()).encode("utf-8")) 分析:
            这句代码的效果相当于: 
            m = hashlib.md5()
            m.update(str(time.time()).encode("utf-8"))   # m.update只能处理bytes格式,而time.time()是一个数字,先将它str,再利用 .encode("utf-8") 将其变成bytes格式
            """
            return m.hexdigest()
    
    people1 = People("neo",22,"male")
    time.sleep(0.1)
    people2 = People("苍老师",20,"female")
    time.sleep(0.1)
    people3 = People("美奈子",18,"female")
    
    print(people1.id)
    print(people2.id)
    print(people3.id)
    
    # 运行结果:
    # 972d042a8c4ab3f6647764d6acf685b6
    # ad91c2e65d01f0d1059178a587ca0342
    # 96bb3ba6d752fd04b4d09516a951cb39
  • 相关阅读:
    《2048》开发5——实现计分功能
    《2048》开发4——继续编辑GameView类,实现游戏逻辑
    《2048》开发3——编辑Card类
    robotframework(rf)中对时间操作的datetime库常用关键字
    弹框和单选框,复选框
    Selenium IDE安装与使用
    全面的功能测试点总结
    RF新手常见问题总结--(基础篇)
    常用断言关键字(rf中)
    jmeter录制(ios)app脚本
  • 原文地址:https://www.cnblogs.com/neozheng/p/8491195.html
Copyright © 2020-2023  润新知