• 封装和多态


    封装和多态

    返回首页

    多态

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

    比如,动物类,人和猪都是自己的形态,但是他们都是动物。序列类型有多种形态:字符串,列表,元组。

    #多态:同一种事物的多种形态,动物分为人类,猪类(在定义角度)
    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()

    多态性:

    #####多态性:一种调用方式,不同的执行效果(多态性)
    def func(obj):
        obj.run()
    
    func(peo1)
    func(pig1)
    func(d1)
    peo1.run()
    pig1.run()
     
    #####多态性依赖于:
    #####     1.继承
    #####     2.
    #####多态性:定义统一的接口,可以传入不同类型的值,但是调用的逻辑都一样,执行的结果却不一样
    def func(obj): #obj这个参数没有类型限制,可以传入不同类型的值
        obj.run() #调用的逻辑都一样,执行的结果却不一样
    
    func(peo1)
    func(pig1)
    func(d1)

    封装

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

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

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

    第一个层面的封装(什么都不用做):创建类和对象会分别创建二者的名称空间,我们只能用类名. 或者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("from A")
        def bar(self):
            self.__foo()     #只有在类内部才可以通过__foo的形式访问到。

    这种自动变形的特点:

    1、类中定义的__X只能在内部使用,如self.__x,引用的就是变形的结果。

    2、这种变形其实正是针对外部的变形,在外部是无法通过__X这个名字访问到的。

    3、在子类定义的__X不会覆盖在父类定义的__X,因为子类变形成了:__子类名__X,而父类中变形成了:__父类名__x,即双下划线开头的属性在继承给子类时,子类是无法覆盖的。

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

    变形需要注意的问题:

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

    2、变形的过程只在类的定义是发生一次,在定义后的赋值操作,不会变形。

    3、在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的。

    封装的特性:

    property:是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值。

    被property装饰的属性,会优于对象的属性被使用,而被property装饰的属性,分为三种,有property、函数.setter、函数.deleter。

    import math
    class Circle:
        def __init__(self,radius):
            self.radius = radius   #圆的半径
        @property   #area = property(area)
        def area(self):
            """
            计算面积
            :return:
            """
            return math.pi * self.radius**2
    
        @property   #perimeter = property(perimeter)
        def perimeter(self):
            """
            计算周长
            :return:
            """
            return 2*math.pi * self.radius
    
    c = Circle(7)
    print(c.radius)
    # print(c.area())
    # print(c.perimeter() )
    ####可以像访问数据属性一样去访问area,会接触一个函数的执行,动态计算一个值。
    
    ###加了property装饰器的方法,可以不加括号运行,将函数变成了属性
    print(c.area)  #加了装饰器property的area方法,可直接运行
    print(c.perimeter)  #加了装饰器property的area方法,可直接运行

    使用property的好处:

    1、将一个类的函数定义成特性之后,对象再去使用的时候obj.name,根本无法察觉自己的name是执行了一个函数然后计算出来的,这种特性的使用方式遵循了统一访问的原则。

    2、就是封装。

    class Peopel(object):
        def __init__(self,name,age,height,weight):
            self.name = name
            self.age = age
            self.height = height
            self.weight = weight
        @property
        def bodyindex(self):
            return self.weight / (self.height**2)
    
    p1 = Peopel("george",29,1.75,98)
    # print(p1.bodyindex())
    p1.weight = 70
    print(p1.bodyindex)
    class Peopel(object):
        def __init__(self,name,sex):
            self.__Name = name
            self.__Sex = sex
    
        @property
        def tell_name(self):
            return self.__Name
    
        @property
        def sex(self):
            return self.__Sex
    
        @sex.setter   #这个sex是被property修饰的sex函数名结果,只要它被property修饰完,它就可以调用下面的.setter方法
        def sex(self,value):
            print(self,value)
            if isinstance(value,str):
                self.__Sex = value
            else:
                raise TypeError("性别必须是字符串类型")
    
        @sex.deleter
        def sex(self):
            del self.__Sex  #删掉的就是 p1.__Sex,del p1.__Sex
    
    p1 = Peopel("george","male")
    # print(p1._Peopel__Name)
    # print(p1.tell_name())
    print(p1.tell_name)
    print(p1.sex)
    # p1.tell_name = "wang"  #报错,因为tell_name实质上不是一个属性,而是函数,不能这样去改值。名字真实存放的位置在self.__Name里。
    p1.sex = "female"
    # p1.sex = 1
    # print(p1.sex)
    del p1.sex

    staticmethod:静态方法,位于类定义的命名空间中,不会对任何实例类型进行操作,python为我们内置了函数staticmethod来把类中的函数定义成静态方法。

    staticmethod的使用,正常的类中的函数方法也加上self参数,而加了@staticmethod方法的函数,可以不用加参数,也可以使用,不用加self,是给类自己用的方法。

    class Foo:
        def spam(x,y,z):  #类中的一个函数,千万不要懵逼,self和x啥的没有不同,都是参数名。
            print(x,y,z)
        spam = staticmethod(spam)   #把spam函数做成静态方法。

    基于之前的装饰器的知识,@staticmethod 等同于spam = staticmethod(spam),于是

    class Foo:
        @staticmethod   #装饰器
        def spam(x,y,z)
            print(x,y,z)

    使用演示:

    print(type(Foo.spam))  #类型本质就是函数
    Foo.spam(1,2,3)  #调用函数应该有几个参数就传几个参数
    
    f1 = Foo()
    f1.spam(3,3,3)  #实例也可以使用,但通常静态方法都是给类用的,实例在使用时丧失了自动传值的机制。

    应用场景:编写类时需要采用很多不同的方式来创建实例,而我们只有一个__init__函数,此时静态方法就派上用场了。

    import time
    class Date:
        def __init__(self,year,month,day):
            self.year=year
            self.month=month
            self.day=day
        #
        # def test():
        #     print('from test')
        #
        @staticmethod
        def now(): #用Date.now()的形式去产生实例,该实例用的是当前时间
            t=time.localtime() #获取结构化的时间格式
            obj=Date(t.tm_year,t.tm_mon,t.tm_mday) #新建实例并且返回
            return obj
    
        @staticmethod
        def tomorrow():#用Date.tomorrow()的形式去产生实例,该实例用的是明天的时间
            t=time.localtime(time.time()+86400)
            return Date(t.tm_year,t.tm_mon,t.tm_mday)
    d1=Date(2017,1,13)  #自己定义时间
    d2=Date.now()   #采用当期时间
    d3=Date.tomorrow()   #采用明天的时间
    # # Date.test()   
    # print(d1.test)
    # d1.test()
    
    print(d1.year,d1.month,d1.day)
    print(d2.year,d2.month,d2.day)
    print(d3.year,d3.month,d3.day)

       

    classmethod:把一个方法绑定给类:类.绑定到类的方法(),会把类本身当做第一个参数自动传给绑定到类的方法。

    class Foo:
        def bar(self):
            pass
        @classmethod #把一个方法绑定给类:类.绑定到类的方法(),会把类本身当做第一个参数自动传给绑定到类的方法
        def test(cls,x):
            print(cls,x) #拿掉一个类的内存地址后,就可以实例化或者引用类的属性了
    
    print(Foo.bar)
    print(Foo.test)
    Foo.test(123123)
    f=Foo()
    print(f.bar)
    print(f.test)
    print(Foo.test)
    f.test(1111)
    import time
    class Date:
        def __init__(self,year,month,day):
            self.year=year
            self.month=month
            self.day=day
    
        # def test():
        #     print('from test')
        @classmethod
        def now(cls): #用Date.now()的形式去产生实例,该实例用的是当前时间
            print(cls)
            t=time.localtime() #获取结构化的时间格式
            obj=cls(t.tm_year,t.tm_mon,t.tm_mday) #新建实例并且返回
            return obj
    
        @classmethod
        def tomorrow(cls):#用Date.tomorrow()的形式去产生实例,该实例用的是明天的时间
            t=time.localtime(time.time()+86400)
            return cls(t.tm_year,t.tm_mon,t.tm_mday)
    
    class EuroDate(Date):
        def __str__(self):
            return 'year:%s,month:%s,day:%s' %(self.year,self.month,self.day)
    
    # e1=EuroDate.now()  #这个e1是由Date产生的。
    # print(e1)
    
    e1=EuroDate(1,1,1)
    print(e1)

    __str__的用法:

    #__str__定义在类内部,必须返回一个字符串类型,
    #什么时候会出发它的执行呢?打印由这个类产生的对象时,会触发执行
    
    class People:
        def __init__(self,name,age):
            self.name=name
            self.age=age
        def __str__(self):
            return '<name:%s,age:%s>' %(self.name,self.age)
    
    p1=People('egon',18)
    print(p1)
    str(p1) #----->p1.__str__()

    总结:

    在类内部定义的函数无非三种用途:

    一、绑定到对象的方法:

      只要是在类内部定义的,并且没有被任何装饰器修饰过的方法,都是绑定对象的。 

    class Foo:
        def test(self):   #绑定到对象的方法
            pass
        def test1():   #也是绑定到对象的方法,只是对象.test1(),会把对象本身自动传给test1,因test1没有参数,所以会抛出异常。
            pass

      绑定到对象,指的是:就给对象去用。

      使用方法:对象.对象的绑定方法(),不用为self传值。

      特性:调用时会把对象本身当做第一个参数传给对象的绑定方法。

    二、绑定到类的方法:classmethod

      在类内部定义的,并且被装饰器@classmethod修饰过的方法,都是绑定到类的

    class Foo:
        def test(self):  #绑定到对象的方法
            pass
        def test1():     #也是绑定到对象的方法,只是对象.test1(),会把对象本身自动传给test1,因test1没有参数,所以会抛出异常。
            pass

      绑定到对象,指的是:就给对象去用。

      使用方法:对象.对象的绑定方法()。

      特性:调用时会把对象本身当做第一个参数传给对象的绑定方法。

    三、解除绑定的方法:staticmethod

      即不与类绑定,也不与对象绑定,不与任何事物绑定。

      绑定的特性:自动传值(绑定到类的就是自动传类,绑定到对象的就自动传对象)

      解除绑定的特性:不管是类还是对象来调用,都是没有自动传值这么一说了。

      所以说staticmethod就是相当于一个普通的工具包。

    class Foo:
        def test1(self):
            pass
        def test2():
            pass
    
        @classmethod
        def test3(cls):
            pass
        
        @classmethod
        def test4():
            pass
    
        @staticmethod
        def test5():
            pass
    #test1与test2都是绑定到对象方法:调用时就是操作对象本身。
    #test3与test4都是绑定到类的方法:调用时就是操作类本身。
    #test5是不与任何事物绑定的:就是一个工具包,谁来都可以用,没说专门操作谁这么一说。

      

    ------------ END -----------

  • 相关阅读:
    IDEA maven 项目编译忽略xml文件问题
    Mybatis Generator 路径和实体类要放的路径不一致 导致Could not resolve type alias
    Mybatis 错误:Error parsing Mapper XML. Cause: org.apache.ibatis.type.TypeException: Could not resolve type alias
    Spring Security使用报错 No bean named 'springSecurityFilterChain' is defined
    连接mysql失败 Path does not chain with any of the trust anchors
    IPFS客户端导致IDEA SSH登录错误,显示Connection reset
    Electron-从windows向electron拖动文件失败
    【Electron】renderer.js中无法require('electron').remote
    getElementsByNames无法获得对应的svg标签
    [React] setState更新成功不触发渲染,不常见的解决方法
  • 原文地址:https://www.cnblogs.com/george92/p/14581396.html
Copyright © 2020-2023  润新知