美文网首页Python学习日志程序员
【第十一天】Python对象的深入理解

【第十一天】Python对象的深入理解

作者: 人生苦短_我用Python | 来源:发表于2018-03-23 21:46 被阅读7次

    4.4

    1.循环对象

    py中的许多语法结构都是由对象实现的,循环就可以通过对象实现
    循环对象并不是在py诞生之初就存在的,但它的发展极为迅速
    特别是在py3时代,循环对象正是成为循环的标准形式

    那么,什么是循环对象呢?所谓的循环对象包含有一个next()方法
    这个方法的目的是生成循环的下一个结果,在生成过循环的所有结果之后
    该方法将抛出StopIteration异常

    当一个像for这样的循环语法调用循环对象时,它会在每次循环的时候
    调用next()方法,直到StopIteration循环出现
    循环接受到这个异常,就会知道循环已经结束,将停止调用next()

    我们用内置函数iter()把一个列表转变为循环对象
    这个循环对象将拥有next()方法,我们多次调用next()方法
    将不断返回列表的值,直到出现异常:

    example_iter = iter([1,2])
    example_iter.__next__()    #显示1
    example_iter.__next__()    #显示2
    example_iter.__next__()    #出现StopIteration异常
    

    我们上面重复调用next()的过程,就相当于手动进行了循环
    我们可以把循环对象包裹在for中自动进行循环:

    for item in iter([1,2])
        print(item)
    

    在这里,for结构自动调用了next()方法,将该方法的返回值赋予给item
    循环知道出现StopIteration的时候结束,当然,我们可以省去内置函数iter的转换
    这是因为,for结构会自动执行这一转换

    相对于序列,循环对象的好处在于:不用在循环还没开始的时候
    就生成要使用的元素,所有要使用的元素可以在循环过程中逐渐生成
    这样,不仅节省了空间,提高了效率,还会使编程更加灵活

    我们可以借助生成器(generator)来自定义循环对象
    生成器的编写方法和函数定义类似,只是在return的地方改为yield
    生成器中可以有多个yield,当生成器遇到一个yield时
    会暂停运行生成器,返回yield后面的值,当再次调用生成器的时候
    会从刚才暂停的地方继续运行,直到下一个yield
    生成器自身又构成一个循环对象,每次循环使用一个yield返回的值

    def gen():
        a = 100
        yield a
        a = a*8
        yield a
        yield 1000
    

    该生成器共有三个yield,如果用作循环对象时,会进行三次循环

    for i in gen():
        print(i)
    
    100
    800
    1000
    

    再考虑下面一个生成器:

    def gen():
        i = 0
        while i < 10000000:
            i = i + 1
            yield i
    

    这个生成器能产生10000000个元素,如果先创建序列保存这10000000个元素
    再循环遍历,那么这个序列将占用大量空间,出于同样原因
    py中的内置函数range()返回的是一个循环对象,而不是一个序列

    for i in range(10000000):
        print('hello world')  #打印10000000遍hello world
    

    2.函数对象

    前面说过,在py中,函数也是一种对象,中、实际上,任何一个有call()
    特殊方法的对象都被当作是函数,例如:

    class SampleMore(object):
        def __call__(self,a):
            return a + 5
    add_five = SampleMore()        #生成函数对象
    print(add_five(2))             #像一个函数一样调用函数对象,结果为7
    

    add_five为SampleMore类的一个对象,当被调用时,add_five执行加5的操作

    3.模块对象

    前面说过,py中的模块对应一个.py文件,模块也是对象
    比如,我们直接引入标准库的模块time:

    import time
    print(dir(time))
    
    ['_STRUCT_TM_ITEMS', '__doc__', '__loader__', '__name__',
     '__package__', '__spec__','altzone', 'asctime', 'clock', 'ctime',
     'daylight', 'get_clock_info', 'gmtime', 'localtime', 'mktime',
     'monotonic', 'perf_counter', 'process_time', 'sleep','strftime', 
    'strptime', 'struct_time', 'time', 'timezone', 'tzname', 'tzset']
    

    可以看到,time有很多属性可以调用,例如sleep()方法
    我们之前用import语句引入其他文件中定义的函数
    实际上就是引入模块对象的属性,比如:

    from time import sleep
    sleep(10)
    print('wake up')
    

    模块time的sleep()会中止程序,调用时的参数说明给了中止的时间

    我们还可以用简单暴力的方法,一次性引入模块的所有属性:

    from time import *
    sleep(10)
    

    既然知道了sleep()是time的一个方法
    那么我们当然可以利用对象属性的方式来调用它

    import time
    time.sleep(10)
    

    我们在调用方法时附带上了对象名,这样做的好处是可以拓展程序的命名空间
    避免同名冲突,例如,如果两个模块中都有sleep()方法,
    那么我们可以通过不一样的模块名来区分开,在my_time.py中写入函数:

    def sleep(self):
        print('I am sleeping')
    

    在main.py中引入内置模块和自定义模块my_time:

    import time
    import my_time
    
    time.sleep()
    my_time.sleep()
    

    上面的两次对sleep()方法的调用中,我们分别通过对象名区分出了不同的sleep()

    在引入模块时,我们还可以给模块换个名字:

    import time as t
    t.sleep(10)
    

    在引入名字比较长的模块时,这个换名字的方法更加方便快捷

    可以将功能相似的模块放在同一个文件夹中,构成一个模块包
    比如放在this_dir中:

    import this_dir.module
    

    引入this_dir文件夹中的module模块
    该文件夹中必须包含一个init.py的文件,提醒py,该文件夹为一个模块包
    init.py可以是一个空文件
    每个模块对象都有一个name属性,用来记录模块的名字,例如:

    import time
    print(time.__name__)
    

    当一个.py文件作为主程序运行时,比如python foo.py
    这个文件也会有一个对应的模块对象
    但这个模块对象的name属性会是“main
    因此,我们在很多.py文件中可以看到下面的语句:

    if __name__ == "__main__":
        ...
    

    它的意思是说,如果这个文件作为一个主程序运行,那么将执行下面的操作
    有的时候,一个.py文件中同时有类和对象的定义,以及对它们的调用
    当这些.py文件作为库引入时,我们可能并不希望执行这些调用
    通过把调用语句放到上面的if中,就可以在调用时不执行这些语句

    4.异常对象

    前面我们提到过,可以在程序中加入异常处理的try结构,捕捉程序中出现的异常
    实际上,我们捕捉到的也是一个对象,比如

    try:
        m = 1/0
    except ZeroDivisionError as e:
        print("Catch NameError in the sub-function")
    
    print(type(e))     #类型为'exceptions.ZeroDivisionError'
    print(dre(e))      #异常对象的属性
    print(e.message)   #异常信息integer division or modulo by zero
    

    利用except...as...的语法,我们在except结果中用e来代表捕捉到的类型对象
    关键字except直接跟随ZeroDivisionError实际上是异常对象的类
    正因为如此,我们在举出异常时会创建一个异常对象:

    raise ZeroDivisionError()
    

    在py中,循环,函数,模块,异常都是某种对象
    当然,我们可以完全按照面向过程中的方式来调用这些语法
    而不必关注它们底层的对象模型,但是出于学习的目的
    这些语法结构的对象模型能加深我们对py的理解

    相关文章

      网友评论

        本文标题:【第十一天】Python对象的深入理解

        本文链接:https://www.haomeiwen.com/subject/nnkfcftx.html