zoukankan      html  css  js  c++  java
  • 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__': <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
    
  • 相关阅读:
    Linux 常用命令
    一些实用但不为人知的Unix命令
    Linux 驱动开发笔记(一)
    etcd数据备份和恢复--转发
    使用kubeadm 新加入节点(原始token过期后)---转发
    centos7 ipython安装
    Linux系统 jboss/Tomcat服务器pdf文件乱码问题
    k8s pod节点调度及k8s资源优化
    docker 常见问题处理汇总
    Grafana+Prometheus实现Ceph监控和钉钉告警-转载(云栖社区)
  • 原文地址:https://www.cnblogs.com/yangzhenwei123/p/6759311.html
Copyright © 2011-2022 走看看