Python面向对象编程-封装

来源:互联网 发布:c语言游戏编程 编辑:程序博客网 时间:2024/04/19 04:21

大家使用Python这门语言,大多是作为一门脚本语言来使用。这里熟悉下python的面向对象的特性。

1、几个基本概念

1.1 面向过程和面向对象

根据操作数据的函数或语句块来设计程序的过程,叫做面向过程编程。与之相对应的,如果把数据和功能结合起来,用称为对象的东西包裹起来,这种组织程序的方法称为面向对象的编程。面向对象的编程一般用在稍微大型的项目或者是要求更加合理的解决方案的场合,相对与面向过程的编程有优势。

1.2 面向对象的特性

面向对象的编程有三个特性:封装、继承和多态,这里会分别作介绍。

1.3 类和对象

类就是一个新的、程序员自己根据需求定义的一个新的类型,而对象是这个类型的一个实例。比如说,定义了一个新的类型Person,然后用这个类型定义了一个变量tom,这里,tom就是一个对象,而Person就是一个类。

2、封装

面向对象的程序设计中,某个类把所需要的数据(也可以说是类的属性)和对数据的操作(也可以说是类的行为)全部都封装在类中,分别称为类的成员变量和方法(或成员函数)。这种把成员变量和成员函数封装在一起的编程特性称为封装。

2.1 类的属性

类由属性和方法组成,类的属性是对数据的封装,而类的方法是对类的行为的封装。类的属性按使用范围分为共有属性和私有属性。具体地,在python实现面向对象的编程思想的时候,封装在类中的属性可以分为两种数据类的属性和数据对象的属性(也可以成为数据类的成员变量和属于对象的成员变量),其中,这两种成员变量又各自分为共有成员变量和私有成员变量。

2.1.1 类的成员变量和对象的成员变量

类的成员变量定义在类中,和类的成员函数在同一缩进等级。而对象的成员变量定义在__init__(self)成员函数中,和__init__(self)函数下的变量和语句在同一等级。通俗地讲,类的成员变量属于类,共类的所有对象和类本身所共有,也就是说所有的类的对象和类只有一份这样的变量。而对象的成员变量属于类的对象本身,每个对象都有一份,而且各个对象之间互不影响。

2.1.2 公有成员变量和私有成员变量

python中用成员变量的名字来区分是共有成员变量或者是私有成员变量。python中,以两个下划线‘__’开头的变量都是私有成员变量,而其余的变量都属于公有成员变量。其中,私有的成员变量只能在类的内部访问,而共有的公有的成员变量可以在类的外部进行访问。

总结起来有如下的四种成员变量其使用方法如下图1。


图1 类属性的访问权限

要注意的是,这里有一个例外但是紧紧用来体调试程序。可以通过“objName._ClsName__attriName”的方式来访问类的私有变量,但是注意这种方式也仅仅用于调试程序(比如可以用tom._Person__gold来访问Person类的私有成员变量__gold)。

除了上面提到的属性之外,类还有一种属性叫内置属性,它们是在系统定义类的时候默认添加的,由前后两个下划线命名。

  • __dict__ : 类的属性(包含一个字典,由类的数据属性组成)
  • __doc__ :类的文档字符串
  • __name__: 类名
  • __module__: 类定义所在的模块(类的全名是'__main__.className',如果类位于一个导入模块mymod中,那么className.__module__ 等于 mymod)
  • __bases__ : 类的所有父类构成元素(包含了以个由所有父类组成的元组)

2.2 类的方法

2.2.1 公有方法和私有方法

如上面所说,类的方法是对类行为的封装。类的方法也分为公有方法和私有方法。类的私有方法只能通过对象名(在类内部也就是self)在类的内部进行访问,而公有方法可以在类的外部通过对象名进行访问。和属性不同的是,一般意义上的类方法属于对象,也就是说只有通过对象才可以进行调用,不能直接通过类名进行调用。一般类方法的第一个参数必须是代指类对象本身的(一般我们常用self,实际上可以是任何自定义的名字,只不过self是大家约定俗成的用法,在下面介绍的类方法中,大家一般用cls,因为那里更多地标识的是一个类),可以通过self访问类对象的成员函数和数据。

