???? 引言
???? 面向对象编程
???? 类
???? 实例
???? 绑定与方法调用
???? 子类,派生和继承
???? 内建函数
???? 定制类
???? 私有性
???? 授权与包装
???? 新式类的高级特性
???? 相关模块
13.1 介绍
    在Python 中,面向对象编程主要有两个主题,就是类和类实例
类与实例
    如何创建一个类:
        class MyNewObjectType(bases):
            'define MyNewObjectType class'
            class_suite #类体
        关键字是class,紧接着是一个类名。随后是定义类的类体代码。这里通常由各种各样的定义和声明组成。
        所有新式类必须继承至少一个父类,参数bases可以是一个(单继承)或多个(多重继承)用于继承的父类。
        object 是“所有类之母”。
        如果你的类没有继承任何其他父类,object 将作为默认的父类。它位于所有类继承结构的最上层。

        如果你没有指定一个父类,或者如果所子类化的基本类没有父类,你这样就是创建了一个经典类。

    新式类和经典类
        新式类必须继承至少一个父类,可以继承 一个(单继承)或多个(多重继承)父类。
            class MyNewObjectType(bases):
                'define MyNewObjectType class'
                class_suite #类体
        经典类没有指定一个父类,或者如果所子类化的基本类没有父类。
                class MyNewObjectType:
                    'define MyNewObjectType classic class'
                    class_suite
    实例化    
        创建一个实例的过程称作实例化(没有使用new 关键字)
        myClass = new MyClass()
        类名使用我们所熟悉的函数操作符(()),以“函数调用”的形式出现。
    名称空间容器
        只要你需要,类可以很简单,也可以很复杂。最简单的情况,类仅用作名称空间(namespaces)。
        这意味着你把数据保存在变量中,对他们按名称空间进行分组,使得他们处于同样的关系空间中。
        所谓的关系是使用标准Python 句点属性标识。
            >>> class MyClass:
            ... pass
            ...
            >>> myClass = MyClass()
            >>> myClass.x = 1
            >>> myClass.y = 2
            >>> y = myClass.x + myClass.y
            >>> print(y)
            3
            >>> print(myClass.x,myClass.y)
            1 2
            >>>
方法
    定义类(和方法)
        >>> class MyFoo(object):
        ... 'with method named bar'
        ... def bar(self): #self
        ... print('you invoked method bar()')
        ...
        >>> myFoo = MyFoo()
        >>> myFoo.bar()
        you invoked method bar()
        >>>
    方法__init__()
        __init__()类似于类构造器,它在创建一个新的对象时被调用。
        在Python 中,__init__() 实际上不是一个构造器。
        创建一个类(类定义)
            >>> class Foo(object):
            ...          def __init__(self,x,y): 
            ...              self.x = x 
            ...              self.y = y 
            ...              print('you invoked __init__(x,y)') 
            ...          def Show(self): 
            ...             print('x + y = %s' %(self.x + self.y)) 
            ...
            >>>
        创建实例(实例化)
            >>> foo = Foo(1,1)
            you invoked __init__(x,y)
            >>>
            如果不存在默认的参数,那么传给__init__()的两个参数在实例化时是必须的。
        访问实例属性
            >>> foo.x + foo.y
            2
            >>>
        方法调用(通过实例)
            >>> foo.Show()
            x + y = 2
            >>>
创建子类
    >>> class Foo(object):
    ...          def __init__(self,x): 
    ...             self.x = x 
    ...          def show(self): 
    ...             print('Foo.x = %s' %self.x) 
    ...
    >>> class Bar(Foo):
    ...         def __init__(self,x,y): 
    ...             Foo.__init__(self,x)  #base.__init__
    ...             self.y = y 
    ...         def showbar(self): 
    ...             print('Bar.x = %s, Bar.y = %s' %(self.x,self.y)) 
    ...
    >>> bar = Bar(1,2)
    >>> bar.showbar()
    Bar.x = 1, Bar.y = 2
    >>> bar.show()
    Foo.x = 1
    >>>
