1 封装
封装,即隐藏对象的属性和实现细节,仅对外公开接口。
2 为什么要封装
封装数据:可以保护隐私(比如银行卡号、密码)
封装方法:隔离复杂度(把内部具体的复杂实现过程隐藏起来。)
在python中因为没有像java中那样的接口实现。所以我们这里说的向外提供的接口,是函数,也叫接口函数。
3 封装有哪些表现
3.1 python自带的封装
创建一个类或对象,就会创建二者的命名空间,只需要用类名.或对象.的方式访问命名空间里的变量名,就是一种封装。
>>> r1.nickname
'德玛西亚之力'
>>>Riven.camp
'Noxus'
3.2 类中的封装
将类中的某些变量属性和方法隐藏(或者说定义为私有),只在类内部使用、访问,或留下少量函数接口给外部访问。
在python中,在变量名或函数名前加“__”来实现属性的隐藏(设置为私有)
class A:
__x=0
def __init__(self):
self.__y = 10
def __func(self):
print("from A")
a=A()
#print(a.__y) #AttributeError: 'A' object has no attribute '__y'
#a.__func() #AttributeError: 'A' object has no attribute '__func'
print(a.__dict__)
print(A.__dict__)
结果:
{'_A__y': 10}
{'__module__': '__main__', '_A__x': 0, '__init__': <function A.__init__ at 0x000002D5F8E9BAE8>, '_A__func': <function A.__func at 0x000002D5F8E9BB70>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None}
可以发现,并不能直接用a.__y和a.__func()来访问以双下划线开头的变量名。查看对象命名空间,会发现并不存在__y和__func(),取而代之的是_A__y和_A__func()。
print(a._A__y)
a._A__func()
结果:
10
from A
我们会发现python并没有真正把变量名变为私有,只是将变量名和函数名前面加上了“_类名__变量名和_类名__函数名”。
a.__n=100
#print(a._A__n) #AttributeError: 'A' object has no attribute '_A__n'
print(a.__n) #100
我们从上,可以看出,这种将变量变为“私有”的方式,只在类定义时有用。定义完成后,再在用这种方式并不能隐藏变量,此时只是当做普通变量。
这种将变量“隐藏”的特点:
- 类中定义的__x,可以在类中用self.__x或self.__xx()的方式,来访问变量或函数。但此时内部会自动转换为self._类名__x或self._类名__xx()。分析问题的时候,最好自己把它转换成改变后的形式。
- 这种变换形式是针对外部的访问,在外部无法通过__x这个名字来访问。
- 在子类定义的__x并不会覆盖父类的__x,因为子类会变转换成_子类名__x,而父类会转换成_父类名__x,即双下滑线开头的属性在继承给子类时,子类是无法覆盖的。
注意:对于这一层面的封装(隐藏),我们需要在类中定义一个函数(接口函数)在它内部访问被隐藏的属性,然后外部就可以使用了。
通过添加“__”来达到隐藏属性的目的,但并不能完全隐藏。所以“__”只是为了告诉别人这个属性是隐藏的,不要直接访问。那又该如何来访问呢?(这里可以参照一下java中的get、set方法来访问私有变量的方式)
class A:
def __init__(self,x):
self.__x=x
def get_x(self):
return self.__x
def set_x(self,x):
self.__x=x
a=A(123)
print(a.get_x())
a.set_x(456)
print(a.get_x())
结果:
123
456
通过get_x()来获取“__x”的值,用set_x()来修改“__x”的值。而不直接使用a._A__x。这样达到将属性封装的效果。
在使用类的封装时,需要注意的问题
看两个例子:
例子1:
class A:
def fa(self):
print("from A")
def test(self):
self.fa()
class B(A):
def fa(self):
print("from B")
B().test()
结果:
from B
例子2:
class A:
def __fa(self): #_A__fa
print("from A")
def test(self):
self.__fa() #self._A__fa
class B(A):
def __fa(self):
#print(B()._A__fa)
print("from B")
# print(A._A__fa)
# B()._A__fa() #from A
B().test()
结果:
from A
3.3 property
python还为我们提过了一种将函数封装成“变量属性”的办法。通过用@property修饰函数,这样我们在访问的时候,只需要 对象.函数名 就可以访问了。
先来看看是如何定义的:
class People:
def __init__(self,name):
self.__name=name
@property
def name(self):
return self.__name
def set_name(self,name):
self.__name = name
p1=People("yang")
print(p1.name) #yang
结果:
yang
{'__module__': '__main__', '__init__': <function People.__init__ at 0x0000020A0939BAE8>, 'name': <property object at 0x0000020A092A6688>, 'set_name': <function People.set_name at 0x0000020A0939BBF8>, '__dict__': <attribute '__dict__' of 'People' objects>, '__weakref__': <attribute '__weakref__' of 'People' objects>, '__doc__': None}
此时p1.name,就是去寻找People下的name变量名。此时的name是一个property对象。该对象下面有getter、setter、deleter等方法,这个我们后面说。当我们用p1.name,此时就会调用被@property修饰的方法(其实会优先调用,name下的getter,但没写getter方法,所以直接调用被@property的name)
下面再来看下,@property下的其他方法
class People:
def __init__(self,name):
self.__name=name #此时是直接调用@name.setter
@property #产生一个property对象name
def name(self):
print("@property")
@name.setter
def name(self,name):
print("@name.setter")
@name.deleter
def name(self):
print("@name.deleter")
p1=People("yang")
p1.name
p1.name="zzz"
del p1.name
结果为:
@property
@name.setter
@name.deleter
我们可以看到:
p1.name 会自动调用被@property修饰的name
p1.name="zzz" 由于此时有一步赋值操作,会自动调用name下的setter
del p1.name 由于此时有一步删除操作,会自动调用name下的deleter
那么getter又是怎样的?
class People:
def __init__(self,name):
self.__name=name #此时是直接调用@name.setter
@property
def name(self):
print("@property")
@name.getter
def name(self):
print("@name.getter")
p1 = People("yang")
p1.name
结果:
@name.getter
由于此时name写了getter方法,p1.name不再是返回@property修饰的name。(但通常情况两者实现的功能类似,所有都没怎么用getter)
那么我们用@property怎么实现上述set_x,get_x的功能?
class People:
def __init__(self,name):
self.name=name #此时是直接调用@name.setter
@property
def name(self):
return self.__name
@name.setter
def name(self,name):
self.__name = name
@name.deleter
def name(self):
del self.__name
p1=People("yang")
print(p1.name) #yang
p1.name="zzz"
print(p1.name) #zzz
#del p1.name
#print(p1.name) #AttributeError: 'People' object has no attribute '_People__name'
print(p1.__dict__)
打印结果:
yang
zzz
{'_People__name': 'zzz'}
在__init__()中的语句“self.name=name”,这里self.name并不是给对象添加一个name属性,而是调用下面被@property修饰的name。从最后对象的局部命名空间,也不难发现,对象的属性里并没有name,只有一个变形了的_People__name,它的原形__name是在p1.name="zzz"时修改的,而添加是在p1=People("yang")。
由此可见,@property修饰的属性,要比对象属性优先访问。
再说一种property的用法:
class People:
def __init__(self,name):
self.__name=name
def get_name(self):
return self.__name
def set_name(self,name):
self.__name = name
name=property(get_name,set_name)
p1=People("yy")
print(p1.get_name())
p1.name="zz"
print(p1.name)
结果:
yy
zz
但这种,不如装饰器表达的清晰。
对比@property和set_x、get_x两种方式实现的效果,@property能实现更好的封装效果。@property将对象对方法的调用,都变成了对象.函数名,这样外部也以为自己只是调用的一个数据属性,同时也遵循了统一访问的原则。并且可以加入限制条件。
4 类方法和静态方法
4.1 类方法
通常情况,只要是类内定义的,且不被任何装饰器修饰的函数,都是该类的对象的绑定方法。对象在调用时,会自动将对象作为第一个位置参数传给函数。
而python同样也为我们提供了类的绑定方法。凡是被@classmethod修饰的函数都是类的绑定方法。类(或对象)在调用时,会自动将类(或对象)作为第一个位置参数传入。
class Foo:
def test1(x): # 绑定到对象的方法
print("test1")
def test2(): # 也是绑定到对象的方法,只是对象.test1(),会把对象本身自动传给test1,因test1没有参数所以会抛出异常
print("test2")
@classmethod
def test3():
print("test3")
@classmethod
def test4(cls):
print("test4",cls)
f=Foo()
print(f.test1)
print(f.test2)
#f.test2() #TypeError: test2() takes 0 positional arguments but 1 was given
print(Foo.test3)
print(Foo.test4)
print(f.test4)
#Foo.test3() #TypeError: test3() takes 0 positional arguments but 1 was given
Foo.test4()
f.test4()
结果:
<bound method Foo.test1 of <__main__.Foo object at 0x000001CCB0F3AB00>>
<bound method Foo.test2 of <__main__.Foo object at 0x000001CCB0F3AB00>>
<bound method Foo.test3 of <class '__main__.Foo'>>
<bound method Foo.test4 of <class '__main__.Foo'>>
<bound method Foo.test4 of <class '__main__.Foo'>>
test4 <class '__main__.Foo'>
test4 <class '__main__.Foo'>
可以看到test1和test2都是Foo对象的绑定方法。
test3和test4是类的绑定方法。
从test2和test3报错,我们可以看出。绑定方法,会把调用它的对象(或类),作为第一个位置参数传进去。如果一个形参也没有,就会报错。
从test1可以看出,第一个位置参数,就是一个形参,无论是写self或其他名字都是可以的。
从f.test4和f.test4()与Foo.test4和Foo.test4(),可以看出来,类的绑定方法,也可以给类的对象使用,且不需要传参,但结果却和类自己调用一样。此时python做了一步额外的操作,先把该对象的类获取出来(f.__class__),再将类作为第一个位置参数传入。
总结:
- 通常情况,只要是类内定义的,且不被任何装饰器修饰的函数,都是该类的对象的绑定方法。
- 凡是被@classmethod修饰的函数都是类的绑定方法。
- 绑定方法会自动把调用它的对象(或类)作为第一个位置参数传入,如果缺少这样一个参数会报错。并且该位置参数无论叫什么名字都可以(但通常我们在对象的绑定方法第一个位置参数写self,在类的绑定方法第一个位置参数写cls)。
- 类的绑定方法,该类的对象也可以使用。而且结果和类调用该类的绑定方法一样。(就是说python会先把该对象的类获取出来(f.__class__),再将类作为第一个位置参数传入。)
4.2 静态方法
python中类直接调用自身的不被任何装饰器修饰的函数,就是调用函数。但类的对象调用这些函数,默认就是调用绑定方法。那如何让对象也能像类那样调用的是函数,而不是绑定方法。
这里就要引入另一个装饰器@staticmethod。从名字可以看出,叫静态方法,也可以叫解除对象的绑定。
被@staticmethod修饰的函数,对象调用时,不再作为绑定方法来用,而是作为普通的函数来调用。此时不再将对象作为第一个位置参数传入。
class Foo:
@staticmethod
def test5():
print("test5")
f=Foo()
print(f.test5)
print(Foo.test5)
f.test5()
Foo.test5()
结果:
<function Foo.test5 at 0x00000176D9CFBD90>
<function Foo.test5 at 0x00000176D9CFBD90>
test5
test5
结合4.1中的例子来看,类方法(也叫静态方法),既不是类的绑定方法,也不是对象的绑定方法。对象和类都可以像调用普通函数一样调用它。
既然叫类方法,通常都是给类用的。类的作用一个是属性引用,一个是实例化对象。
这里就来说一下,如何用@staticmethod来实例化对象。
import time
class Date:
def __init__(self,year,month,day):
self.year = year
self.month = month
self.day = day
@staticmethod
def now():
t=time.localtime()
return Date(t.tm_year,t.tm_mon,t.tm_mday)
@staticmethod
def tomorrow():
t=time.localtime(time.time()+86400)
return Date(t.tm_year,t.tm_mon,t.tm_mday)
def __str__(self):
return "%s 年 %s 月 %s 日"%(self.year,self.month,self.day)
d1=Date(2017,1,1)
a=d1.now()
b=d1.tomorrow()
print(a.year,a.month,a.day)
print(b.year,b.month,b.day)
结果:
2017 4 23
2017 4 24