day31——模块使用、面向对象介绍、类的属性、类的方法、类的内置方法、类的继承、

    之前介绍过函数,Python有很多内置函数,那么这些函数可以直接拿来使用,Python的强大之处不仅有很多函数,还有很多很多模块,这些模块还有很多方法,这些方法跟那些函数用起来基本是一样的,可以拿来直接使用就可以了。以前用的时候直接在开头用import引入模块,其实像我们用的模块呢在系统当中都能找到模块的原文件。
    模块是Python组织代码的基本方式。
    一个Python脚本可以单独运行,也可以导入到另一个脚本中运行,当脚本被导入运行时,我们将其称为模块(module)
    所有的.py文件都可以作为一个模块导入。
    模块名与脚本的文件名相同
    例如我们编写了一个名为hello.py的脚本,则可以在另一个脚本中用import hello语句来导入它。
    包
    Python的模块可以按目录组织为包(包是一个模块的集合)
    创建一个包的步骤:
        ●创建一个名字为包名的目录
        ●在该目录下创建一个__init__.py文件(这个文件可以是空的,必须得有这样的文件,Python才能把它识为包)
        ●根据需要,在该目录下存放脚本文件或已编译的扩展及子包
          ● import pack.m1,pack.m2,pack.m3(前面是包名后面是模块名)
    查看Python的模块都在什么位置
          ●sys.path:Python系统当中有一个sys模块,这个sys模块有一个path属性,可以通过这个sys.path来看到模块的位置
          ●
    先用import导入模块,然后按Tab可以查看所有的方法和属性,其中有一个是.path,sys.path返回的是一个list列表,每个元素都是目录,第一个元素是空的,表示当前目录,
 
    比如插入import string模块,它会从第一个目录找(也就是当前目录查找,当前目录就是IPython所在的目录),如果当前目录里面没有string的话找第二个目录直到找到,如果找不到呢,会给出这样的异常,No module named t表示这个模块不存在。大部分模块都在'/usr/lib64/python2.6'下,
    rpm -ql python-libs :装了一个python-libs包,列出的这些文件路径基本都在/usr/lib64/python2.6/下,有很多很多的模块,这些都是用Python写的脚本,除了这些默认的sys.path外呢也可以自定义sys.path。
    比如创建一个目录 mkdir /root/library 当成sys.path,作为模块来插入,怎么让它变成Python path呢?有两种方法,第一种方法呢就是在脚本当中(既然是个列表就可以有个append方法,把刚才的路径/root/library加进来),然后再sys.path时会加到最后一个。还有一种方法就是修改系统的环境变量:vim /root/.bashrc,在里面写:export PYTHONPATH=/root/library。如果有多个路径用:冒号分开,
    而且加到前面去了,如果有一些模块可以直接放到/root/library目录下,
    自己以前写的都可以当成模块来导入,比如写一个脚本,统计一个文件有多少字符、多少个单词、多少行:
    定义一个函数,统计字符串有几个字符可以直接统计长度就可以了,有几个单词呢在Linux是以空格分开的,所以直接用split切一下,切过之后是一个list,还是通过len函数来统计一下有多少个元素就有多少个单词,行怎么统计呢?还是先用split切一下,这次是用换行符来切,切完之后还是统计有多少个元素就有几行。先打开一个文件,这个文件打开之后呢有一个句柄(文件句柄其实就是文件对象了,然后有一个.read方法,read方法返回的就是一个字符串,把这个字符串保存到s里),最后去调用这个函数。可以使用wc 命令来验证一下,发现第一个数不一样,