核心笔记:命名类、属性和方法
    类名通常由大写字母打头。这是标准惯例,可以帮助你识别类,特别是在实例化过程中(有时看
起来像函数调用)。还有,数据属性(译者注:变量或常量)听起来应当是数据值的名字,方法名应
当指出对应对象或值的行为。另一种表达方式是:数据值应该使用名词作为名字,方法使用谓词(动
词加对象)。数据项是操作的对象、方法应当表明程序员想要在对象进行什么操作。在上面我们定义
的类中,遵循了这样的方针,数据值像“name”,“phone”和“email”,行为如“updatePhone”,
“updateEmail”。这就是常说的“混合记法(mixedCase)”或“骆驼记法(camelCase)”。Python 规
范推荐使用骆驼记法的下划线方式,比如,“update_phone”,“update_email”。类也要细致命名,
像“AddrBookEntry”,“RepairShop”等等就是很好的名字。
13.2 面向对象编程
    编程的发展已经从简单控制流中按步的指令序列进入到更有组织的方式中,依靠代码块可以形
成命名子程序和完成既定的功能。结构化的或过程性编程可以让我们把程序组织成逻辑块,以便重
复或重用。创建程序的过程变得更具逻辑性;选出的行为要符合规范,才可以约束创建的数据。迪
特尔父子(这里指DEITEL 系列书籍作者Harvey M.Deitel 和Paul James Deitel 父子,译者注)认
为结构化编程是“面向行为”的,因为事实上,即使没有任何行为的数据也必须“规定”逻辑性。
    然而,如果我们能对数据加上动作呢?如果我们所创建和编写的数据片段,是真实生活中实体
的模型,内嵌数据体和动作呢?如果我们能通过一系列已定义的接口(又称存取函数集合)访问数据
属性,像自动取款机卡或能访问你的银行帐号的个人支票,我们就有了一个“对象”系统,从大的
方面来看,每一个对象既可以与自身进行交互,也可以与其它对象进行交互。
    面向对象编程踩上了进化的步伐,增强了结构化编程,实现了数据与动作的融合:数据层和逻
辑层现在由一个可用以创建这些对象的简单抽象层来描述。现实世界中的问题和实体完全暴露了本
质,从中提供的一种抽象,可以用来进行相似编码,或者编入能与系统中对象进行交互的对象中。
类提供了这样一些对象的定义,实例即是这些定义的实现。二者对面向对象设计(object-oriented
design,OOD)来说都是重要的,OOD 仅意味来创建你采用面向对象方式架构来创建系统。

面向对象设计与面向对象编程的关系

    面向对象设计(OOD)不会特别要求面向对象编程(OOP)语言。
    OOD 可以由纯结构化语言来实现,比如C,但如果想要构造具备对象性质和特点的数据类型,就需要在程序上作更多的努力。
    一门面向对象的语言不一定会强制你写OO 方面的程序。
*常用术语
    抽象/实现   
抽象/实现
	抽象指对现实世界问题和实体的本质表现,行为和特征建模,建立一个相关的子集,可以用于
描绘程序结构,从而实现这种模型。抽象不仅包括这种模型的数据属性,还定义了这些数据的接口。
对某种抽象的实现就是对此数据及与之相关接口的现实化(realization)。现实化这个过程对于客户
程序应当是透明而且无关的。
    封装/接口   
封装/接口
	封装描述了对数据/信息进行隐藏的观念,它对数据属性提供接口和访问函数。通过任何客户端
直接对数据的访问,无视接口,与封装性都是背道而驰的,除非程序员允许这些操作。作为实现的
一部分,客户端根本就不需要知道在封装之后,数据属性是如何组织的。在Python 中,所有的类属
性都是公开的,但名字可能被“混淆”了,以阻止未经授权的访问,但仅此而已,再没有其他预防
措施了。这就需要在设计时,对数据提供相应的接口,以免客户程序通过不规范的操作来存取封装
的数据属性。
    合成   
