封装和多态

封装和多态

返回首页

多态

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

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

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

posted @ 2021-03-26 11:22  王先生是胖子  阅读(74)  评论(0编辑  收藏  举报