vim 20.py
    在这里统计字符用line函数,统计有多少单词用split切一下,之后统计列表有多少个元素也是单词数,现在统计有几行是通过split 换行符'\n'来切的,切完之后本来有四行,一个换行符有一行,而通过split '\n'切完之后呢五个元素,最后一个元素是空的,它把\n作为切割符切完之后呢最后末尾还有一个空的,所以最后会有一个空的元素,这个要注意一下,那么最后以换行符为结尾的进行split切完之后会有一个空的字符,那怎么避免呢?可以不使用split,用.count方法,.count是统计这个字符串里面有几个重复的字符,比如统计'\n'就行了,有4个就表示有4行。
    这会输入就和wc就一样了。这就是一个函数,统计字符数、单词数还有行数。
    然后再创建一个21.py文件(.py的文件就是一个模块,是可以直接导入到别个一个脚本当中来使用),想使用20.py的话需要import 21(注意,.py不要写)
    如果想作为模块导入呢,这个脚本不能以这样方式来命名(不能以数字开头,其实跟那个变量有点相似),mv 20.py wc.py,执行21.py跟执行wc.py脚本的效果是一样的,
    当在21.py中导入了wc模块,那么默认情况下它是找的当前目录下的,这时会多出个wc.pyc文件出来,当wc.py这个模块或者脚本被当成一个模块被另外一个脚本导入的时候,那么就会产生这样一个编译的pyc文件,产生它的目的呢就是为了下回在脚本当中(比如在21.py脚本在引用的时候效率更高些、更快些,就是把它进行一个编译)
    在看ls /usr/lib64/python2.6/下的文件时有很多文件一般都是三个,.py是源码的,.pyc和.pyo是编译过的。
 
 
    当执行21.py时就相当于执行wc.py。有这个模块了,那能否使用wordCount()这个函数呢?wordCount()是对字符串进行统计的。先定义一个字符串,这个字符串两行三个单词,出错说wordCount未被定义,虽然这个模块加载了,但是这个函数呢不能直接去使用,如果想使用的话需要这么来使用:前面加上模块名,然后再使用这个函数名
    酱紫就没问题了,但是结果不是所期望的,期望是只统计这个字符串s,不想再统计/etc/passwd的输出了,明显输出的第一行信息是/etc/passwd的统计信息,第二行是字符串s的信息。
    假如不想要第一行,先看脚本的执行,执行脚本的时候是import wc,相当于把wc.py这个脚本又给执行了一遍,执行了一遍就有了44 79 2160的输出了,接下来是去定义变量s,定义变量s之后再通过wordCount()这个函数去处理了一下,然后看到了1 3 18这行的输出,假如只想要第二行的话,更多的是想用wordCount这个函数,在这里可以修改一下这个wc.py文件,把最后两行s = open('/etc/passwd').read()    wordCount(s)先给注释掉,但注释掉后会发现运行wc.py时就没输出了,这就影响了原文件的输出结果,但是在执行21.py时输出是对的,就是说牺牲了一个文件,但这也不是想要的结果,想要的结果是原文件这些输出没有变(就是原模块输出没有变,调用模块的那个脚本21.py还不要显示原文件的输出)
    在wc.py里面先看下__name__这个变量(它是Python的内置变量,Python有内置函数还有内置变量),它的值是__main__,表示在执行这个模块的时候是我在执行这个模块,当执行21.p时它输出的是什么?21.py里面也会执行wc.py这个函数,但是__name__这个变量变了,变成wc了,wc就是这个模块名,__name__这个变量有这样的特点,我们可以根据这个特点作一个这样的判断,如果是__main的话表示执行当前模块这个脚本,
    if __name__ == '__main__':这个经常会用到,以后去写脚本时,脚本当中定义了很多函数或者是类,那么最后在调用这些函数或者类的时候,会经常写这样的判断,在这个判断下面去调用这个函数,这样写的目的呢将来这个脚本可以作为其他脚本的一个模块来使用,比如写完一个脚本,其它脚本想调用这个脚本作为模块的话使用就没有问题了。这里面有一个__name__变个变量。其实调用模块的目的就是为了简化编程,一般情况下在编程里相同的代码不写两次,这是我们的一个原则,相同的代码可以从其他的脚本当中可以把它借鉴过来,或者把它写一个函数可以直接使用。   
    如果想把它作为包的话可以这样来做:
    包就是需要在里面先创建一个空的文件(这个文件可以是空的,如果不是空的可以是这个包的描述),包的名字就是这个目录的名字。
    怎么使用包呢?
    总结:
    ● 模块是一个可以导入的Python脚本文件
     ● 包是一些按目录组织的模块和子包,目录下有__init__.py文件,此文件可以存放包的信息
     ● 导入模块和包的语法:
        import,import as
        from ... import ...    (导入包的时候一定要有from,然后是包的名字,后面是import,import后面是模块名,模块就是.py的文件),如果想使用里面的函数呢,比如直接使用里面的函数是from 包的名字.(加点)模块的名字再import 函数或者是一个类,这么来导入。
    
    面向对象是编程的一种方法,那么在Python的哲学里呢一切皆对象,之前也定义过很多变量,变量有不同的数据类型(如整型、字符串、序列、还有字典),那么它叫做对象,数值叫数值对象,字符串叫字符串对象,还有字典对象,当时我们还说到,对象里面有属性和方法,那么拿数值来说,如果想看里面的属性和方法,打个1.按Tab键就可以看到,
    如想看字符串的,str.按Tab键
    如想看列表的,list.按Tab键
    如想看元组的,tuple.按Tab键
    如想看字典的,dict.按Tab键
    像这些都是它的一些方法,那什么是面向对象呢?它和面向过程是相对应的,那么在以前我们所写的所有程序里面都是面向过程的,面向过程其实就是分析出解决问题所需要的一些步骤,然后可以把这些步骤一步一步地通过函数去实现,最后去使用的时候呢是一个一个在调用,先定义函数,有很多函数集合最后去调用,这都是面向过程的。面向过程比较典型的语言是C语言,Shell脚本也是面向过程的,还有之前写的所有Python脚本,我们用的是那种面向过程的编程方法,那么什么是面向对象呢?面向对象就是把数据还有数据的一些操作方法把它放到一起,作为一个相互依存的整体,可以把这个整体呢叫做对象。
    比如还是解决同样的问题,我们如果使用面向对象呢这个思路可以这样来做,先创建一个类,那么在类里面去定义一些属性,这些属性呢我们可以把它理解为之前学过的变量,那么再定义一些方法,这些方法呢我们可以把它理解为之前学过的函数,那么在这些函数里面呢直接可以去使用上面定义的属性了,不再需要在方法里面再重新定义了,因为这个属性和方法它们在一个整体里,看着就是一个对象,因为都属性类的成员。
    面向对象的编程思想需要在实践当中慢慢体会,如果这个问题比较简单,我们就可以使用面向过程,如果问题比较复杂我们可以使用面向对象,当然我们必须得对这个面向对象得有很好的理解,才能使用起来更得心应手。
    那么在面向对象的编程语言中,比较有代表性的有C++、Java、Python等。
    介绍面向对象的一些基本术语,然后怎么看懂别人写的面向对象的一些编程(利用面向对象来设计的编程),读懂他们的代码,而且在我们后边的工作、实践当中慢慢使用这种面向对象的编程思想。接下来介绍类和对象,
    类和对象
    ● 类和对象:是面向对象中的两个重要概念
        -类:   是对事物的抽象:比如:人类、球类(类有属性,还有一些方法,方法比如吃、穿、住、行。属性可以把它理解为变量,就去可以理解为函数)
        -对象:是类的一个实例,比如:足球、篮球(比如小张去给我倒杯水,小张就是一个人类的实例,一个实际的例子,不能说人类去给我倒杯水,只能把这个类进行实例化,实例化之后呢才能有倒水的动作。通过这个对象可以调用倒水的方法,或者这个对象可以思考呀,)
    ● 实例说明:
        -球类可以对球的特征和行为进行抽象,然后可以实例化一个真实的球实体出来。
    为什么要使用面向对象呢
     ● 面向对象的主要思想是:
        - 封装
        - 继承
        - 多态
      ● 这种思想方便解决较为复杂的项目,且维护起来较为容易。
    Python类定义
     ● 类定义:
        类把需要的变量和函数组合成一起,这种包含称为“封装”(定义一个类就是封装)
        class A(object):    (在Python定义类是通过class关键字,后面是个类名,括号里面可以继承object类,类的名字跟以前学的变量名、函数名都一样的,什么只能由数字、字母、下划线组成,不能以数字开头,之前定义变量都是小写的,如果是两个单词的话中间会出现一个下划线之类的,函数如果是两个单词的话第二个单词以后首字母是大写的。那么定义类呢,原则一个单词首字母大写,如果两个或者多个单词则每个单词的首字母都是大写)
     ● 类的结构:
        class 类名:
            成员变量-属性
            成员函数-方法
    关键字是class,然后定义一些属性(属性就是变量),比如皮肤是什么颜色的,color = 'yellow'这个就是一个属性,跟之前定义变量没什么区别,如果现在执行是什么都看不到的。如想想访问这个类的属性的话需要把类给实例化,实例化其实就是在创建一个对象,ren = People()括号里面可以是空的,还要写那个参数,然后print看下这个ren是什么,ren现在是一个对象,这是通过People()这个类实例化的,实例化之后得到一个对象ren,打印之后出现一个<>,反正<>号的都是一个对象,然后通过这个具体的对象来访问里面的属性,.color就可以了访问了。
    类的创建
    ● class MyClass(object):
            def fun(self):
                print "I am function"
    ● 类的方法中至少有一个参数self
    color叫静态属性,也可以去定义动态方法,动态方法跟以前定义函数一样,那么在定义方法的时候它必须(最少)得有一个参数,这个参数是一定是self,self表示这个类的自己或者叫这个类的本身,如果还有其他参数在后面加一下,在这里没有参数,但是self必须得有。那怎么去调用这个方法呢?一样还是通过对象来调用这个方法,它是个函数嘛别忘了它后面的括号(),而这个color是个属性后面就没有括号,think()括号里面没有参数,因为这个think(self)里的self不算参数。
    
    在这个函数里面呢其实可以使用下面的属性,怎么使用呢?必须得加上self然后.color,self就表示类的本身嘛,表示去使用上面color的属性。比如重新给它赋一个值。
    这是一个非常简单的class,里面有一个属性,还有一个方法,如果想去访问这个属性和访问这个方法的时候,要先把这个类实例化,实例化就是把这个类赋给一个变量,这个变量就是一个对象,这个变量的类型是一个对象,通过这个对象ren去访问里面的属性color以及访问这个think()方法。
 
 
    从类的定义里面我们会看到,这个类的组成呢由属性还有方法组成的,其实这个属性我们可以把它叫做成员变量,这个方法叫做成员函数,22.py脚本最后三行是把类实例化,实例化y就会产生一个对象,那么这个对象呢包括三个方面的特性,有对象的句柄(在这个脚本就是ren这个变量)、对象的属性和方法。对象的属性就是color,还有对象的方法think(),对 对象来说这个属性和方法跟这个类的成员变量和成员函数是一 一对应的,那么只不过名称变了,我们在类里面把它叫做类的属性和类的方法,那么在外面把类实例化之后有了一个对象之后又把它叫做对象的属性和对象的方法。被实例化之后才会产生这样的对象。从这个例子当中能够看到这个对象就是从这个People这个类别当中实例化出来的,那么它继承了这个类别当中的一些属性和方法(继承了color,color的属性它的值yellow,又继承了think方法,think方法下面有这些操作),同时这个对象呢也是一个个体,它与类别呢还有些不一样,比如说属性,我们可以通过对象属性去修改这个值。
    类的属性
    ● 类的属性按使用范围分为公有属性和私有属性,类的属性范围取决于属性的名称。
     ● - 公有属性:  在类中和类外都能调用的属性。(22.py脚本中的color就属性公有属性,self.color = 'black'就是在类中去调用color,print ren.color是在类外调用,跟正常定义变量一样)
     ● -私有属性:不能在类外及被类以外的函数调用(22.py脚本class下面的缩进就是类的内部,外部就是把它实例化之后去调用内部类是不行的,)
        定义方法:以"__"双下划线开始的成员变量就是私有属性
        可以通过instance._classname__attribute方式访问。
    比如在22.py脚本中定义一个私有属性:定义一个__age,它跟color一样,它们都是属于属性,只不过__age多了两个下划线,多了两个下划线表示它是一个内私有属性,私有属性只有在类的内部使用,如果想用的话前面加self,后面写上私有属性,执行后没问题,说明在类的内部可以使用。
    
    那能不能通过刚才实例化对象去调用呢?这个对象是ren,之前调用color是这样调用的,调用这个__age就不行了,下面有一个报错,说这个'People'没有__age属性,表示在外部不能被调用,这样是不行的。我们可以把它放到一个方法里(就是放到一个成员函数里),这样我们间接地调用这个成员函数其实呢也可以去间接地调用内部属性,这么来做,这样是可以的。
    还有一种方法也是可以的,这种方法不建议使用,可以在测试的使用使用一下,ren是实例化的那个对象名,后面跟类的名字,不过前面需要加上下划线,后面写上私有属性的名字。写法是:前面是实例化这个对象的名字,.后面加上这个类的名字,类前面还要加上下划线,最后加上私有属性的名字,私有属性和这个类中间没有小数点.,这样就把这个私有属性从外部也可以访问到了,其实私有属性目的就是为了保护这个私有属性不被外部访问的,所以不建议在外部这么写,私有属性在内部是没有问题是可以使用的,如果想使用私有属性可以把它写到一个方法里,通过调用这个方法间接地去调用这个私有属性,
      ● -内置属性:由系统在定义类的时候默认添加的,由前后双下划线构成,__dict__,__modult__。
        使用方法跟使用正常的公有属性一样,属性后面是没有括号的,方法是有括号的。打印后它的值只有color和black,它把属性和value值都保存到字典里面去了(作为key和value)。
        
    其实像这些属性可以通过对象来访问,也可以通过类来访问,比如说想访问color,也是可以访问的。
    既然这样能不能通过People这个类来访问私有属性呢?可以看到通过正常访问在外部是都不能去访问的,可以使用非正常方法去访问,就是通过实例的名字加上点.下划线加类的名字再加上私有属性,
    对象和类有不一样的地方,比如属性我们可以去修改这个属性的值,实例化一个对象之后又继承了这个类里面的属性和方法,
    默认情况下它不支持中文,要想让脚本支持中文的话这么来写:conding:utf8,这块有好几种写法(最少有三种写法),最简单的就是condig:utf8(utf-8中间加不加-都可以),还可以把coding写成encoding,从网上查的还可以这样写:#-*- encoding:utf-8 -*-
    通过这个对象把属性给修改了,完了之后打印它的值确实被更改了,最后还会打印一个通过这个类来调用的这个属性,它的值还是yellow,这个值没有改,因为实例化完这个对象它就是一个个体,可以通过对象对这个属性做一些操作,但不会影响整个类的,通过这个类去调用属性它还是这个yellow。
    print ren.__dict__这里是通过对象去调用了一个内部属性,那能不能通过类来调用内置属性呢?内置属性不需要去定义。
 
 
    前面课程学习了类的属性,属性分为公有属性和私有属性还有内置属性,那么方法呢和类的属性也一样,也分为公有方法、私有方法,还有类方法和静态方法。
    ● 方法的的定义和函数一样,但是需要self作为第一个参数。
    ● 类方法为:
            - 公有方法
            - 私有方法
            - 类方法
            - 静态方法
    类的方法:
           ● 公有方法:在类中和类外都能调用的方法。
           ● 私有方法:不能被类的外部调用,在方法前面加上"__"双下划线就是私有方法。
           ● self参数
                用于区分函数和类的方法(必须有一个self),self参数表示执行对象本身。 
    cp 22.py 23.py
    这个脚本有一个叫think的公有方法,比如再定义一个叫test的公有方法,然后在类的内部去调用,调用方法和调用属性一样,前面加个self,函数调用,这个函数没有参数。然后通过jack这个变量把这个类进行实例化一下,实例化之后然后去访问一下jack.test()这个方法。跟访问think方法一样。
    self.think()是在内部调用,jack.test()是在外部调用,
    私有方法跟私有属性差不多一样的,在这里定义一下,在test这个方法下面去调用一下,完了之后通过jack对象去访问test方法。
    是不能通过对象来访问呢,会报这个对象没有这个属性的错,跟访问私有属性基本一样的:
    我们发生在定义方法的时候每个参数后面都会多一个self,其实这个self就是为了区别类方法与函数的,在定义函数时是不需要self的,那么类的方法是需要的,而且self是作为第一个参数的,如果后面还有参数呢就跟我们定义函数时带参数是一样的,
    类的方法
    刚才在讲类的属性的时候,可以通过类的名字去调用属性,在这可以不可以通过类的名字去调用方法呢?比如通过People这个类去调用think方法,提示不行,要调用必须通过People这个类的实例来调用。想通过类调用也可以,需要做一些操作,需要通过classmethod()函数做处理。
    ● 类方法:被classmethod()函数处理过的函数,能被类所调用,也能被对象所调用(是继承的关系)
    把test方法变成类方法,只需要通过classmethod()函数处理一下就可以变成类方法,后面参数是函数的名字,如果没有说明不是在调用这个函数,在这里面需要一个返回值,需要把它赋值给变量。这个方法定义跟其他方法定义是一样的,只不过需要通过classmethod()函数去处理,后面参数不要加括号,加括号表示在调用这个test(self)函数了。这种还可以理解为动态方法,就是通过这个类People去访问这个方法test,通过这个类再去访问这个方法的时候,它没有把类当中的其他属性和方法加载到内存里,比如这个方法里出现一些类当中的其他属性和方法,这个时候才会加载,需要哪些才会加载,如果这里 print 'Testing...'没有出现的话是不会加载的,所以说它是一个动态的,也就意味着通过类来访问方法占用的资源很少,def __talk(self):上面的代码都没有被加载到内存里,
    在这def test(self):去访问一个属性,当通过类去访问test方法时,那么这里涉及到了一个属性,它也会把这个属性加载到内存,没有涉及到的其他属性和方法是不会被加载的,这是类方法,跟类属性比较相似。还有一种也是可以通过类来访问,这种方法叫静态方法。
     ● 静态方法:相当于"全局函数",可以被类直接调用,可以被所有实例化对象共享,通过staticmethod()定义,静态方法没有"self"参数。静态方法也是可以通过类来访问的,如果在类里面直接定义一个def test()它叫一个函数,没有self,但是这样是不正确的。现在有一个jack对象,想访问一下test()这个,之前按照访问方法是jack.test()这样来访问的,运行后访问不了。虽然在这里定义了一个函数def test():没有被报错,但是在访问的时候就有错误了,告诉我少一个参数,因为它把它当成一个类的方法,类的方法里面必须得有一个参数self。
    那么在定义类的方法时候,它能不能通过类Pepole调用呢?也提示不能被调用,那么如何才能把它变成一个静态的方法呢?
    在这需要通过一个函数去处理,这个函数叫staticmethod(),括号里还是写上要转成静态的函数名,通过变量名去接收,然后调用的时候直接写这个函数名。这样就可以了,那么def test()就是一个静态方法,跟刚才类的方法区别就是在定义函数的时候没有参数,结果是一样的,它在这里用的是staticmethod()函数处理,虽然看到的结果是一样的,但是运行机制是不一样的。由于这个函数没有self参数,不知道这个类里面都有什么东西,当在通过这个类的名字People来访问这个函数的时候,那么它会所类所有的一些属性和方法都会加载到内存里,不像刚才类的方法是动态的,静态的就是把关于这个类的所有一切东西都加载到内存,因为在定义这个函数的时候没有self,self就表示类的本身,__talk这个方法通过self可以访问到这个类的任何东西(任何一个资源,不管是属性呀还是其他的方法),但是在这里面定义的test()静态方法它这个函数里面没有self,它又不知道这个类有其他的成员,所以说通过这个People类在访问它的时候会把这个类当中所有其他的成员都加载到内存中,这样会占用一些内存资源,但是它的速度要比那个动态的快一些,动态的是要用到哪个成员才会访问,比如访问color,用到了它的属性会访问。在这里test()由于一次都加载到内存里了,它会把所有的信息就是在内存当中直接来访问了。在这个静态访问里面能不能访问类里面其他的成员呢?在这个静态方法里面能不能访问类里面其他成员呢?比如说想访问这个color,之前是通过self.color形式来访问的,在这是不行的,它说全局name没有被定义,它把这个当成全局变量了,因为它不知道这个self是什么,那怎样访问类里其他成员呢?通过类来访问了。
    这样就可以访问了,
    print People.color改成:print People.__age
      ● 装饰器:不需要通过函数去处理,@后面其实就是函数名, @classmethod只对它下面的函数def test():起作用(被它修饰的下面函数起作用),表示这个函数是类方法(需要加上self才表示类方法),在这里面还是通过People访问,直接访问方法的名字。同样的静态方法也是一样的,也可以加上装饰器,在函数的上面加上装饰器,装饰器跟函数名一样,装饰器只对下面一个函数起作用,在访问的时候仍然是通过类来访问,
        - @classmethod:动态方法
        - @staticmethod:静态方法
    
 