合成
	合成扩充了对类的描述,使得多个不同的类合成为一个大的类,来解决现实问题。合成描述了
一个异常复杂的系统,比如一个类由其它类组成,更小的组件也可能是其它的类,数据属性及行为,
所有这些合在一起,彼此是“有一个”的关系。比如,RepairShop“有一个”技工(应该至少有一个
吧),还“有一个”顾客(至少一个)。
    派生/继承/继承结构   
派生/继承/继承结构
	派生描述了子类的创建,新类保留已存类类型中所有需要的数据和行为,但允许修改或者其它
的自定义操作,都不会修改原类的定义。继承描述了子类属性从祖先类继承这样一种方式。从前面
的例子中,技工可能比顾客多个汽车技能属性,但单独的来看,每个都“是一个”人,所以,不管
对谁而言调用talk()都是合法得,因为它是人的所有实例共有的。继承结构表示多“代”派生,可
以描述成一个“族谱”,连续的子类,与祖先类都有关系。
    泛化/特化   
泛化/特化
	泛化表示所有子类与其父类及祖先类有一样的特点,所以子类可以认为同祖先类是“是一个”
的关系,因为一个派生对象(实例)是祖先类的一个“例子”。比如,技工“是一个”人,车“是一
个”交通工具,等等。在上面我们间接提到的族谱图中,我们可以从子类到祖先类画一条线,表示
“是一个”的关系。特化描述所有子类的自定义,也就是,什么属性让它与其祖先类不同。
    多态   
多态
	多态的概念指出了对象如何通过他们共同的属性和动作来操作及访问,而不需考虑他们具体的
类。多态表明了动态(又名,运行时)绑定的存在,允计重载及运行时类型确定和验证。
    自省/反射   
自省/反射
	自省表示给予你,程序员,某种能力来进行像“手工类型检查”的工作,它也被称为反射。这
个性质展示了某对象是如何在运行期取得自身信息的。如果传一个对象给你,你可以查出它有什么
能力,这样的功能不是很好吗?这是一项强大的特性,在本章中,你会时常遇到。如果Python 不
支持某种形式的自省功能,dir()和type()内建函数,将很难正常工作。请密切关注这些调用,还
有那些特殊属性,像__dict__,__name__及__doc__。可能你对其中一些已经很熟悉了!
13.3 类
    类是一种数据结构,是现实世界的抽象的实体以编程形式出现。
    实例是把数据值和行为特性融合在一起的这些对象的具体化。
创建类
    Python 类使用class 关键字来创建。
    class ClassName(bases):
        'class documentation string'    #'类文档字符串'
        class_suite    #类体
    基类是一个或多个用于继承的父类的集合;
    类体由所有声明语句,类成员定义,数据属性和函数组成。
    类通常在一个模块的顶层进行定义,以便类实例能够在类所定义的源代码文件中的任何地方被创建。
声明与定义
    对于Python 函数来说,声明与定义类没什么区别,因为他们是同时进行的,
    定义(类体)紧跟在声明(含class 关键字的头行[header line])和可选(但总是推荐使用)的文档字符串后面。
    Python 并不支持纯虚函数(像C++)或 者抽象方法(如在JAVA 中),这些都强制程序员在子类中定义方法。
13.4 类属性
    属性就是属于另一个对象的数据或者函数元素,可以通过我们熟悉的句点属性标识法来访问。
类的数据属性
    数据属性仅仅是所定义的类的变量,即静态变量,或者是静态数据,表示这些数据是与它们所 属的类对象绑定的,不依赖于任何类实例。
    静态成员通常仅用来跟踪与类相关的值,大多数情况下,你会考虑用实例属性,而不是类属性。
        >>> class Foo(object):
        ... bar = 100
        ...
        >>> Foo.bar
        100
        >>> Foo.bar += 100
        >>> Foo.bar
        200
        >>> foo = Foo()
        >>> foo.bar
        200
        >>> foo.bar+=100
        >>> Foo.bar
        200
        >>> foo.bar
        300
        >>>
