python面向对象编程

  • 示例代码
    复制代码
    """Framework for getting filetype-specific metadata.
    
    Instantiate appropriate class with filename.  Returned object acts like a
    dictionary, with key-value pairs for each piece of metadata.
        import fileinfo
        info = fileinfo.MP3FileInfo("/music/ap/mahadeva.mp3")
        print "\\n".join(["%s=%s" % (k, v) for k, v in info.items()])
    
    Or use listDirectory function to get info on all files in a directory.
        for info in fileinfo.listDirectory("/music/ap/", [".mp3"]):
            ...
    
    Framework can be extended by adding classes for particular file types, e.g.
    HTMLFileInfo, MPGFileInfo, DOCFileInfo.  Each class is completely responsible for
    parsing its files appropriately; see MP3FileInfo for example.
    """
    import os
    import sys
    from UserDict import UserDict
    
    def stripnulls(data)
        "strip whitespace and nulls"
        return data.replace("\00", "").strip()
    
    class FileInfo(UserDict):
        "store file metadata"
        def __init__(self, filename=None):
            UserDict.__init__(self)
            self["name"] = filename
    
    class MP3FileInfo(FileInfo):
        "store ID3v1.0 MP3 tags"
        tagDataMap = {"title"   : (  3,  33, stripnulls),
                      "artist"  : ( 33,  63, stripnulls),
                      "album"   : ( 63,  93, stripnulls),
                      "year"    : ( 93,  97, stripnulls),
                      "comment" : ( 97, 126, stripnulls),
                      "genre"   : (127, 128, ord)}
    
        def __parse(self, filename):
            "parse ID3v1.0 tags from MP3 file"
            self.clear()
            try:                               
                fsock = open(filename, "rb", 0)
                try:                           
                    fsock.seek(-128, 2)        
                    tagdata = fsock.read(128)  
                finally:                       
                    fsock.close()              
                if tagdata[:3] == "TAG":
                    for tag, (start, end, parseFunc) in self.tagDataMap.items():
                        self[tag] = parseFunc(tagdata[start:end])               
            except IOError:                    
                pass                           
    
        def __setitem__(self, key, item):
            if key == "name" and item:
                self.__parse(item)
            FileInfo.__setitem__(self, key, item)
    
    def listDirectory(directory, fileExtList):
        "get list of file info objects for files of particular extensions"
        fileExtList = [ext.upper() for ext in fileExtList]
        fileList = [os.path.join(directory, f) for f in os.listdir(directory) \
                    if os.path.splitext(f)[1].upper() in fileExtList]
        def getFileInfoClass(filename, module=sys.modules[FileInfo.__module__]):
            "get file info class from filename extension"
            subclass = "%sFileInfo" % os.path.splitext(filename)[1].upper()[1:]
            return hasattr(module, subclass) and getattr(module, subclass) or FileInfo
        return [getFileInfoClass(f)(f) for f in fileList]
    
    if __name__ == "__main__":
        for info in listDirectory("/music/_singles/", [".mp3"]): 
            print "\n".join(["%(key)s=%(value)s" % vars() for key, value in info.items()])
            print
    复制代码

    修改目录路径到自己机器的MP3文件路径

  1. 面向对象综述:模块,类,函数,文档字符串,读代码找到总体印象,Python是完全面向对象的。
  2. 定义类:可以自己定义类,从自己的或者是内置的类继承,然后对类进行实例化;只要定义类,然后编码就行了;类以保留子class开始,跟着是类的名字;
    • 最简单的python类
      class foo:      #类的名字,没有从任何地方继承。
          pass          #python保留字,是一条什么都不做的语句,删除函数或者类的时候是一个很好的占位符

      实际上大多数的类都是继承而来的,然后定义自己的类方法和属性,Python 有一个同c++构造器类似的东西__init__方法。

    • 定义FIleInfo类
      复制代码
      from UserDict import UserDict
      
      class FileInfo(UserDict):   #FileInfo是从UserDict类继承而来的,是一个像字典一样的动作的类,允许完全子类化字典数据类型,同时增加自己的行为
          "store file metadata"       #文档字符串
          def __init__(self, filename=None):   #__init__在类的实例创建后即被调用,可能会诱使你,但是不正确的,叫它为类的构造器。看上去像是构造器,是类第一个定义的方法,在一个新创建的类实例中,它是首先被执行的代码,不正确的是,是一位对象在__init__被调用的时候已经被构造出来了,已经有了对类的新实例一个有效的引用。 #self,每一个类的方法的第一个参数,都是指向了类的当前实例的一个引用,按照习惯被定义成self。像c++中的this,__init__中self指向新创建的对象,在其他方法中,他指向方法被调用的对象。 #__init__方法可以接受任意个数的参数,就像函数一样,参数可以用缺省值定义,可以设置成对于调用者可选
              UserDict.__init__(self)   #一些伪面向对象语言,父类的方法在子类的方法执行前被自动调用,Python不行,必须要显示调用在父类中合适的方法。而且调用父类的方法的时候要包含参数self,跟着类方法所接受的任何其他类型。此处,除了self没有使用其他参数调用UserDict类的__init__方法
               self["name"] = filename   #这个类像字典一样动作,将参数filename赋值给对象name关键字,作为它的值。init方法不返回任何东西。
      复制代码

      每个类的方法的第一个参数都命名为self,是一个强烈的习惯。

      __init__方法是可选的,但是一旦定义了一个,就必须记得显示调用父类的__init__方法。注意:任何一个子类想要扩展父类的行为,后代方法必须在适当的时机,使用适当的参数,显示的调用父类的方法。

  3. 类的实例化:实例化一个类,只需要简单的调用它,传递给它的是__init__函数中的参数。返回值将是新创建的对象,没有c++中的new操作符。
    • FileInfo实例(定义在fileinfo模块里)
      复制代码
      >>>import fileinfo
      >>>f = fileinfo.FileInfo("/media/shi.mp3")  #创建一个实例,赋给变量f,传入一个参数,即__init__里边的filename
      >>> f.__class__                                          #对象的类是内置属性
      <class fileinfo.FileInfo at 010EC204>
      >>> f.__doc__                                             #一个类的所有实例共享一个字符串
      'base class for file info'
      >>> f                                                              #init将文件名参数赋值给self['name'],传递的参数从右向左发送给init方法
      {'name':'/media/shi.mp3'}
      复制代码

      不需要显示的释放实例,应为当它们赋值给的变量超出作用域的时候,自动的释放,内存泄露在python中很少见。

    1. 尝试内存泄露
      >>> def leakmem():
      ...            f = fileinfo.FileInfo('/shi.mp3') #每次leakmen被调用,创建一个实例给变量f,这个变量是函数内的一个局部变量。然后函数结束没有释放f,认为有泄露,但是不是,函数结束的时候f已经释放掉了
      ...
      >>> for i in range(100):  #每一次调用leakmem函数,决不会泄露内存,每一次,python将在leakmem返回前破坏掉新创建的FileInfo类。
      ...            leakmem()

      对于这种垃圾收集的方式,技术上的术语叫做‘引用计数’。python维护着为每一个实例创建的实例的引用列表,上边对实例的引用只有一个f,函数结束的时候,f超出了作用域,所以引用计数变成0,python自动破坏实例。

  4. UserDict:一个封装类
    • 在UserDict模块中的UserDict类,是FileInfo类的父类,没什么特别的。
    • 不能子类化字符串,列表,字典的内置数据类型,作为补充,提供了封装类,可以模拟这些内置数据类型的行为,UserString,UserList,UserDict通过使用一个由普通和专用方法组成的联合体,UserDict是一个字典的出色模仿品,仅仅是一个类它,可以对它进行子类化,提供可定制的字典的类,比如,FileInfo。
      class UserDict:                             #是一个基类,不是继承而来
          def __init__(self, dict=None):             #2
              self.data = {}                         #3
              if dict is not None: self.update(dict) #4

       2,FIleInfo中覆盖了的 __init__方法。这个父类的参数与子类的不同,有的语言支持通过参数列表的函数重载,也就是,同名的多个函数,有着不同个数的参数,或者不同类型的参数。另外的语言甚至支持通过参数名的重载,也就是,同名的多个函数,有相同个数相同类型的参数,但是参数名字不同。这个python 都是不支持的,不管怎么样都没有函数重载的方式。一个__init__方法就是一个__init__方法,不管它有什么样的参数,每个类只能拥有一个__init__方法,如果一个子类拥有一个__init__方法,它总是覆盖父类的__init__方法,甚至子类可以用不同的参数列表来定义它。3,python 支持数据属性即数据成员,它是由某个特定的实例所拥有的属性,要从类外引用这个名字需要在名字前边加上实例的名字来限制它,instanc.data,限定的方法与你用模块的名字来限定函数一样。要在类的内部引用一个数据属性,使用self作为限定符。为了方便,所有的数据属性的值都在__init__方法中初始化为有意义的值,然而这并不是必须的,因为数据属性,像局部变量一样,首次赋值给它的时候突然产生。4,当在一个块中仅有一条语句的一个简写,也可以用缩进代码,但是不能混用。

    • UserDict普通方法
      复制代码
      def cleaf(self): self.data.clear()                               #1
      def copy(self):                                                  #2
           if self.__class__ is UserDict:                              #3
              return UserDict(self.data)                     
          import copy                                                  #4
          return copy.copy(self)
      def keys(self): return self.data.keys()                          #5
      def items(self):return self.data.items()
      def values(self): return self.data.values()
      复制代码

      1,python类中的普通方法,python 不会替你调用,只是像类的一般方法一样调用,类封装的基本技术:保存一个真正的字典作为数据属性,定义所有的字典有拥有的方法,并且将每个类方法重定向到真正字典上的响应方法。(clear方法删除它所有关键字和对应的值)。2,真正的字典copy方法会返回一个新的字典,是原始字典原样的复制。但是不能简单的重定向到self.data.copy,因为那个方法返回的是一个真正的字典,而我们想返回的是同一个类的一个新的实例,就像是self。3,使用__class__属性来查看self是否是一个userdict,如果是 就创建一个新的userdict实例返回,并传给他真正的字典,这个字典已经放在了self.data中了。4,self.__class__不是Userdict,那么self一定是userdict的某个子类,userdict不知道怎么生成一个它的子类的一个原样的拷贝,有可能在子类中定义了一些新的数据属性,所以我们只能完全拷贝他们,确定拷贝了他们的全部内容。幸运的是,python的一个模块可以正确的完成这件事情,叫做copy,copy能够拷贝任何的python对象return copy.copy(self)。5,其他的方法直接重定向到self.data的内置函数上去。

  5. 专用类方法
    • 除了普通的类方法,还要有一些对于Python类可以定义的专用方法,专用方法是在特殊情况下或者是当使用特别的语法时候python替你调用的,而不是在代码中直接调用,上一节中,普通的方法对在类中封装字典很有帮助。但是只有普通方法是不够的,因为除了字典调用方法之外,还有很多事情可以做,例如,可以通过一种没有包含显示方式调用的语法来得到和设置数据项,这就是专用方法产生的原因:提供了一种方法,可以将非方法调用影射到方法调用上。
    • __getitem__专用方法
      复制代码
      def __getitem__(self, key): return self.data[key]
      >>f = fileinfo.FileInfo("/music/ka.mp3")
      >>f
      {"name":"/music/ka.mp3"}
      >>f.__getitem__("name")        #这个方法只是重定向到字典,返回了字典的值,但是怎么调用它呢,可以直接调用这个方法,但是实际的操作过程当中不会那样做,在这里执行只是告诉怎么工作的,正确的调用__getitem__是python替我们调用的
      "music/ka.mp3"
      >>f["name"]                                #这个看上去像一个得到字典值的语法,事实上它返回你期望的值。暗地里边python已经转化了过程。其实是:f.__getitme__("name")的方法调用。这就是为什么说是专用的类方法,可以自己调用也可以python帮助我们调用,"music/ka.mp3"
      复制代码
    • __setitem__专用方法
      复制代码
      def __setitem__(self, key, item): self.data[key]=item
      >>> f
      {'name':'/music/_singles/kairo.mp3'}
      >>> f.__setitem__("genre", 31)                   #通常不会直接调用的
      >>> f
      {'name':'/music/_singles/kairo.mp3', 'genre':31}
      >>> f["genre"] = 32                                          #暗地调用了上边的方法
      >>> f
      {'name':'/music/_singles/kairo.mp3', 'genre':32}
      复制代码

      __setitem__是一个专用类方法,也仍然是一个类方法,在userdict中定义__setitem__方法一样容易,我们可以在子类中重新对它进行定义,对父类的方法进行重新覆盖,允许我们定义出某些方面像字典的类,但是我们可以自己定义它的行为,超过和超出内置的字典。这个是学习的整个框架的基础。每个文件类型可以拥有一个处理器类,这些类知道如何从一个特殊的文类型得到元数据。一旦知道了某些属性比如文件名和位置,处理器类就知道如何自动的得到其他的属性。它的实现是通过覆盖__setitem__方法,检查特别的关键字,然后当找到后加入额外的处理。

    • 在MP3FileInfo中覆盖__setitem__:MP3FileInfo是FileInfo的一个子类,在设置一个MP3FileInfo的name时候,并不只是设置了name关键字(父类中这么做的),它还要在文件自身内部进行搜索MP3的标记然后填充一整套关键字集合。
      复制代码
      def __setitem(self, key, item):            #进行定义的时候要严格按照父类方法相同的形式定义,技术上说,参数的名字没有关系,只是个数
          if key == 'name' and item             #如果想给name赋值我们还想做额外的事情。
              self.__parse(item)                #对name所作的额外处理封装在了__parase方法中,这是定义在MP3FileInfo中的另一个类的方法,我们调用它的时候,使用self对其限定,否则仅仅是调用__parse将只会看成定义在类中的一个类方法,当然用同样的方法来引用数据属性
          FileInfo.__setitem__(self, key, item)  #做完额外的处理时后,需要调用父类的方法,Python中不会自动完成,需要手工进行执行,尽管没有一个直接的类方法,我们还是直接用父类调用,Python会沿着父类树走,直到它找到一个有着我们正在调用方法的类,这行代码最终会找到并且调用定义在userdict中的__setitem__
      复制代码

      在一个类中存取数据类型的时候,需要限定属性名字:self.attribute, 调用类中的其他方法的时候,需要限定方法:self.method。

    • 设置一个MP3FileInfo的name
      复制代码
      >>> import fileinfo
      >>> mp3file = fileinfo.MP3FileInfo()                #创建一个实例,但是没有传递问键名字,应为__init__方法中的filename参数是可选的,MP3FileInfo中没有__init__方法,沿着父类树走,在FileInfo中的__init__方法,这个__init__方法手工调用了父类的__init__方法,然后设置name关键字为filename,它为none。因为我们没传入一个文件名,所以mp3file最初看上去像有关键字
      >>> mp3file
      {'name':None}
      >>> mp3file["name"] = "/music/_singles/kairo.mp3"     #这个时候设置name关键字触发了MP3FileInfo的__setitem__(不是userdict),这个方法我们用一个真实的值来设置name关键字,接着调用self.__parse方法。通过输出可以看到,设置了其他的几个关键字
      >>> mp3file
      {'album': 'Rave Mix', 'artist': '***DJ MARY-JANE***', 'genre': 31,
      'title': 'KAIRO****THE BEST GOA', 'name': '/music/_singles/kairo.mp3',
      'year': '2000', 'comment': 'http://mp3.com/DJMARYJANE'}
      >>> mp3file["name"] = "/music/_singles/sidewinder.mp3"  #python 调用__setitem__,__setitem__调用__parse,self.parse设置其他所有的关键字。
      >>> mp3file
      {'album': '', 'artist': 'The Cynic Project', 'genre': 18, 'title': 'Sidewinder', 
      'name': '/music/_singles/sidewinder.mp3', 'year': '2000', 
      'comment': 'http://mp3.com/cynicproject'}
      复制代码
  6. 高级专用类方法:刚才的基础上还有更多的专用函数,模拟出许多甚至可能不知道的功能
    • 在UserDict中更多的专用方法
      复制代码
      def __repr__(self): return repr(self.data) #返回一个对象的字符串表示。可以用再任何对象上,而不仅仅是实例,
      def __cmp__(self, dict):                              #在比较类实例的时候被调用,通常可以使用==比较任意两个Python对象,不只是实例。有一些规则,定义何时内置数据类型被认为是相等的,例如,字典再有着相同的关键字和值的时候是相等的。对于类实例可以自己编写比较逻辑,然后可以使用==来比较你的类,python将会替你调用写的__cmp__专用方法。
          if isinstance(dict, UserDict):
              return cmp(self, data, dict.data)
      def __len__(self): return len(self.data)      #返回一个对象的长度,是一个内置的函数len(instance),字典的len是它的关键字的个数,列表或者序列的是元素的个数,对于类实例,定义len方法,可以自己编写长度的计算,然后调用len(instance),python替你调用__len__方法。
      def __delitem(self, key): del self.data[key] #从字典中删除单个元素的方法,调用 del instance[key]。
      复制代码
    • 所有的这些操作只是为了在类中做一些我可以对一个内置数据类型所作的操作,不错,如果你能够从像字典一样的内置数据类型进行继承的话,事情就容易多了,但是也许可以,专用方法仍然是有用的,因为他们可以用给任何类,而不是只像userdict的封装类。
    • 专用方法意味着,任何类可以像字典一样保存键-值对,只要定义__setitem__对任何类可以表现的像一个序列,只要通过定义__getitem__方法,任何定义了__cmp__方法的类可以用==进行比较,并且如果你的类表现拥有类似长度的东西,不要定义GetLength方法,而定义__len__方法,使用len(instance)。
    • 存在许多其他的专用方法。有一整套的专用方法,可以让类表现得像数值一样,允许你在类上进行加,减,和执行其他数学操作,__call__方法让一个类表现的像一个函数,允许你直接调用一个类实例,并且存在其他的专用函数,允许类只有读或者写的数据属性。
  7. 类属性:数据属性是被一个特定类定例所拥有的变量。python也支持类属性,由类本身拥有的。
    • 类属性介绍
      复制代码
      class MP3FileInfo(FileInfo):
          "store ID3v1.0 MP3 tags"
          tagDataMap = {"title"   : (  3,  33, stripnulls),
                        "artist"  : ( 33,  63, stripnulls),
                        "album"   : ( 63,  93, stripnulls),
                        "year"    : ( 93,  97, stripnulls),
                        "comment" : ( 97, 126, stripnulls),
                        "genre"   : (127, 128, ord)}
      
      >>>import fileinfo
      >>>fileinfo.MP3FileInfo                  # MP3FileInfo是类本身不是任何类实例
      <class fileinfo.MP3FileInfo at 01257fdc
      >>>fileinfo.MP3FileInfo.tagDataMap #tagDataMap是类属性,是创建任何实例之前就有效了。
      {'title': (3, 33, <function stripnulls at 0260C8D4>), 
      'genre': (127, 128, <built-in function ord>), 
      'artist': (33, 63, <function stripnulls at 0260C8D4>), 
      'year': (93, 97, <function stripnulls at 0260C8D4>), 
      'comment': (97, 126, <function stripnulls at 0260C8D4>), 
      'album': (63, 93, <function stripnulls at 0260C8D4>)}
      >>>m  = fileinfo.MP3FileInfo()    #类属性既可以通过直接对类的引用,也可以通过对类的任意实例的引用来使用
      >>>m.tagDataMap
      {'title': (3, 33, <function stripnulls at 0260C8D4>), 
      'genre': (127, 128, <built-in function ord>), 
      'artist': (33, 63, <function stripnulls at 0260C8D4>), 
      'year': (93, 97, <function stripnulls at 0260C8D4>), 
      'comment': (97, 126, <function stripnulls at 0260C8D4>), 
      'album': (63, 93, <function stripnulls at 0260C8D4>)}
      复制代码

      类属性可以作为类级别的常量来使用,这就是为什么我们在MP3FileInfo中使用他们,但是他们不是真正的常量,可以修改他们

    • 修改类属性
      复制代码
      >>> class counter:
      ...     count = 0                                          #count是counter类的一个类属性
      ...     def __init__(self):
      ...             self.__class__.count += 1    #__class__是每个类实例的一个内置属性,也是每个类的。它是一个类的引用,而self是一个类的实例。
      ... 
      >>> counter
      <class __main__.counter at 0x84a929c>
      >>> counter.count                                   #count是一个类属性,可以在创建任何类实例前通过直接对类的引用而得到
      0
      >>> c = counter()                                     #创建一个类实例会调用__init__方法,方法中会给类属性count加1,这样会影响到类本身,不只是新创建的实例。
      >>> c.count
      1
      >>> counter.count
      1
      >>> d = counter()                                     #创建第二个实例会再次增加类属性count,类属性被类和所有类实例所共享,有些语言叫做共享变量。
      >>> d.count
      2
      >>> c.count
      2
      >>> counter.count
      2
      复制代码
  8. 私有函数
    • python也有私有函数的概念,私有函数不可以在他们的模块外边被调用,私有类方法,不能够从他们的类外面被调用,私有属性不能从他们的类外边被使用,一个python函数,方法,属性是私有还是公有,完全取决于它的名字。
    • 在MP3FileInfo中有两个方法,__parse和__setitem__。正如我们已经通过的,__setitem__是一个专有方法,通常,不直接调用它而是通过一个类上使用字典语法来调用它,但它是公有的,并且如果有一个好的理由,可以直接调用它(甚至在fileinfo模块的外边),但是,__parse是私有的,因为它的名字前边有两个下划线。
      • 如果一个Python函数的名字,类方法,或属性以两个下划线开始(但不是结束),它是私有的;其他所有的都是公有的。
      • 在python中,所有的专用方法(__setitem__)或者内置属性(像__doc__)遵守一个标准的命名习惯:开始和结束都有两个下划线,不要对自己的方法和属性用这种方法命名,后便只会搞乱你。
      • python没有类方法保护的概念,只能用于他们自己类和子父类中,类方法要不私有(只能在自己类中使用)要不公有(任何地方都可以使用)
    • 尝试调用一个私有方法
      复制代码
      >>>import fileinfo
      >>>m = fileinfo.MP3FileInfo()
      >>>m.__parse("/musi/shi.mp3")  #试图调用一个类的私有方法,引发一个有些误导的异常,称那个方法不存在,它确实存在,但是它是私有的,在类外是不可使用的
      Traceback (innermost last):
        File "<interactive input>", line 1, in ?
      AttributeError: 'MP3FileInfo' instance has no attribute '__parse'
      复制代码
    • 严格的苏和哦,私有方法在类外是有效的,只是不容易处理。在Python中没有什么是真正私有的。你可以通过 _MP3FileInfo__parse 名字来使用MP3FileInfo 类的 __parse 方法。知道了这个方法很有趣,然后要保证决不在真正的代码中使用它。私有方法由于某种原因而私有,但是象其它很多在Python中的东西一样,它们的私有化基本上是习惯问题,而不是强迫的。
  9. 处理异常:python具有异常处理,通过使用try...except块。
    • 标准python库中每个模块都有使用他们,python自己是在不同的情况下引发他们
      • 使用不存在的字典关键字引发:keyerror异常
      • 搜索列表中不存在的值引发:valueerror异常
      • 调用不存在的方法:attributeerror异常
      • 引用不存在的变量:nameerror异常
      • 未强制转换就混合数据类型:typeerror异常
    • 这些情况下我们都是简单的使用了python ide:一个错误发生,异常被打印出来,并且就是这些,传出一些调试信息然后程序终止,但是如果发生在真正的Python程序运行的时候,真个程序将会终止。
    • 一个异常不一定引起程序的完全崩溃,异常引发时候,可以处理掉,有的时候异常是因为代码中的bug但是,许多时候,一个异常是可以预计的,如果知道一行代码会引发异常(打开一个不存在的文件,连到一个可能不能处理的数据库),应该使用一个try except块来处理异常。
    • 打开一个不存在的文件
      复制代码
      >>>fsock = open("/notthere", "r")                 #文件不存在 引发IOError异常
      Traceback (innermost last):
        File "<interactive input>", line 1, in ?
      IOError: [Errno 2] No such file or directory: '/notthere'
      >>>try:
      ...        fsock = open("/nottehrer")
      ...    except IOError:                                        #捕捉异常接着执行我们自己的代码块,这个代码快打出自己定义的代码
      ...        print "The file does not exist, exiting gracefully"
      ...    print "This line will always print"         #异常被处理,在块后继续执行最后一样总是打印出来,无论异常是否发生。
      The file does not exist, exiting gracefully
      This line will always print
      复制代码
    • 异常看上去不友好,不能捕捉异常,整个程序将崩溃,但是考虑一下别的方法,宁愿找回对于不存在文件的不可用的文件对象么?不管怎么样都要检查它的有效性,而且如果你忘记了,程序将会在下面某个地方给出奇怪的错误,这样就的追到源程序,使用异常,一旦发生错误,就可以找到问题源头。
    • 异常处理还有许多其他的用处,在标准python库中的一个普通的方法就是试着导入一个模块,然后检查他是否能使用,导入一个并不存在的模块将引发一个importerror异常,使用这种方法来定义许多级别的功能,依靠在运行的时候哪个模块是有效的,支持多种平台(平台特定的代码被分离到不同的模块中)
    • 支持特定平台功能,这个代码来源于getpass模块,一个从用户得到口令的封装模块。得到口令在UNIX,WINDOWS,和MACOS平台上的实现是不同的,但是这个代码封装了所有的不同
      复制代码
      # Bind the name getpass to the appropriate function
      try:
          import termios, TERMIOS                     #termios 是个特定的模块,提供了对于输入终端的底层控制。如果模块无效,导入失败,引发异常。
      except ImportError:
          try:
              import msvcrt                           #试试msvcrt,十一个windows特的模块,可以提供在Microsoft Visual C++运行服务中的许多有用的函数的一个API。如果导入失败,Python会引发我们捕捉的 ImportError 异常。
          except ImportError:
              try:
                  from EasyDialogs import AskPassword  #如果前两个不能工作,我们试着从 EasyDialogs 导入一个函数,它是一个MacOS特定模块,提供了各种各样类型的弹出对话框。再一次,如果导入失败,Python会引发一个我们捕捉的 ImportError 异常。
              except ImportError:
                  getpass = default_getpass           #这些平台特定的模块没有一个有效(有可能,因为Python已经移植到了许多不同的平台上了),所以我们需要回头使用一个缺省口令输入函数(这个函数定义在 getpass 模块中的别的地方)。注意,我们在这里做的:我们将函数 default_getpass 赋给变量 getpass。如果你读了官方 getpass 文档,它会告诉你 getpass 模块定义了一个 getpass 函数。它是这样做的:通过绑定 getpass 到正确的函数来适应你的平台。然后当你调用 getpass 函数时,你实际上调用了平台特定的函数,是这段代码已经为你设置好的。你不需要知道或关心你的代码正运行在何种平台上;只要调用 getpass,则它总能正确处理。
              else:                                   
                  getpass = AskPassword
          else:
              getpass = win_getpass
      else:                                                              #一个 try...except 块可以有一条 else 子句,就象 if 语句。如果在 try 块中没有异常引发,然后 else 子句被执行。在本例中,那就意味着如果 from EasyDialogs import AskPassword 导入可工作,所以我们应该绑定 getpass 到 AskPassword 函数。其它每个 try...except 块有着相似的 else 子句,当我们找到一个 import 可用时,来绑定 getpass 到适合的函数 。
          getpass = unix_getpass
      复制代码
  10. 文件对象
    • python 有一个内置函数,open,用来打开磁盘上的文件,open返回一个文件对象,拥有一些方法和属性,可以得到打开文件的信息,和对打开的文件进行操作。
    • 打开文件
      复制代码
      >>> f = open("/home/shishang/Interface.first", "rb") #open接受三个参数,文件名,参数,缓冲区参数。只有一个参数是必须的就是文件名,这里以二进制方式打开文件读取
      >>> f                                                    #open函数返回一个对象,一个文件对象有几个有用的属性
      <open file '/home/shishang/Interface.first', mode 'rb' at 0xb76eff40>
      >>> f.mode                                        #告诉文件打开模式
      'rb'
      >>> f.name                                        #文件的名字
      '/home/shishang/Interface.first'
                                                                                      
      复制代码
    • 读取文件
      复制代码
      >>> f
      <open file '/music/_singles/kairo.mp3', mode 'rb' at 010E3988>
      >>> f.tell()                             #打开的文件当前位置,文件没做任何事,当前位置是0,最开始处              
      0
      >>> f.seek(-128, 2)             #0表示移动到一个绝对位置,1移到一个绝对位置,2表示对于文件尾的一个相对位置,我们使用2并且告诉文件对象从文件尾移动到128字节的位置,
      >>> f.tell()                             #确认移到了当前位置,
      7542909
      >>> tagData = f.read(128) # read 方法从打开文件中读取指定个数的字节,并且返回含有读取数据的字符串。可选参数指定了读取的最大字节数。如果没有指定参数,read 将读到文件末尾。(我们本可以在这里简单地说一下 read(),因为我们确切地知道在文件的何处,事实上,我们读的是最后128个字节。)读出的数据赋给变量 tagData,并且当前的位置根据所读的字节数作了修改。
      >>> tagData
      'TAGKAIRO****THE BEST GOA         ***DJ MARY-JANE***            Rave Mix                      2000http://mp3.com/DJMARYJANE     \037'
      >>> f.tell()              #确认了当前位置已经移动了。
      7543037
      复制代码
    • 关闭文件
      复制代码
      >>> f
      <open file '/home/shishang/Interface.first', mode 'rb' at 0xb76eff40>
      >>> f.closed                   #文件对象的cloesd属性表示对象是否打开或关闭了文件,这里依然打开着,处理完毕,需要关闭文件
      False
      >>> f.close()                    #关闭文件,调用文件对象的close方法,这样就释放了在文件上的锁
      >>> f
      <closed file '/home/shishang/Interface.first', mode 'rb' at 0xb76eff40>
      >>> f.closed
      True
      >>> f.seek(0)                  #一旦文件关闭,可操作打开文件的方法没有一个可以使用,引发异常,文件被关闭,但是不意味着对象文件停止存在,变量f继续存在,直到超出作用域或者手工删除。
      Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
      ValueError: I/O operation on closed file
      >>> f.tell()
      Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
      ValueError: I/O operation on closed file
      >>> f.read()
      Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
      ValueError: I/O operation on closed file
      >>> f.close()       #不会发生异常,静静的失败
      复制代码
    • MP3FileInfo中的文件对象
      复制代码
              try:                        #打开和读取文件有风险,并且可能引发异常,所有这些都用一个try...except块封装                     
                  fsock = open(filename, "rb", 0)  #open 函数可能引发异常,
                  try:                           
                      fsock.seek(-128, 2)                 #seek方法可能引发异常
                      tagdata = fsock.read(128)     #可能引发异常
                  finally:                                            #新的finally模块,一旦文件被成功打开,我们应该绝对保证把它关闭,甚至由于seek或者read方法引发一个异常。try...finally可以用来:finally中的代码将总被执行,甚至某些东西在try块中引发一个异常也会执行。可以这样考虑,不管在路上发生什么,代码都会被即将灭亡的执行。
                      fsock.close()              
                  .
                  .
                  .
              except IOError:                                #最后处理IO异常 ,它可能是由调用 open,seek,或 read 引发的 IOError 异常。这里,我们其实不关心,因为将要做的事就是静静地忽略它然后继续。(记住,pass 是一条不做任何事的Python语句。)这样完全合法,“处理”一个异常可以明确表示不做任何事。它仍然被认为处理过了,并且处理将正常继续,从 try...except 块的下一行代码。                  
                  pass   
      复制代码
  11. for循环:在其他方面python太出色,通常不需要他们。其它大多数语言没有象Python一样的强大的列表数据类型,所以你需要亲自做很多事情,指定开始,结束和步长,来定义一定范围的整数或字符或其它可重复的实体。但是在Python中,for 循环简单地在一个列表上循环,与映射列表的工作方式相同。
    复制代码
    >>> li = ['a', 'b', 'e']   
    >>> for s in li:                  #   for循环类似于映射列表
    ...     print s
    ... 
    a
    b
    e
    >>> print "\n".join(li)       #当你想要的只是一个join或是列表映射的时候,代替了for循环
    a
    b
    e
    复制代码
  12. 一次赋多个值:最酷的程序简写之一就是可以使用序列一次赋多个值
    • 一次赋值多个
      复制代码
      >>> v = ('a', 'b', 'e')   #v是一个三个元素的序列,(x,y,z)是一个三个变量的序列,依次赋值。
      >>> (x, y, z) = v
      >>> x
      'a'
      >>> y
      'b'
      >>> z
      'e'
      复制代码

      构建可重用的模块的时候,经常给一个名字赋以一系列的值,C或者C++里边,经常用enum并且手工的列出每个常量和它对应的值,值是连续的时候显示的特别繁琐。python里边可以使用内置的range函数来迅速的给多个变量赋予连续值。

    • 赋值连续
      复制代码
      >>> range(7)                                            #内置的range是一个证书列表。接受一个上限 返回一个从0开始计数但是不包括上限的一个列表。                        
      [0, 1, 2, 3, 4, 5, 6]
      >>> (MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY) = range(7)   #MONDAY,TUESDAY,WEDNESDAY,THURSDAY,FRIDAY,SATURDAY,和 SUNDAY 是我们定义的变量。(这个例子来自 calendar 模块,一个有趣的用来打印日历的小模块,就象UNIX程序 cal。calendar 模块定义了星期每天的整数常量。)
      >>> MONDAY                                                                      
      0
      >>> TUESDAY
      1
      >>> SUNDAY
      6
      复制代码

      使用这种技术,可以构建返回多值的函数,只要通过返回包含所有值的一个序列,调用者可以把返回值看成一个序列,或者将值赋给单个变量。

    • 函数中返回多值
      复制代码
      >>> import os       #         os.path操作文件路径的函数,split函数可以分割整个路径,返回一个包含路径和文件名的序列。
      >>> os.path.split("/music/ap/mahadeva.mp3")                        
      ('/music/ap', 'mahadeva.mp3')
      >>> (filepath, filename) = os.path.split("/music/ap/mahadeva.mp3") 
      >>> filepath                                                       
      '/music/ap'
      >>> filename                                                       
      'mahadeva.mp3'
      >>> (shortname, extension) = os.path.splitext(filename) # 分割了一个文件名     
      >>> shortname
      'mahadeva'
      >>> extension
      '.mp3'
      复制代码

      只要有可能,你最好使用在 os 和 os.path 中的函数来处理对文件,目录,和路径的操作。这些模块是对于平台特定模块的封装产物,所以象 os.path.split 之类的函数可以工作在UNIX,Windows,MacOS,和其它任何支持Python的平台上。利用多变量赋值甚至有更多可以做的,可以用在遍历一个序列列表的时候,意味着可将它用于for循环和列表映射当中,可能认为序列列表不是每天都要用到的东西,但是实际上字典的Items方法就返回一个序列列表,每个序列的形式为(key, value)。所以多变量赋值允许你通过简单的方法来遍历字典的元素。

    • 遍历字典
      复制代码
      >>> for k, v in os.environ.items()  #os.environ 是一个在你的系统中所定义的环境变量的字典。在Windows下,它们是你的用户和系统变量,可以容易地从MS-DOS中得到。在UNIX下,它们是输出(export)到你的shell启动脚本的变量。在MacOS下,没有环境变量的概念,所以这个字典为空。
      ...     print "%s=%s" % (k, v)
      USERPROFILE=C:\Documents and Settings\mpilgrim
      OS=Windows_NT
      PROCESSOR_IDENTIFIER=x86 Family 6 Model 6 Stepping 10, GenuineIntel
      COMPUTERNAME=MPILGRIM
      USERNAME=mpilgrim                  #os.environ.items() 返回一个序列列表:[(key1,value1),(key2,value2),...]。for 循环遍历这个列表。第一轮,它将 key1 赋给 k 而 value1 赋给 v,所以 k = USERPROFILE 而 v = C:\Documents and Settings\mpilgrim。第二轮,k 得到第二个关键字,OS,而 v 得到相对的值,Windows_NT。
      
      […snip…]
      复制代码

      使用多变量赋值不是绝对必需的。它是一种方便的简写,且可以让你的代码更加可读,特别是当处理字典时(通过 items方法)。但是如果发现你迫使自已的代码通过种种周折(为了以正确的形式得到数据),只是为了让你可以一次给两个变量赋值,可能就不值得那么做了。

    • 通过多变量赋值进行字典映射
      复制代码
      >>> print "\n".join(["%s=%s" % (k, v) for k, v in os.environ.items()]) 
      USERPROFILE=C:\Documents and Settings\mpilgrim
      OS=Windows_NT
      PROCESSOR_IDENTIFIER=x86 Family 6 Model 6 Stepping 10, GenuineIntel
      COMPUTERNAME=MPILGRIM
      USERNAME=mpilgrim
      
      […snip…]          #多变量赋值也可以用于列表映射,使用这种简捷的方法来将字典映射成列表。本例中,我们通过将列表连接成一个字符串使得这种用法更深一步。注意它的输出与前例中的 for 循环一样。这就是为什么你在Python中看到那么少的 for 循环的原因;许多复杂的事情可以不用它们完成。你可以讨论是否这种方法更易读,但是它相当快,因为只有一条输出语句而不是许多。
      复制代码
    • 在MP3FileInfo中的多变量for循环
      复制代码
      tagDataMap = {"title"   : (  3,  33, stripnulls),
                        "artist"  : ( 33,  63, stripnulls),
                        "album"   : ( 63,  93, stripnulls),
                        "year"    : ( 93,  97, stripnulls),
                        "comment" : ( 97, 126, stripnulls),
                        "genre"   : (127, 128, ord)} 
          .
          .
          .
                  if tagdata[:3] == "TAG":
                      for tag, (start, end, parseFunc) in self.tagDataMap.items(): 
                          self[tag] = parseFunc(tagdata[start:end])                
      复制代码

      tagDataMap 是一个类属性,它定义了我们正在一个MP3文件中所查找的标记。标记被保存在定长的字段中;一旦我们读出文件的最后128个字节,字节3到32是歌曲的题目,33-62是歌手名字,63-92是专集名字,等等。注意tagDataMap 是一个序列字典,每个序列包含两个整数和一个函数引用。#这个看上去有些复杂,其实不是。for 变量结构与通过 items 返回的列表元素的结构相匹配。记住,items 返回一个形式为(keyvalue)的序列列表。列表的第一个元素是("title", (3, 33, <function stripnulls>)),所以循环的第一轮,tag 得到 "title"start 得到 3end 得到 33,而 parseFunc 得到函数 stripnulls。#现在我们已经提取出了单个MP3标记的所有参数,保存标记数据很容易。我们从 start 到end 划分 tagdata 以得到这个标记的实际数据,调用 parseFunc来对数据进行后续处理,然后将它作为关键字的值赋给伪字典 self 的 tag 关键字。在遍历了 tagDataMap 中所有元素之后, self 拥有所有标记的值,并且你知道那看上去象什么

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值