CSDN 11-09
光棍节就要到了,要不要给你介绍个 Python 对象?| CSDN 博文精选
index_new4.html
../../../zaker_core/zaker_tpl_static/wap/tpl_keji1.html

 

作者 |   天元浪子

责编   | 刘静

出品 |   CSDN 博客

前言

光棍节就要到了,一说介绍对象,我猜你一定想到了派森大叔家的克蕾丝(class)小姐姐和黛夫(def)小哥哥。别想入非非了,严肃点儿!我们今天的的话题,不是介绍男女朋友,而是讲解如何面向对象编程,也就是程序员常说的 OOP 啦。

不知道前辈们为什么会把 Object Oriented Programming 翻译成面向对象编程,搞得单身程序员经常心猿意马地产生幻觉,以为屏幕上的俊男美女就是自己将来要面对的对象了。说到这里,我觉得还是台湾同行的计算机术语翻译得较为恰当。比如,cache,我们叫 " 缓存 ",人家叫 " 快取 ",音意俱佳。台湾同行把 OPP 翻译成 " 物件导向编程 " ——虽然同样不明觉厉,但至少可以让单身程序员暂时忘记没有 " 对象 " 的烦恼。

限盐少许,本博主正式开始为大家介绍对象。

类和对象的概念

学习面向对象编程,首先得搞明白,什么是对象?类是什么?实例化是什么意思?下图表达了我对 OOP 这几个基本概念的理解(实际上是妥协的结果——我和我的同事们讨论了很久,并翻墙参考了维基百科的说法)。

类是对我们要处理的客观事物的抽象。类用来描述具有相同的属性和方法的对象的集合,它定义了该集合中每个对象所共有的属性和方法。对象是类在内存的实例,一个类可以实例化为多个对象。类是抽象的,不占用内存,而对象是具体的,占用存储空间。

类的成员

作为 Python 初学者,大可不必把精力花费在令人费解的概念上,只需要掌握使用类的基本要素就可以了。未来的日子里,你有足够多的时间慢慢体会 OOP 的博大精深。随着经验的积累,OOP 会自然而然地成为你的思维工具。

下面的代码,定义了一个名为 A 的类。所有的类,都有构造函数和析构函数,此外,还可以包含成员函数和成员变量。我喜欢把成员函数叫做类的方法,把成员变量叫做类的属性。

class   A:

     def   __init__ ( self ) :

         """   构造函数 """

         self.a   =   10   #   定义了一个成员变量 a      

     def   getA ( self ) :

         """ 成员函数 """

         print ( "a=%d"   %   self.a )

     def   __del__ ( ) :

         """ 析构函数 """

         print ( "delete   object" )

a   =   A ( )

a.getA ( )

当类被实例化为对象时,首先执行构造函数,当对象被销毁时,会自动执行析构函数。一般的,我们会在构造函数中进行初始化工作,在析构函数中进行清理工作。

读到这里,有很多初学者一定会说:我定义类的时候,写过构造函数,但从没有写过析构函数,你为什么说所有的类都有构造函数和析构函数呢?没错,定义类的时候,即便我们不写构造函数和析构函数,这两个方法也照样存在(析构函数稍微有点特殊,我们不能直接看到它——除非是我们自己定义的)。如果我们自己定义了构造函数和析构函数,则将会取代系统自动赋予的这两个函数。下面的例子清晰地说明了其中的奥秘:类 A 既没有构造函数,也没有析构函数,类 B 只有析构函数,两个类都可以生成类实例,也都可以销毁,且 del b 时首先调用了自定义的析构函数。

>>>   class   A:

     pass

>>>   a   =   A ( )

>>>   del   a

>>>   a

Traceback   ( most   recent   call   last ) :

   File   "<pyshell#70>",   line   1,   in   <module>

     a

NameError:   name   'a'   is   not   defined

>>>   class   B:

     def   __del__ ( self ) :

         print ( ' 执行析构函数,清理现场 ' )

>>>   b   =   B ( )

>>>   del   b

执行析构函数,清理现场

>>>   b

Traceback   ( most   recent   call   last ) :

   File   "<pyshell#75>",   line   1,   in   <module>

     b

