python 封装 1 封装 2 为什么要封装 3 封装有哪些表现 4 类方法和静态方法

封装,即隐藏对象的属性和实现细节,仅对外公开接口。


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

     我们从上,可以看出,这种将变量变为“私有”的方式,只在类定义时有用。定义完成后,再在用这种方式并不能隐藏变量,此时只是当做普通变量。

这种将变量“隐藏”的特点:

  1. 类中定义的__x,可以在类中用self.__x或self.__xx()的方式,来访问变量或函数。但此时内部会自动转换为self._类名__x或self._类名__xx()。分析问题的时候,最好自己把它转换成改变后的形式。
  2. 这种变换形式是针对外部的访问,在外部无法通过__x这个名字来访问。
  3. 在子类定义的__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__),再将类作为第一个位置参数传入。

总结:

  1. 通常情况,只要是类内定义的,且不被任何装饰器修饰的函数,都是该类的对象的绑定方法。
  2. 凡是被@classmethod修饰的函数都是类的绑定方法。
  3. 绑定方法会自动把调用它的对象(或类)作为第一个位置参数传入,如果缺少这样一个参数会报错。并且该位置参数无论叫什么名字都可以(但通常我们在对象的绑定方法第一个位置参数写self,在类的绑定方法第一个位置参数写cls)。
  4. 类的绑定方法,该类的对象也可以使用。而且结果和类调用该类的绑定方法一样。(就是说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