1、封装
封装,顾名思义就是将内容封装到某个地方,以后再去调用被封装在某处的内容。
所以,在使用面向对象的封装特性时,需要:
1)将内容封装到某处
2)从某处调用被封装的内容
第一步:将内容封装到某处
class Foo: def __init__(self, name, age): self.name = name self.age = age #根据Foo类构造obj1对象,自动执行Foo类的__init__方法 #将Tom和20分别封装到self也就是obj1的name和age属性中 obj1 = Foo('Tom',20) #根据Foo类构造obj2对象,自动执行Foo类的__init__方法 #将Mike和25分别封装到self也就是obj2的name和age属性中 obj2 = Foo('Mike',25)
self 是一个形式参数,当执行 obj1 = Foo('Tom', 20)时,self等于obj1
当执行 obj2 = Foo('Mike', 25)时,self等于obj2
所以,内容其实被封装到了对象obj1和obj2中,每个对象中都有name和age属性,在内存里类似于下图来保存。
第二步:从某处调用被封装的内容
调用被封装的内容时,有两种情况:
1)通过对象直接调用
2)通过self间接调用
1、通过对象直接调用被封装的内容
上图展示了对象 obj1 和 obj2 在内存中保存的方式,根据保存格式可以如此调用被封装的内容:对象.属性名
class Foo: def __init__(self, name, age): self.name = name self.age = age obj1 = Foo('Tom', 20) print(obj1.name) # 直接调用obj1对象的name属性 print(obj1.age) # 直接调用obj1对象的age属性 obj2 = Foo('Mike', 25) print(obj2.name) # 直接调用obj2对象的name属性 print(obj2.age) # 直接调用obj2对象的age属性
2、通过self间接调用被封装的内容
执行类中的方法时,需要通过self间接调用被封装的内容
class Foo: def __init__(self, name, age): self.name = name self.age = age def detail(self): print(self.name) print(self.age) obj1 = Foo('Tom', 20) obj1.detail() # Python默认会将obj1传给self参数,即:obj1.detail(obj1),所以,此时方法内部的self = obj1,即:self.name是Tom,self.age是20 obj2 = Foo('Mike', 25 obj2.detail() # Python默认会将obj2传给self参数,即:obj1.detail(obj2),所以,此时方法内部的self = obj2,即:self.name是Mike, self.age是25
综上所述,对于面向对象的封装来说,其实就是使用构造方法将内容封装到对象中,然后通过对象直接或者self间接获取被封装的内容。
2、封装与扩展性
封装在于明确区分内外,使得类实现者可以修改封装内的东西而不影响外部调用者的代码;而外部使用用者只知道一个接口(函数),只要接口(函数)名、参数不变,使用者的代码永远无需改变。这就提供一个良好的合作基础——或者说,只要接口这个基础约定不变,则代码改变不足为虑。
#类的设计者 class Room: def __init__(self,name,owner,width,length,high): self.name=name self.owner=owner self.__width=width self.__length=length self.__high=high def tell_area(self): #对外提供的接口,隐藏了内部的实现细节,此时我们想求的是面积 return self.__width * self.__length #使用者 >>> r1=Room('卧室','egon',20,20,20) >>> r1.tell_area() #使用者调用接口tell_area #类的设计者,轻松的扩展了功能,而类的使用者完全不需要改变自己的代码 class Room: def __init__(self,name,owner,width,length,high): self.name=name self.owner=owner self.__width=width self.__length=length self.__high=high def tell_area(self): #对外提供的接口,隐藏内部实现,此时我们想求的是体积,内部逻辑变了,只需求修该下列一行就可以很简答的实现,而且外部调用感知不到,仍然使用该方法,但是功能已经变了 return self.__width * self.__length * self.__high #对于仍然在使用tell_area接口的人来说,根本无需改动自己的代码,就可以用上新功能 >>> r1.tell_area()
例子1:
class Parent: def func(self): print('in Parent func') def __init__(self): self.func() class Son(Parent): def func(self): print('in Son func') son1 = Son()
结果是打印了in Son func
在实例化Son类时的流程:
1、在内存中创建了一个空间存储实例化对象son1
2、将实例化对象son1的内存指针指向Son类
3、自动执行Son类中的__init__方法,并将实例化对象son1传给self
4、由于Son类中没有__init__方法,所以就从父类Parent中寻找__init__方法并执行
5、执行父类中的__init__方法,调用了self.func()函数,而此时self是实例化对象son1,所以执行的是son1的func方法
6、son1的func方法打印in Son func
例子2:
class A: a = 0 b = 1 def __init__(self): c = 222 d = A() d.a = 1 d.b = 2 d.c = {'1':1} e = A() print(e.a) print(e.b) print(e.c) 结果:0 1 报错'A' object has no attribute 'c'
1、创建A类时,在A类中定义了静态属性a和b,创建了特殊方法__init__,c=222并不是创建了静态属性,也没有返回c,所以c在内存中并不存在
2、实例化对象d=A(),在d的空间内创建了静态属性a,b,c,但并不会修改A类中的属性
3、实例化对象e=A(),e可以调用A类中的静态属性a和b,但是A类中并不存在静态属性c,所以e.c会报错