类的函数属性
    为与OOP 惯例保持一致,Python 严格要求,没有实例,方法是不能被调用的,即Python所描述的绑定概念(binding),在此,方法必须绑定(到一个实例)才能直接被调用。
    非绑定的方法 可能可以被调用,但实例对象一定要明确给出,才能确保调用成功。
    不管是否绑定,方法都是它所在的类的固有属性,即使它们几乎总是通过实例来调用的。   
    >>> class Foo(object):
    ... def bar(self):
    ... pass
    ...
    >>> Foo.bar
    <unbound method Foo.bar>
    >>> foo = Foo()
    >>> foo.bar
    <bound method Foo.bar of <__main__.Foo object at 0x000000000217A2B0>>
    >>> Foo.bar()
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: unbound method bar() must be called with Foo instance as first argument (got nothing instead)
    >>> foo.bar()
    >>>
决定类的属性
    dir()内建函数
    类的字典属性__dict__
 特殊的类属性
    13 面向对象编程 - 《Python 核心编程》
13.5 实例
初始化:通过调用类对象来创建实例
    实例化的实现,可以使 用函数操作符
        >>> class MyClass(object): # define class 定义类
        ...          pass
        >>> mc = MyClass() # instantiate class 初始化类
__init__() "构造器"方法
    __init__()是在解释器为你创建一个实例后调用的第一个方法   
__init__() "构造器"方法
	当类被调用,实例化的第一步是创建实例对象。一旦对象创建了,Python 检查是否实现了
__init__()方法。默认情况下,如果没有定义(或覆盖)特殊方法__init__(),对实例不会施加任
何特别的操作.任何所需的特定操作,都需要程序员实现__init__(),覆盖它的默认行为。如果
__init__()没有实现,则返回它的对象,实例化过程完毕。
	然而,如果__init__()已经被实现,那么它将被调用,实例对象作为第一个参数(self)被传递
进去,像标准方法调用一样。调用类时,传进的任何参数都交给了__init__()。实际中,你可以想
像成这样:把创建实例的调用当成是对构造器的调用。
	总之,你没有通过调用new 来创建实例,你也没有定义一个构造器。是Python 为你创建了
对象; __init__(),是在解释器为你创建一个实例后调用的第一个方法,在你开始使用它之前,
这一步可以让你做些准备工作。
	__init__()是很多为类定义的特殊方法之一。其中一些特殊方法是预定义的,缺省情况下,不
进行任何操作,比如__init__(),要定制,就必须对它进行重载,还有些方法,可能要按需要去实
现。本章中,我们会讲到很多这样的特殊方法。你将会经常看到__init__()的使用,在此,就不举
例说明了。
__new__() “构造器”方法
    __new__()必须返回一个合法的实例,这样解释器在调用__init__()时,就可以把这个实例作为self 传给它。
    __new__()和__init__()在类创建时,都传入了(相同)参数。   
__new__() “构造器”方法
	与__init__()相比,__new__()方法更像一个真正的构造器。类型和类在版本2.2 就统一了,
Python 用户可以对内建类型进行派生,因此,需要一种途径来实例化不可变对象,比如,派生字符
串,数字,等等。
	在这种情况下,解释器则调用类的__new__()方法,一个静态方法,并且传入的参数是在类实例
化操作时生成的。__new__()会调用父类的__new__()来创建对象(向上代理)。
	为何我们认为__new__()比__init__()更像构造器呢?这是因为__new__()必须返回一个合法