同样,公有的成员函数和私有的成员函数也是通过名字来区分的,双下划线‘__’开头的函数是私有成员函数。

2.2.2 类方法和静态方法

python中对象有两种:经典对象和新型对象。经典对象就是不继承object父类定义出来的对象,新型对象就是要继承object类定义出的对象。新型对象和经典对象最大的区别就是,新型对象中提供了对类方法和静态方法的支持。

类方法就是被classmethod()函数处理过的函数,能够直接通过类名进行调用,也能够被对象直接调用。静态方法相当于类层面的全局函数,可以被类直接调用,可以被所有实例化对象共享,通过staticmethod()定义静态方法,静态方法没有self参数。当然,也可以用更加方便的装饰器@classmethod和@staticmethod来定义类方法和静态方法。

下面的代码说明了类方法和静态方法的区别:

class A ( object ):       def foo ( self , x ):           print "executing foo(%s,%s)" %( self , x )         @classmethod       def class_foo ( cls , x ):           print "executing class_foo(%s,%s)" %( cls , x )         @staticmethod       def static_foo ( x ):           print "executing static_foo(%s)" % x  
下面是对象实例调用方法的通常做法。对象实例A,隐式传递给方法的第一个参数。(译者加:也就是将实例a对象传递给foo方法的self参数)。
a.foo(1)   
# executing foo(<__main__.A object at 0xb7dbef0c>,1)
类方法的方式,类的对象实例作为隐式的参数传递给第一个参数而不是self。(译者加:也就是将类A传递给对象实例的class_foo方法的cls参数)
a.class_foo(1)   
# executing class_foo(<class '__main__.A'>,1)
你甚至可以通过类调用class_foo方法。其实,如果你定义类方法内容,可能你就是想通过类调用它而不是通过对象实例。A.foo(1)会引发类型错误异常(TypeError),A.class_foo(1)不会。   
而静态方法,既不需要self(对象实例)也不需要cls(类)作为隐藏参数被传递。
a.static_foo(1)
# executing static_foo(1)   
foo只是个函数,但是当你调用a.foo时你不是简单的调用函数,实际上你调用了一个加工过的函数版本:绑定对象实例a作为第一个参数的函数。foo需要2个参数,而a.foo只需要一个函数(译者加:对象实例a自动传递给了第一个参数self嘛)
a绑定到了foo。这就是为什么下面的终端输出"bound"的原因。
print (a.foo)   
# <bound method A.foo of <__main__.A object at 0xb7d52f0c>>   
而a.class_foo,对象实例a是没有绑定到foo的,而是类A绑定到foo。
print (a.class_foo)   
# <bound method type.class_foo of <class '__main__.A'>>
这里,静态方法,虽然是一个方法,但是a.static_foo只是一个没有绑定任何参数的完好的函数。static_foo需要1个参数,同样a.static_foo也只需要一个参数。
print (a.static_foo)   
# <function static_foo at 0xb7d479cc>
[1]以上内容来自下面的参考链接

2.2.2 构造函数和析构函数

2.2.2.1 __init__和__del__

python中的构造函数是__init__(),用于初始化类对象的初始化,__init__()方法是可选的,如果不提供,python会给出一个默认的__init__()方法。python中的析构函数是__del__(),用于释放对象占用的资源。python中析构函数也是可选的,如果不提供,则python会提供默认的析构函数。如果要显示调用析构函数,可以使用del关键字。

关于__init__()构造函数要注意点,上面说到对象的变量定义在__init__()函数中。其实,除了__init__()函数,其他函数通过传入的self参数,也可以给对象增加一个变量,在其它对象中可以访问到。这里,上面之所以这样说,是因为__init__()函数的好处是不用自己显示调用,在对象创建的时候,自动被调用。这样 ,在__init__()中定义的变量就会有一个效果就是在对象存在的时候,它们就已经存在了,这实际上更符合我们对于对象的变量的期望,因此,我们就说对象的变量一般定义在__init__函数中。

2.2.2.2 python的垃圾回收机制

Python采用垃圾回收机制来清理不再使用的对象,python中提供了gc模块来释放不再使用的对象。python采用“引用计数”的方式来处理回收,也就是说当某个对象在其作用域内再也不被其它对象引用的时候,Python就自动清除对象。Python中也提供了collect()函数,来一次性收集所有待处理的对象,并返回待处理对象的数目(gc.collect())。