NameError:   name   'b'   is   not   defined

新式类和旧式类

在 PY2 中,类有新式类和旧式类两种。新式类需要继承自虚类 Object,旧式类则不需要。PY2 中类的写法有三种:

class   A ( object ) :新式类写法

class   A ( ) :旧式类写法

class   A:旧式类写法

在 PY3 中,只有新式类,不再支持旧式类。你如果习惯继承 Object 的写法,也完全没有问题。上面三种写法在 PY3 中都被解释成新式类。新式类和旧式类的主要区别是:

新式类可以继承 Object 的构造函数和析构函数,如果类的构造和析构函数没有特别的工作,可以省略。而旧式类则不能:

class   A ( ) :

     def   print ( self ) :

         print ( "I   am   A" )

class   B ( A ) :

     def   __init__ ( self ) :

         A.__init__ ( self )

b   =   B ( )

此时用 PY2 运行,会出现错误:AttributeError: class A has no attribute '__init__',使用 PY3 不会出现此错误。若改成新式类写法:

class   A ( object ) :

     def   print ( self ) :

         print ( "I   am   A" )

class   B ( A ) :

     def   __init__ ( self ) :

         A.__init__ ( self )

b   =   B ( )

仍然用 PY2 运行,则都不会出错。

新式类可以使用 super:

class   A ( object ) :

     def   print ( self ) :

         print ( "I   am   A" )

class   B ( A ) :

     def   __init__ ( self ) :

         super ( A,   self ) .__init__ ( )

b   =   B ( )

多重继承时,各父类的初始化和函数查找顺序不同:旧式类为深度优先继承,新式类为广度优先继承。

静态变量和实例变量

在构造函数中定义的变量,我们称之为实例变量。实例变量只能在实例化后使用 < 对象名 . 变量名 > 的方式访问。静态变量一般定义在类的开始位置,独立于构造函数之外。静态变量既可以 < 对象名 . 变量名 > 的方式访问,也可以 < 类名 . 变量名 > 的方式访问。通常,类的静态变量一般用于保存类的静态属性,该属性可被类的方法使用,但不应该被类的方法修改。

>>>   class   A:

     static_x   =   10   #   静态变量

     def   __init__ ( self ) :

         self.instance_y   =   5   #   实例变量

>>>   a   =   A ( )

>>>   a.static_x

>>>   a.instance_y

5

>>>   A.static_x

>>>   A.instance_y

Traceback   ( most   recent   call   last ) :

   File   "<pyshell#89>",   line   1,   in   <module>

     A.instance_y

AttributeError:   type   object   'A'   has   no   attribute   'instance_y'

静态函数

与其他语音的静态函数不同,Python 的静态函数有两种,都是用装饰器实现的:

class   A:

     static_x=   10

     def   __init__ ( self ) :

         self.y   =   10

     @staticmethod

     def   staticFuc ( ) :

         print ( A.static_x )

     @classmethod

     def   classFuc ( cls ) :

         print ( cls.static_x )

A.staticFuc ( )

A.classFuc ( )

Staticmethod 函数不能使用 Self 参数,因此不成访问任何成员变量,只能通过类名访问类的静态变量。

Classmethod 函数也不能使用 Self 参数,因此不成访问任何成员变量,但它有 cls 参数。cls 参数不是对象的引用,而是类的引用,可以通过 cls 参数访问类的静态变量。

面向对象三要素

面向对象,有三大要素:继承、封装、多态。这里面概念非常多,往往越讲越糊涂。为了不至于误导读者,我尽可能不做解释,只给出例子,请自行揣摩。

( 1 ) 继承

如果派生类只有一个父类,就是单继承。这是最常见的类定义形式。

class   Animal:

     def   eat ( self ) :

         print ( ' 我能吃 ' )

class   Brid ( Animal ) :

     def   __init__ ( self ) :

         Animal.__init__ ( self )

     def   fly ( self ) :

         print ( ' 我会飞 ' )

brid   =   Brid ( )

brid.eat ( )

brid.fly ( )

如果派生类有多个父类,就是多继承。