的实例,这样解释器在调用__init__()时,就可以把这个实例作为self 传给它。调用父类的__new__()
来创建对象,正像其它语言中使用new 关键字一样。
__new__()和__init__()在类创建时,都传入了(相同)参数。
__del__() "解构器"方法
    由于Python 具有垃圾对象回收机制(靠引用计数),__del__()  函数要直到该实例对象所有的引用都被清除掉后才会执行。
    Python 中的解构器是在实例释放前提供特殊处理功能的方法,它们通常没有被实现,因为实例很少被显式释放。
    解构器只能被调用一次,一旦引用计数为0,则对象就被清除了。因为系统中任何对象都只被分配及解构一次。
    总结:
        ???? 不要忘记首先调用父类的__del__()。
        ???? 调用 del x 不表示调用了x.__del__() -----前面也看到,它仅仅是减少x 的引用计数。
        ???? 如果你有一个循环引用或其它的原因,让一个实例的引用逗留不去,该对象的__del__()可
        能永远不会被执行。
        ???? __del__()未捕获的异常会被忽略掉(因为一些在__del__()用到的变量或许已经被删除了)。
        不要在__del__()中干与实例没任何关系的事情。
        ???? 除非你知道你正在干什么,否则不要去实现__del__()。
        ???? 如果你定义了__del__,并且实例是某个循环的一部分,垃圾回收器将不会终止这个循环—
        —你需要自已显式调用del。   
跟踪实例
	Python 没有提供任何内部机制来跟踪一个类有多少个实例被创建了,或者记录这些实例是些什
么东西。如果需要这些功能,你可以显式加入一些代码到类定义或者__init__()和__del__()中去。
最好的方式是使用一个静态成员来记录实例的个数。靠保存它们的引用来跟踪实例对象是很危险的,
因为你必须合理管理这些引用,不然,你的引用可能没办法释放(因为还有其它的引用)!
13.6 实例属性
    实例仅拥有数据属性(方法严格来说是类属性),后者只是与某个类的实例相关联的数据值,并且可以通过句点属性标识法来访问。
    这些值独立于其它实例或类。当一个实例被释放后,它的属性同时也被清除了。
“实例化”实例属性(或创建一个更好的构造器)
    设置实例的属性可以在实例创建后任意时间进行,也可以在能够访问实例的代码中进行。
    构造器__init()__是设置这些属性的关键点之一。
    构造器是最早可以设置实例属性的地方,因为__init__()是实例创建后第一个被调用的方法。
    __init__()应当返回None   
        核心笔记:实例属性
            能够在“运行时”创建实例属性,是Python 类的优秀特性之一,从C++或Java 转过来的人会被
        小小的震惊一下,因为C++或Java 中所有属性在使用前都必须明确定义/声明。
        Python 不仅是动态类型,而且在运行时,允许这些对象属性的动态创建。这种特性让人爱不释
        手。当然,我们必须提醒读者,创建这样的属性时,必须谨慎。
        一个缺陷是,属性在条件语句中创建,如果该条件语句块并未被执行,属性也就不存在,而你
        在后面的代码中试着去访问这些属性,就会有错误发生。故事的精髓是告诉我们,Python 让你体验
        从未用过的特性,但如果你使用它了,你还是要小心为好。  
查看实例属性
    内建函数dir()可以显示类属性,同样还可以打印所有实例属性。
    实例也有一个__dict__特殊属性(可以调用vars()并传入一个实例来获取),它是实例属性构成的一个字典。
特殊的实例属性
    实例仅有两个特殊属性。
    对于任意对象I:
        I.__class__    实例化I 的类
        I.__dict__     I 的属性
            __dict__属性由一个字典组成,包含一个实例的所有属性。
            键是属性名,值是属性相应的数据 值。
            字典中仅有实例属性,没有类属性或特殊属性。
        >>> 'hello'.__class__
        <type 'str'>
        >>> str.__class__
        <type 'type'>
        >>>
    核心风格:修改__dict__
        对类和实例来说,尽管__dict__属性是可修改的,但还是建议你不要修改这些字典,除非你知
    道你的目的。这些修改可能会破坏你的OOP,造成不可预料的副作用。使用熟悉的句点属性标识来访
    问及操作属性会更易于接受。需要你直接修改__dict__属性的情况很少,其中之一是你要重载
    __setattr__特殊方法。实现__setattr__()本身是一个冒险的经历,满是圈套和陷阱,例如无穷递
    归和破坏实例对象。这个故事还是留到下次说吧。   