Python内部类
    ● 所谓内部类(就是类的嵌套,在类的里面再创建类),就是在类的内部定义的类,主要目的是为了更好的抽象现实世界(更好的把一些事物更抽象一些)。
    ● 例子:
            汽车是个类,汽车的底盘,轮胎也可以抽象为类,将其定义到汽车类中,则形成内部类,更好的描述汽车类,因为底盘、轮胎是汽车的一部分。
    比如os模块下面有很多的,比如path,path下面还有一些属性和方法,跟我们之前讲的那个有些区别,之前讲的类的实例化,实例化下面通过对象直接访问的对象和属性,这里面os.path.下面还有很多属性和方法,就是有好几级,其实这跟那个内部类很相似,比如os是一个外部类,path是内部类,通过内部类访问内部类里面的属性和方法,下面去创建一下。
    # cp 23.py 24.py
    创建一个内部类跟创建一个正常类是一样的,class Chinese(object):print "I am chinese",那么这个内部类怎么去使用呢?现在jack是一个对象,通过jack对象可以访问People里面的属性和方法,那怎么访问Chinese这个类呢?有两种方法
    内部类的实例化方法
    ● 方法1:直接使用外部类调用内部类,下面为格式:外部类的名字.内部类,这样就把它进行实例化了,
            object_name = outclass_name.inclas_name()
        这样直接就把它给打印出来了:
   
    通过对象来访问内部里面的一个属性,这个属性叫name,,现在访问不了,得通过jack.name去访问一下,People是外部类,Chinese是内部类,jack = People.Chinese()就把这个内部类实例化了,实例化的对象叫jack,jack有一个属性叫name,这样就取到了这个属性name的值了,
    ● 方法2:先对外部类进行实例化,然后再实例化内部类
            out_name = outclass_name()
            in_name = out_name.inclass_name()
            in_name.method()
    out_name = outclass_name()这是对外部类进行实例化,然后通过这个实例化的对象out_name实例化内部类,最后通过这个实例化out_name来访问内部类的属性和方法。
    例:先实例化People这个类,完了之后通过实例化这个ren这个对象再去实例化里面内部类(内部类叫Chinese),
    其实这样访问方法有很多,我们也可以这样来访问,我们直接通过类来访问(之前讲过属性,属性可以通过类直接去访问,如果这个类是公有属性的话),我们就想访问Chinese里面的name属性,可以通过下面方法来访问,People是外部类,Chinese是内部类,.后面是类的名字(以前我们访问属性是通过类的名字来访问),
    把print People.Chinese.name改成print People.Chinese().name也可以,因为People.Chinese().name这种写法呢跟object_name = outclass_name.inclas_name()是一样的,直接去给它实例化,通过外部类然后是.后面是内部类的名字name加()来进行实例化,People.Chinese().name实例化它有一个实例化的对象,下面有一个name属性,而print People.Chinese.name(没有括号)是通过类的方法来访问属性的,People.Chinese().name是通过对象的方式来访问的,People.Chinese()这个就相当于是一个对象,People.Chinese是一个类,结果却都是一样的,都是对属性访问,对属性访问可以通过实例化后的对象访问,也可以通过类来访问,因为这个属性name是一个公有属性,
    下面来看一些方法:
    魔术方法
        ● __str__(self)
          ● 构造函数与析构函数
                - 构造函数:
                    用于初始化类的内部状态,Python提供的构造函数是__init__();
                    __init__()方法是可选的,如果不提供,Python会给出一个默认的__init__方法
                - 析构函数:
                    用于释放对象占用的资源,Python提供的析构函数是__del__();
                    __del__()也是可选的,如果不提供,则Python会在后台提供默认析构函数
    我们通常把这些以__双下划线开关、以__双下划线结尾的这些方法叫做类的内置方法或者魔术方法,
    比如24.py,刚才把People类给实例化了,那么通过实例化对象ren可以去访问里面的属性和访问,那么直接去看下这个实例化的ren是一个什么的呢?打印出来的是一个<__main__.People object at 0x16cc1d0>,一个尖角<>说明它是一个对象,它是一个对象在这显示呢不是很友好,People object意思是它是People这个对象,属性People类,后面0x16cc1d0是它的内存地址。
    用__str__(self)改写一些可以让输出的更友好一些,def __str__(self):里面不能用print只能用return,那么这个__str__(self)用不用在后面通过这个ren这个对象来调用呢?其实不用,直接print ren这个对象就行了,它会打印出str return这个值,不需要去调用,我们在对这个类进行实例化的时候,默认就会去执行这个方法,不需要手动去打印一下(不需要print ren.__str__()),
    构造通常这么来定义:def __init__(self):,初始化类的,在这里面把color的值给修改为'Black',把这个属性更改完之后在最后再看一下,之前值是'yellow',现在被改成了'yellow',那么当我对这个类做实例化的时候再通过这个对象来访问这个color,看这个color的值是什么,结果显示它变成了Black,这是通过对象来访问的,
    
    那如果通过类来访问这个属性呢?在类里面定义这个属性是yellow,那么通过初始化这个函数去把color这个属性给它改变了一下值,变成了Black,通过对象去访问的时候值就变成了初始化更改过的值,如果通过类去访问这个属性的值还会是原来的值,通过对象访问就变成了在初始化里面设的值Black。
    def __init__(self):这个函数也是自动执行的,不需要调用,那它什么时候会去执行呢?在我们把类进行实例化的时候就会自动去执行里面的东西。通常我们会初始化一些参数,通常会把参数写到self的后面,比如在这传个参数c='White',那么在实例化的时候在这ren = People()也可以给它传个参数,如果传的话在People()括号里面写,但这里面有个默认参数,如果不传的话就是c='White',这样写完之后查看输出是否有改变呢?ren = People()相当于传了一个参数,没有写就相当于传了一个参数White,之后打印了一下color,self.color = 'Black'这一块应该修改成参数self.color = c。打印结果显示:通过类来调用的属性没有改变,通过对象来调用的属性变成了White,而White是在初始化函数的时候通过self.color = c设置的这个,
    把ren = People()修改一下并传个值jack = People('green'),在下面访问一下,在这传的是green,然后被c='White'这个参数c给接收就成green了。
    通过对象来访问的属性会根据传的值不同会改变,而根据类去访问的属性它的值是不会变的。初始化函数里面不仅可以放这些属性而且还可以放方法,比如把think这个方法放里面,I am a black相当于执行了think,那么在下面(最后几行)没有调用think方法,那么它是怎么执行呢?是因为把它写到了初始化函数里面,因为这个初始化函数呢也叫构造函数,不需要我们去调用,那么当我们把一个类进行实例化的时候它会自动去执行,它会执行这两行self.color = c(设成green)、 self.think()(设成green之后再调用这个self.think()),在调用self.think()的时候又把self.color = 'black'中的color重新赋了一个值,完了之后又print了三行,print jack.color、print People.color这两行是后面black、yellow的结果,类调用的是yellow,这个是初始化函数。
    跟初始化函数对应的是析构函数,   主要用于释放资源的,比如在脚本打开了一些资源(比如打开一个网络连接呀、一个socket呀,打开一个文件),最后都可以通过一个__del__()内置的方法去把这些资源去给关闭,它是在脚本的最后会执行这个方法,通常这么来定义:def __del__(self):,我们在def __init__下面打印一句话print "Init......"看是否真的执行了,确实打印了Init......
    
    __del__是在最后,通常在前面构造函数里打开了一个文件(open了一个文件),那么可以在del里面把它给关掉self.fd.close(),然后再看下del是什么时候执行的print "Del......"。
    del是在最后执行的,最后是在类调用完之后还是脚本调用完之后呢?可以这么来看一下,在主程序里面写上这样一个print:print "Main end",结果显示它是在脚本退出之后(在脚本执行的最后去执行Del这个方法),它主要是释放一些资源,其实在我们__del__这个析构函数里面如果没有把资源释放也不要紧,Python会有一个自动的垃圾回收机制。
    垃圾回收机制(通过gc这个模块来实现的):
        ● Python采用垃圾回收机制来清理不再使用的对象:Python提供gc模块释放不再使用的对象
          ● Python采用"引用计数"的算法方式来处理回收,即:当某个对象在其作用域内不再被其他对象引用的时候,Python就自动清除对象;
          ● gc模块的collect()可以一次性收集所有待处理的对象(gc.collect)
    如果想用这个模块的话先import进来,最后可以通过collect()这样的方法可以去查看,它是采用"引用计数"的方法来计算有没有回收的,可以在这print gc.collect(),如果它的值呢如果是0的话表示没有回收的,
    打印两个都显示0,其实我们不用关心,Python会自动把这些垃圾进行回收,因为这个模块gc呢它在后台呢就是做自动清理的工作,刚才看到都0表示没有可回收的,其实在后台它已经做得很好了,把后台一些垃圾已经回收了,
 
 
