封装和多态
封装和多态
多态
多态是一类事物的多种形态。一个抽象类有多个子类,因而多态的概念依赖于继承。
比如,动物类,人和猪都是自己的形态,但是他们都是动物。序列类型有多种形态:字符串,列表,元组。
#多态:同一种事物的多种形态,动物分为人类,猪类(在定义角度) 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 -----------