内建类型属性
    内建类型也是类,有像类一样的属性。
    对内建类型也可以使用 dir(),与任何其它对象一样,可以得到一个包含它属性名字的列表。
        >>> x = 1 + 2J
        >>> x.__class__
        <class 'complex'>
        >>> dir(x)
        ['__abs__', '__add__', '__bool__', '__class__', '__delattr__', '__dir__', '__div
        mod__', '__doc__', '__eq__', '__float__', '__floordiv__', '__format__', '__ge__'
        , '__getattribute__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__int
        __', '__le__', '__lt__', '__mod__', '__mul__', '__ne__', '__neg__', '__new__', '
        __pos__', '__pow__', '__radd__', '__rdivmod__', '__reduce__', '__reduce_ex__', '
        __repr__', '__rfloordiv__', '__rmod__', '__rmul__', '__rpow__', '__rsub__', '__r
        truediv__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__
        ', '__truediv__', 'conjugate', 'imag', 'real']
        >>>
实例属性 vs 类属性
        类和实例都是名字空间,类是类属性的名字空间,实例则是实例属性的。
    访问类属性
        类属性可通过类或实例来访问。
            Python 首先会在实例中搜索名字,然后是类,再就是继承树中的基类。
        从实例中访问类属性须谨慎
            与通常Python 变量一样,任何对实例属性的赋值都会创建一个实例属性(如果不存在的话)并且对其赋值。
            如果类属性中存在同名的属性,有趣的副作用即产生。(经典类和新式类都存在)--“隐藏”类属性
    类属性持久性
        修改类属性需要使用类名,而不是实例名。
13.7 从这里开始校对----------绑定和方法调用
    方法仅仅是类内部定义的函数。(方法是类属性而不是实例属性)。
    方法只有在其所属的类拥有实例时,才能被调用,当存在一个实例时,方法才被认为是绑定到那个实例了,没有实例时方法就是未绑定的。
    任何一个方法定义中的第一个参数都是变量self,它表示调用此方法的实例对象。
        核心笔记:self 是什么?
            self 变量用于在类实例方法中引用方法所绑定的实例。因为方法的实例在任何方法调用中总是
        作为第一个参数传递的,self 被选中用来代表实例。你必须在方法声明中放上self(你可能已经注
        意到了这点),但可以在方法中不使用实例(self)。如果你的方法中没有用到self , 那么请考虑创建
        一个常规函数,除非你有特别的原因。毕竟,你的方法代码没有使用实例,没有与类关联其功能,
        这使得它看起来更像一个常规函数。在其它面向对象语言中,self 可能被称为 this。       
调用绑定方法
    方法,不管绑定与否,都是由相同的代码组成的,唯一的不同在于是否存在一个实例可以调用此方法,在很多情况下,程序员调用的都是一个绑定的方法。
    当你在实例中调用一个绑定的方法 时,self 不需要明确地传入了。
    当你还没有一个实例并且需要调用一个非绑定方法的时候你必须传递self 参数。   
调用绑定方法
	方法,不管绑定与否,都是由相同的代码组成的。唯一的不同在于是否存在一个实例可以调用
此方法。在很多情况下,程序员调用的都是一个绑定的方法。假定现在有一个 MyClass 类和此类的
一个实例 mc,而你想调用MyClass.foo()方法。因为已经有一个实例,你只需要调用mc.foo()就可
以。记得self 在每一个方法声明中都是作为第一个参数传递的。当你在实例中调用一个绑定的方法
时,self 不需要明确地传入了。这算是"必须声明self 作为第一个参数"对你的报酬。当你还没有
一个实例并且需要调用一个非绑定方法的时候你必须传递self 参数。
调用绑定方法
调用非绑定方法

相关文章:

  • 2022-12-23
  • 2021-06-16
  • 2021-05-17
  • 2021-06-12
  • 2021-07-28
  • 2021-04-22
猜你喜欢
  • 2021-08-29
  • 2022-02-19
  • 2022-02-14
  • 2022-02-24
  • 2022-12-23
  • 2022-12-23
相关资源
相似解决方案