类的继承
● 继承是面向对象的重要特性之一;
● 继承关系:继承是相对两个类而言的父子关系(一个类去继承父类,子承父业,),子类继承了父类的所有公有属性和方法
● 继承实现了(继承的最大就是就是)代码重用。
面向对象的三个特点是封装、继承还有多态,前面讲的都是封装,就是如何去创建一个类,在类里面去创建属性和方法以及内部类,这些都属性封装。
    第二个特点是继承,
    使用继承
    ● 继承可以重用已经存在的数据和行为(就是有的一些属性和方法可以重用),减少代码的重复编写。Python在类名后使用一对括号来表示继承关系,括号中的类即为父类。
    ● class Myclass(ParentClass)    (这句代码表示Myclass继承了ParentClass这个类,那么ParentClass里面有的属性和方法在Myclass里面都可以去使用,)
        如果父类定义了__init__方法,子类必须显式调用父类的__init__方法:
        ParentClass.__init__(self,[args...])
        如果子类需要扩展父类的行为,可以添加__init__方法的参数。
    下面例子如何显示类的继承:
    现在有一个类叫People,这个类里面有一些属性和一些方法,在这里面想继承的话再创建一个类,这个类名字叫Chinese,括号里面写上父类People,表示继承的意思,假如后面什么都不写(用pass表示),其实在Chinese这个类里面呢就可以使用color、think,现在把Chinese实例化一下,然后看下cn.color
    看到的值yellow是父类定义的,同样也可以调用父类里面的think,
    这是简单的继承,那么有的的时候呢在这个父类中有一些构造函数,比如在父类里面创建一个__init__函数,然后在这里面再定义一个变量,定义一个属性(居住在什么时候,如居住在地球),那么现在在子类当中能否访问dwell这个属性呢?没问题是可以访问到的,
    上面说如果父类定义了__init__方法,子类必须显式调用父类的__init__方法,在这里显然并没有去调用的,它也能去访问,我们看下它到底有没有去执行呢?执行了,意味着子类呢只要这么去写Chinese(People):就继承了父类,继承了父类呢那么父类的初始化函数也会去执行。
     下图中只要产生这么一个对象那么就执行了,但是有的时候给构造函数传一个参数,但这个参数呢没有进行操作,那么在子类中这么写class Chinese(People):pass行不行呢?
    运行后会报错,提示要给两个参数,但只给了一个,class Chinese(People):pass这么写其实也在调用这个父类__init__函数,但是呢它默认只传一个参数(是self),没有这个c,在这里class Chinese(People):pass就不行了,那么如果想继承这个父类的话还得在这个子类当中去改写这个__init__,在这里去继承,那么怎么去写呢?语法是这样的,写上父类类名的名字,__init__表示执行父类的这个构造函数,self,后面是一个字符串,这样就没问题的。
    当父类当中__init__函数的参数多于或大于2个时,在子类调用的时候一定要显式地调用父类的__init__,就像下面的写法,把父类的__init__去调用一下表示去继承父类,否则会有这样的参数不一致的报错。
    上面是子类继承父类,其实在定义类的时候有两种方法,一种是传统的方法,另外一种是new_style,像刚才定义的就是new_style,所谓new_style就是在定义类的时候可以加括号,括号里是object,传统的方法是后面没有object,甚至连括号都没有,
    在这里Chinese是继承了父类,括号里面一定要写上父类的名字class Chinese(People):,
    还有一种方法继承父类可以通过叫super的内置函数也可以去继承父类,它的语法格式是在子类当中通过super函数,括号里面写上子类的名字加逗号再加self,小数点后面加__init__(),其中__init__是父类的构造函数。
    super函数