3、例子

3.1 该例子中涵盖了python成员函数、类变量,对象变量的各种访问和调用方式。

#!/usr/bin/python#-*-coding:utf8-*-class Person():    #人口数和土地面积属于人类共享的信息,设置为类公有变量    population = 0    earth = 10000    #诺亚方舟能再多少人,自然不应该让每个人都知道,设置为类私有变量    __noahCapacity =10    def __init__(self, name, age, salary):        #名字就是用来让大家知道的,每个人不一样,所以这里设置为对象的公有变量        self.name = name        #年龄也类似,设置为对象的公有变量        self.age = age        #薪水当然属于每个人都很敏感的啦,设置为对象的私有变量        self.__salary = salary    def __privateFun(self):        print "这是私有成员函数的输出"    #在类的内部访问对象的公有和私有变量    def printInstance(self):        print "*"*50,"公有成员函数被调用","*"*50        print "在类内部可以通过self访问对象的公有和私有变量:"        print "name: %s age: %d salary:%d" % (self.name, self.age, self.__salary)        print "通过类内部的成员函数,在类的内部调用私有成员函数:"        self.__privateFun()        print "*"*120    #在类的内部访问类的公有变量和私有变量    def printClass(self):        print "-"*50,"公有成员函数被调用","-"*50        print "在类内部可以通过self访问类的公有和私有变量:"        print "population: %d earth: %d NoahCapacity: %d" % (self.population, self.earth, self.__noahCapacity)        print "在类内部也可以通过类名访问类的公有和私有变量:"        print "population: %d earth: %d NoahCapacity: %d" % (Person.population, Person.earth, Person.__noahCapacity)        print "-"*120if __name__ == "__main__":    tom = Person("Tom", 24, 9000)    print "在类外部访问属性操作","|"*100    print "在类外部可以通过对象名访问类的公有变量:"    print "population: %d earth: %d" % (tom.population, tom.earth)    print "在类外部也可以通过对象名用特殊方法访问类的私有变量(仅用于调试):"    print "NoahCapacity: %d" % tom._Person__noahCapacity    print "在类外部可以通过对象名访问对象的公有变量:"    print "name: %s age: %d" % (tom.name, tom.age)    print "在类外部也可以通过类名访问类的公有变量:"    print "population: %d earth: %d" % (Person.population, Person.earth)    print "|"*120    print ""    print "在类的外部调用类的成员函数","|"*100    print "在类外通过对象名调用类的公有成员函数"    tom.printInstance()    tom.printClass()    print "|"*120
运行效果稍显乱,不过还好,如下图2。


图2 类的成员的访问和调用

3.2 静态方法和类方法

#!/usr/bin/python#-*-coding:utf8-*-class A (object):       attri = "类公有属性1"    __attri = "类私有属性1"    def foo(self, x):           print "executing foo(%s,%s)" % (self, x)       @classmethod       def class_foo(cls, x):           print cls.attri,cls.__attri        print "executing class_foo(%s,%s)" % (cls, x)       @staticmethod     def static_foo(x):           print "executing static_foo(%s)" % x    #如下的方法,用classmethod方法也可以将一个方法处理成类方法    #def fun1(cls, x):       #    print "executing class_foo(%s,%s)" % (cls, x)       #class_foo = classmethod(fun1)    #如下的方法,用classmethod方法也可以将一个方法处理成类方法    #def fun2(x):       #   print "executing static_foo(%s)" % x    #static_foo = staticmethod(fun2)if __name__ == "__main__":    a = A()    a.foo(1)    a.static_foo(12)    A.static_foo(12)    a.class_foo(33)    A.class_foo(33)
运行结果如下图3。


图3 静态方法和类方法

从上面的结果可以看出,实际上,类方法是绑定到类的,类方法的第一个参数代表的实际是一个类,可以通过它来访问类的成员变量。静态方法没有self参数,静态方法只不过是一个放在类内部的一个函数罢了,除了放的位置和调用的时候要加一个类名或者对象名限定之外,貌似和一个类外部的函数没有多少区别。

1 0