美文网首页Python Pandas
12-07 第03章2 Python的数据结构、函数和文件

12-07 第03章2 Python的数据结构、函数和文件

作者: 渔家傲_俞 | 来源:发表于2018-12-07 21:28 被阅读0次

    3.2 函数

    函数是Python中最主要也是最重要的代码组织和复用手段。作为最重要的原则,如果你要重复使用相同或非常类似的代码,就需要写一个函数。通过给函数起一个名字,还可以提高代码的可读性。
    函数使用def关键字声明,用return关键字返回值:

    def my_function(x, y, z=1.5):
        if z > 1:
            return z * (x + y)
        else:
            return z / (x + y)
    

    同时拥有多条return语句也是可以的。如果到达函数末尾时没有遇到任何一条return语句,则返回None。
    函数可以有一些位置参数(positional)和一些关键字参数(keyword)。关键字参数通常用于指定默认值或可选参数。在上面的函数中,x和y是位置参数,而z则是关键字参数。也就是说,该函数可以下面这两种方式进行调用:

    my_function(5, 6, z=0.7)
    my_function(3.14, 7, 3.5)
    my_function(10, 20)
    

    函数参数的主要限制在于:关键字参数必须位于位置参数(如果有的话)之后。你可以任何顺序指定关键字参数。也就是说,你不用死记硬背函数参数的顺序,只要记得它们的名字就可以了。

    笔记:也可以用关键字传递位置参数。前面的例子,也可以写为:

    my_function(x=5, y=6, z=7) 
    my_function(y=6, x=5, z=7)
    

    命名空间、作用域,和局部函数

    函数可以访问两种不同作用域中的变量:全局(global)和局部(local)。Python有一种更科学的用于描述变量作用域的名称,即命名空间(namespace)。任何在函数中赋值的变量默认都是被分配到局部命名空间(local namespace)中的。局部命名空间是在函数被调用时创建的,函数参数会立即填入该命名空间。在函数执行完毕之后,局部命名空间就会被销毁(会有一些例外的情况,具体请参见后面介绍闭包的那一节)。看看下面这个函数:

    def func():
        a = []
        for i in range(5):
            a.append(i)
    

    调用func()之后,首先会创建出空列表a,然后添加5个元素,最后a会在该函数退出的时候被销毁。假如我们像下面这样定义a:

    a = []
    def func():
        for i in range(5):
            a.append(i)
    

    虽然可以在函数中对全局变量进行赋值操作,但是那些变量必须用global关键字声明成全局的才行:

    In [168]: a = None
    
    In [169]: def bind_a_variable():
       .....:     global a
       .....:     a = []
       .....: bind_a_variable()
       .....:
    
    In [170]: print(a)
    []
    

    注意:我常常建议人们不要频繁使用global关键字。因为全局变量一般是用于存放系统的某些状态的。如果你发现自己用了很多,那可能就说明得要来点儿面向对象编程了(即使用类)。

    返回多个值

    在我第一次用Python编程时(之前已经习惯了Java和C++),最喜欢的一个功能是:函数可以返回多个值。下面是一个简单的例子:

    def f():
        a = 5
        b = 6
        c = 7
        return a, b, c
    
    a, b, c = f()
    

    在数据分析和其他科学计算应用中,你会发现自己常常这么干。该函数其实只返回了一个对象,也就是一个元组,最后该元组会被拆包到各个结果变量中。在上面的例子中,我们还可以这样写:

    return_value = f()
    

    这里的return_value将会是一个含有3个返回值的三元元组。此外,还有一种非常具有吸引力的多值返回方式——返回字典:

    def f():
        a = 5
        b = 6
        c = 7
        return {'a' : a, 'b' : b, 'c' : c}
    

    函数也是对象

    由于Python函数都是对象,因此,在其他语言中较难表达的一些设计思想在Python中就要简单很多了。假设我们有下面这样一个字符串数组,希望对其进行一些数据清理工作并执行一堆转换:

    In [171]: states = ['   Alabama ', 'Georgia!', 'Georgia', 'georgia', 'FlOrIda',
       .....:           'south   carolina##', 'West virginia?']
    

    不管是谁,只要处理过由用户提交的调查数据,就能明白这种乱七八糟的数据是怎么一回事。为了得到一组能用于分析工作的格式统一的字符串,需要做很多事情:去除空白符、删除各种标点符号、正确的大写格式等。做法之一是使用内建的字符串方法和正则表达式re模块:

    import re
    
    def clean_strings(strings):
        result = []
        for value in strings:
            value = value.strip()
            value = re.sub('[!#?]', '', value)
            value = value.title()
            result.append(value)
        return result
    

    结果如下所示:

    In [173]: clean_strings(states)
    Out[173]: 
    ['Alabama',
     'Georgia',
     'Georgia',
     'Georgia',
     'Florida',
     'South   Carolina',
     'West Virginia']
    

    其实还有另外一种不错的办法:将需要在一组给定字符串上执行的所有运算做成一个列表:

    def remove_punctuation(value):
        return re.sub('[!#?]', '', value)
    
    clean_ops = [str.strip, remove_punctuation, str.title]
    
    def clean_strings(strings, ops):
        result = []
        for value in strings:
            for function in ops:
                value = function(value)
            result.append(value)
        return result
    

    然后我们就有了:

    In [175]: clean_strings(states, clean_ops)
    Out[175]: 
    ['Alabama',
     'Georgia',
     'Georgia',
     'Georgia',
     'Florida',
     'South   Carolina',
     'West Virginia']
    

    匿名(lambda)函数

    Python支持一种被称为匿名的、或lambda函数。它仅由单条语句组成,该语句的结果就是返回值。它是通过lambda关键字定义的,这个关键字没有别的含义,仅仅是说“我们正在声明的是一个匿名函数”。

    def short_function(x):
        return x * 2
    
    equiv_anon = lambda x: x * 2
    

    本书其余部分一般将其称为lambda函数。它们在数据分析工作中非常方便,因为你会发现很多数据转换函数都以函数作为参数的。直接传入lambda函数比编写完整函数声明要少输入很多字(也更清晰),甚至比将lambda函数赋值给一个变量还要少输入很多字。看看下面这个简单得有些傻的例子:

    def apply_to_list(some_list, f):
        return [f(x) for x in some_list]
    
    ints = [4, 0, 1, 5, 6]
    apply_to_list(ints, lambda x: x * 2)
    

    笔记:lambda函数之所以会被称为匿名函数,与def声明的函数不同,原因之一就是这种函数对象本身是没有提供名称name属性。

    柯里化:部分参数应用

    柯里化(currying)是一个有趣的计算机科学术语,它指的是通过“部分参数应用”(partial argument application)从现有函数派生出新函数的技术。例如,假设我们有一个执行两数相加的简单函数:

    def add_numbers(x, y):
        return x + y
    

    通过这个函数,我们可以派生出一个新的只有一个参数的函数——add_five,它用于对其参数加5:

    add_five = lambda y: add_numbers(5, y)
    

    add_numbers的第二个参数称为“柯里化的”(curried)。这里没什么特别花哨的东西,因为我们其实就只是定义了一个可以调用现有函数的新函数而已。内置的functools模块可以用partial函数将此过程简化:

    from functools import partial #这种情况下,一个函数有一个或多个参数预先就能用上,以便函数能用更少的参数进行调用。
    add_five = partial(add_numbers, 5)
    

    生成器

    能以一种一致的方式对序列进行迭代(比如列表中的对象或文件中的行)是Python的一个重要特点。这是通过一种叫做迭代器协议(iterator protocol,它是一种使对象可迭代的通用方式)的方式实现的,一个原生的使对象可迭代的方法。比如说,对字典进行迭代可以得到其所有的键:

    In [180]: some_dict = {'a': 1, 'b': 2, 'c': 3}
    
    In [181]: for key in some_dict:
       .....:     print(key)
    a
    b
    c
    

    当你编写for key in some_dict时,Python解释器首先会尝试从some_dict创建一个迭代器:

    In [182]: dict_iterator = iter(some_dict)
    
    In [183]: dict_iterator
    Out[183]: <dict_keyiterator at 0x7fbbd5a9f908>
    

    迭代器是一种特殊对象,它可以在诸如for循环之类的上下文中向Python解释器输送对象。大部分能接受列表之类的对象的方法也都可以接受任何可迭代对象。比如min、max、sum等内置方法以及list、tuple等类型构造器:

    In [184]: list(dict_iterator)
    Out[184]: ['a', 'b', 'c']
    

    生成器(generator)是构造新的可迭代对象的一种简单方式。一般的函数执行之后只会返回单个值,而生成器则是以延迟的方式返回一个值序列,即每返回一个值之后暂停,直到下一个值被请求时再继续。要创建一个生成器,只需将函数中的return替换为yeild即可:

    def squares(n=10):
        print('Generating squares from 1 to {0}'.format(n ** 2))
        for i in range(1, n + 1):
            yield i ** 2
    

    调用该生成器时,没有任何代码会被立即执行:

    In [186]: gen = squares()
    
    In [187]: gen
    Out[187]: <generator object squares at 0x7fbbd5ab4570>
    

    直到你从该生成器中请求元素时,它才会开始执行其代码:

    In [188]: for x in gen:
       .....:     print(x, end=' ')
    Generating squares from 1 to 100
    1 4 9 16 25 36 49 64 81 100
    

    生成器表达式

    另一种更简洁的构造生成器的方法是使用生成器表达式(generator expression)。这是一种类似于列表、字典、集合推导式的生成器。其创建方式为,把列表推导式两端的方括号改成圆括号:

    In [189]: gen = (x ** 2 for x in range(100))
    
    In [190]: gen
    Out[190]: <generator object <genexpr> at 0x7fbbd5ab29e8>
    

    它跟下面这个冗长得多的生成器是完全等价的:

    def _make_gen():
        for x in range(100):
            yield x ** 2
    gen = _make_gen()
    

    生成器表达式也可以取代列表推导式,作为函数参数:

    In [191]: sum(x ** 2 for x in range(100))
    Out[191]: 328350
    
    In [192]: dict((i, i **2) for i in range(5))
    Out[192]: {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
    

    itertools模块

    标准库itertools模块中有一组用于许多常见数据算法的生成器。例如,groupby可以接受任何序列和一个函数。它根据函数的返回值对序列中的连续元素进行分组。下面是一个例子:

    In [193]: import itertools
    
    In [194]: first_letter = lambda x: x[0]
    
    In [195]: names = ['Alan', 'Adam', 'Wes', 'Will', 'Albert', 'Steven']
    
    In [196]: for letter, names in itertools.groupby(names, first_letter):
       .....:     print(letter, list(names)) # names is a generator
    A ['Alan', 'Adam']
    W ['Wes', 'Will']
    A ['Albert']
    S ['Steven']
    

    错误和异常处理

    优雅地处理Python的错误和异常是构建健壮程序的重要部分。在数据分析中,许多函数函数只用于部分输入。例如,Python的float函数可以将字符串转换成浮点数,但输入有误时,有ValueError错误:

    In [197]: float('1.2345')
    Out[197]: 1.2345
    
    In [198]: float('something')
    ---------------------------------------------------------------------------
    ValueError                                Traceback (most recent call last)
    <ipython-input-198-439904410854> in <module>()
    ----> 1 float('something')
    ValueError: could not convert string to float: 'something'
    

    假如想优雅地处理float的错误,让它返回输入值。我们可以写一个函数,在try/except中调用float:

    def attempt_float(x):
        try:
            return float(x)
        except:
            return x
    

    当float(x)抛出异常时,才会执行except的部分:

    In [200]: attempt_float('1.2345')
    Out[200]: 1.2345
    
    In [201]: attempt_float('something')
    Out[201]: 'something'
    

    IPython的异常

    3.3 文件和操作系统

    本书的代码示例大多使用诸如pandas.read_csv之类的高级工具将磁盘上的数据文件读入Python数据结构。但我们还是需要了解一些有关Python文件处理方面的基础知识。好在它本来就很简单,这也是Python在文本和文件处理方面的如此流行的原因之一。
    为了打开一个文件以便读写,可以使用内置的open函数以及一个相对或绝对的文件路径:

    In [207]: path = 'examples/segismundo.txt'
    
    In [208]: f = open(path)
    

    默认情况下,文件是以只读模式('r')打开的。然后,我们就可以像处理列表那样来处理这个文件句柄f了,比如对行进行迭代:

    for line in f:
        pass
    

    如果使用open创建文件对象,一定要用close关闭它。关闭文件可以返回操作系统资源:

    In [211]: f.close()
    

    用with语句可以可以更容易地清理打开的文件:

    In [212]: with open(path) as f:
       .....:     lines = [x.rstrip() for x in f]
    

    这样可以在退出代码块时,自动关闭文件。

    3.4 结论

    我们已经学过了Python的基础、环境和语法,接下来学习NumPy和Python的面向数组计算。

    相关文章

      网友评论

        本文标题:12-07 第03章2 Python的数据结构、函数和文件

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