class   Deer:

     def   showHorns ( self ) :

         print ( ' 我有鹿角 ' )

class   Horse:

     def   showFace ( self ) :

         print ( ' 我有马脸 ' )

class   Cow:

     def   showHoof ( self ) :

         print ( ' 我有牛蹄 ' )

class   Donkey:

     def   showTail ( self ) :

         print ( ' 我有驴尾 ' )

class   Milu ( Deer,   Horse,   Cow,   Donkey ) :   #   多继承派生出一个四不像类

     def   __init__ ( self ) :

         Deer.__init__ ( self )

         Horse.__init__ ( self )

         Cow.__init__ ( self )

         Donkey.__init__ ( self )

milu   =   Milu ( )

milu.showHorns ( )

milu.showFace ( )

milu.showHoof ( )

milu.showTail ( )

不管是单继承还是多继承,都可以在派生类中重写父类的函数——这叫做覆盖。

( 2 ) 封装

所谓封装,就是将类的成员变量、成员函数整合在一起,并对关键的信息进行保护或隐藏。信息保护或隐藏有三个级别:公有、保护、私有。如果你有 C++ 的使用经验,我们先来回顾一下 C++ 的信息隐藏规则:

公有成员:对类外部的任何代码可见 ;

保护成员:对类外部的任何代码都不可见,但对派生类可见 ;

私有成员:对类外部及派生类都不可见。

对应这三个级别,Python 是这样定义的:

以英文字母开头的成员为公有成员

以一个下划线开头的成员为保护成员

以两个下划线开关的成员为私有成员

下面我们试试 Python 的信息保护或隐藏规则是否有效。

>>>   class   A ( object ) :

     def   __init__ ( self,   a,   b,   c ) :

         self.a   =   10        #   公有

         self._b   =   b        #   保护

         self.__c   =   c       #   私有

     def   getA ( self ) :        #   公有

         return   self.a

     def   setA ( self,   a ) :     #   公有

         self.a   =   a

     def   getB ( self ) :        #   公有

         return   self._b

     def   _setB ( self,   b ) :    #   保护

         self._b   =   b

     def   getC ( self ) :        #   公有

         return   self.__c

     def   __setC ( self,   c ) :   #   私有

         self.__c   =   c

>>>   a   =   A ( 10,   20,   30 )

>>>   class   B ( A ) :

     pass

>>>   b   =   B ( 10,   20,   30 )

试试访问公有成员:

>>>   a.a

>>>   a.getA ( )

>>>   a.setA ( 5 )

>>>   a.a

5

>>>   b.a

>>>   b.getA ( )

>>>   b.setA ( 5 )

>>>   b.a

5

公有成员访问规则与 C++ 相同。先跳过保护成员,看看私有成员:

>>>   a.__c

Traceback   ( most   recent   call   last ) :

   File   "<pyshell#85>",   line   1,   in   <module>

     a.__c

AttributeError:   'A'   object   has   no   attribute   '__c'

>>>   a.getC ( )

>>>   a.__setC ( 5 )

Traceback   ( most   recent   call   last ) :

   File   "<pyshell#87>",   line   1,   in   <module>

     a.__setC ( 5 )

AttributeError:   'A'   object   has   no   attribute   '__setC'

>>>   b.__c

Traceback   ( most   recent   call   last ) :

   File   "<pyshell#88>",   line   1,   in   <module>

     b.__c

AttributeError:   'B'   object   has   no   attribute   '__c'

>>>   b.getC ( )

>>>   b.__setC ( )

Traceback   ( most   recent   call   last ) :

   File   "<pyshell#90>",   line   1,   in   <module>

     b.__setC ( )

AttributeError:   'B'   object   has   no   attribute   '__setC'

私有成员的访问规则也与 C++ 相同。那我为什么跳过保护成员了?来试试吧:

>>>   a._b

>>>   a._setB ( 5 )

>>>   a._b

5

看到这里就已经不对了,应该只有类内部的代码和派生类能使用啊,怎么可以直接用了呢?是的,Python 的保护成员访问规则与 C++ 的确实不一样。那 Python 的保护成员是什么样的机制呢?原来,在 Python 的 OOP 中,保护成员公有成员没有任何区别。保护规则仅适用于 from xxx import * 这一种情况。

