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
来源:https://www.cnblogs.com/yangzhenwei123/p/6759311.html