● class A(object):
●     def __init__(self):
●     print "enter A"
●     print "leave A"
 
● class B(A):
●     def __init__(self):
●     print "enter B"
●     super(B,self).__init__()
●     print "leave B"
 
● b = B()
    因为是内置函数查看一下帮助:help(super),它的参数是obj和type,下面有个例子,在这里面有一个class C,它继承了它的父类B,那么怎么调用呢?supper(C,self).meth(arg):C是自己类的名字,然后是self,加小数点.,meth(arg)是父类的构造函数
    这里面报错,其实这个报错跟写的语法没有关系的,报错的原因是这个类的定义这块,这里面用的类是传统风格,需要修改一个,而这个super只支持new_style,所谓new_style就是在定义类的时候有个object,
    
    下面那个位置只需要一个参数就可以了,因为这个self已经写到前面super(Chinese,self).__init__('red')去了,它就是这种语法结构。
    class People(object):从new_style变成class People: 这样的传统style是不可以的,会出现下面的错误,如果出现这种错误说明类的定义是有问题的,它采用的是传统的方式,所谓传统方式就是class People后面没有括号或者后面有括号但是括号里面是空的,
    上面是类的继承,继承之后呢就可以在这个Chinese类里面去使用父类里面的公有属性和公有方法了,一定要记住在父类当中如果有构造函数的话那么在子类当中也要通过一个构造函数去把这个父类给继承过来。这里面有两种继承方式,一种是直接通过类的名字然后是.__init__然后执行构造函数,另外一种是通过super内置函数,建议大家定义类的时候从使用object这种方法,这种方法写的类在继承的时候可以直接使用super函数就可以继承了,如果使用的是传统的风格,那么其他从就还能通过super函数来继承了。
    当然,不管是传统方式还是新形的方式我们都可以通过A.__init__(self)这种格式去继承父类,就是刚才我们所说的第一种格式:父类的名字+构造函数__init__,括号里是self,再然后后面是它的参数,参数怎么看呢?就是看它的构造函数(self,c),下面(self,'read')构造函数里面就是它的参数,
    这也是子类:
    其实在子类当中还可以自己定义自己的属性和方法,比如下面定义一个方法,然后可以直接去调用,非常方便,有了父类之后节省了很多工作,像它的属性和方法直接拿来用就行了,所以类就实现了这种继承,那么最大的好处就是实现了代码的重用,甚至还可以修改父类的方法,
    比如把def talk(self):修改think的方法,修改成这样def think(self):之后,那么在cn.think()调用是调用父类呢还是调用自己呢?父类的输出是两行:print "I am a %s" % self.color和print "I am a thinker",子类输出只有一行:print "I like talking",最后打印结果是输出自己的。可以修改父类的方法,修改完之后呢调用的时候是调用自己的(自己修改过的),
 
 