testA.py

class   A ( object ) :

     pass

class   _B ( object ) :

     pass

testB.py

from   testA   import   *

a   =   A ( )

b   =   _B ( )

执行 testB.py 时:

Traceback   ( most   recent   call   last ) :

   File   "testB.py",   line   4,   in   <module>

     b   =   _B ( )

NameError:   name   '_B'   is   not   defined

此时,保护成员 _B 被保护了。但这种情况仅适用于 from xxx import * 这一种情况。如果 testB.py 这样写:

testB.py

from   testA   import   A,   _B

a   =   A ( )

b   =   _B ( )

或者:

import   testA

a   =   testA.A ( )

b   =   testA._B ( )

则是没有任何问题的。

( 3 ) 多态

当父类有多个派生类,且派生类都实现了同一个成员函数,则可以实现多态:

class   H2O ( object ) :

     def   what ( self ) :

         print ( "I   am   H2O" )

class   Water ( H2O ) :

     def   what ( self ) :

         print ( "I   am   water" )

class   Ice ( H2O ) :

     def   what ( self ) :

         print ( "I   am   ice" )

class   WaterVapor ( H2O ) :

     def   what ( self ) :

         print ( "I   am   water   vapor" ) ;

def   what ( obj ) :

     obj.what ( )

objs   =   [ H2O ( ) ,   Water ( ) ,   Ice ( ) ,   WaterVapor ( ) ]

for   obj   in   objs:

     what ( obj )

抽象类

抽象类不能被实例化,只能作为父类被其它类继承,且派生类必须实现抽象类中所有的成员函数。抽象类应用场景是什么呢?我曾经做过很多下载数据的脚本插件,不同的数据源使用不同的脚本,所有这些脚本要求必须有名字相同的方法,此时,抽象类就派上用场了。

>>>   import   abc

>>>   class   A ( object,   metaclass=abc.ABCMeta ) :

     @abc.abstractmethod

     def   a ( self ) :

         pass

     @abc.abstractmethod

     def   b ( self ) :

         pass

>>>   class   C ( A ) :

     def   a ( self ) :

         print ( "a" )

>>>   c   =   C ( )

Traceback   ( most   recent   call   last ) :

   File   "<pyshell#127>",   line   1,   in   <module>

     c   =   C ( )

TypeError:   Can't   instantiate   abstract   class   C   with   abstract   methods   b

单例模式

单例模式(Singleton Pattern)是一种常用的软件设计模式,该模式的主要目的是确保某一个类只有一个实例存在。当你希望在整个系统中,某个类只能出现一个实例时(如软件配置类,无论在软件的什么地方实例化,永远都是那一个对象),单例模式就能派上用场。比如,Python 日志模块中的日志对象,或者异步通讯框架 Twisted 里面的反应堆 ( reactor ) ,都是典型的单例模式——尽管它们不一定是下面这种方法实现的。

Python 可以使用装饰器的方法使用单例模式:

def   Singleton ( cls ) :

     _instance   =   {}

     def   _singleton ( *args,   **kargs ) :

         if   cls   not   in   _instance:

             _instance [ cls ]   =   cls ( *args,   **kargs )

         return   _instance [ cls ]

     return   _singleton

@Singleton

class   Config ( object ) :

     pass

cfg1   =   Config ( )

cfg2   =   Config ( )

print ( cfg1   is   cfg2 )

后记

行文至此,说几句题外话。CSDN 不止为我们提供了这样一个交流平台,还经常推出各类技术交流活动。近期我将在 GeekTalk 栏目,和 Python 新手共同探讨如何快速成长为基础扎实、功力强大的程序员。CSDN 还为这个活动提供了一些纪念品。如果有兴趣,请扫码加入:

版权声明:本文为 CSDN 博主「天元浪子」的原创文章。

想为博主点赞?

想要请教博主?

扫描下方二维码,快速获取与博主直面沟通的方式吧!

相关文章
评论
没有更多评论了
取消

登录后才可以发布评论哦

12 我来说两句…
打开 ZAKER 参与讨论