python中封装的实现方法_python 封装

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__': , '_A__func': , '__dict__': , '__weakref__': , '__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__': , 'name': , 'set_name': , '__dict__': , '__weakref__': , '__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()

结果:

>

>

>

>

>

test4

test4

可以看到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()

结果:

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值