多重继承
    ● Python支持多重继承,即一个类可以继承多个父类:
        ● 语法:
                class class_name(Parent_c1,Parent_c2,...)  (跟原来一样,就是后面多几个父类的名字)
          ● 注意:
                当父类中出现多个自定义的__init__方法时,多重继承只执行第一个类的__init__方法,其他不执行。 
vim 26.py,在这里面再定义一个类Martian,再定义一个color属性,再定义一个构造函数。People里有一个属性,下面def __init__(self):是一个构造函数,再下面def think(self):是一个方法,下面color是Martian的属性,def __init__(self):也是一个构造函数,然后要去继承一下,比如先继承People,再继承Martian这个,接下来去调用一下think,
执行后报错,多了个'read',刚才把参数c给删除掉了。
    
    现在呢把print "Init..."调换位置,先是继承的是People,继承People里面的color属性,还有People里的think,现在把位置颠倒一下,先继承Martian这个,再继承People,下面先不写这个构造函数,然后看下结果:
    在去调用def think(self):这个方法的时候,它把这个类Martian的color属性和dwell属性传进去了,传到这个think里面去了,其实这是不对的,因为注意里面说了,当父类中出现多个自定义构造函数时,多重继承只执行第一个类的__init__方法。也就是说哪个类在前面它就会把这些类的属性都继承,它不像是变量,变量可能是最后定义的那么保存最后一个值,而继承呢,先继承它的属性就是第一个了,所以它把第一个类的属性传到了这个think里面去调用了,其实我们是想调用People这个类,我们可以显式地去调用一下,显式调用就是定义一个构造函数,这样就是在调用People里面的构造函数,其实People里面的构造函数就一个,而这个color属性类的属性,它没有在构造函数里。我们先看下结果:结果就是在def __init__(self):这里面显式地去调用继承了People这个类,虽然People写在了后面了,但是却显式地去调用People这个类,那么也就会说它会去执行People里面的self.dwell属性,颜色还是red这是不对的,我们应该让它继承People里面的color,
    可以这么写,把color写到构造函数里,这回这个结果就对了。
    多重继承跟这个位置很有关系,那么谁写在前面就用谁的属性和方法,当然了那么如果我的People里面的属性和方法跟前面不一样的话当然这个People里面的属性和方法也是可以用的。如果def __init__(self):里面还有方法,那么这个定义的talk方法能还能用呢?在初始化函数def __init__(self):里面先是调用的是People这个类,那么凡是继承的这些类,那么它里面的属性和方法都是可用的,所以说这个talk也是可用的。
    唯独的区别就是如果父类当中都有构造函数的话那么默认情况下会使用class Chinese(Martian,People):这里面第一个类里面的构造函数,那么如果想继承其他类里面的初始化函数在这里面显式地去调用或者去继承。有单一继承还有多重继承,更多的时候我们可能继承一个类就可以了。
 
 
 
 
posted @ 2017-12-03 23:36  落后乞丐  阅读(56)  评论(0)    收藏  举报