关于人工智能:Python函数基础回顾

4次阅读

共计 8163 个字符,预计需要花费 21 分钟才能阅读完成。

文章和代码等曾经归档至【Github 仓库:https://github.com/timerring/dive-into-AI】或者公众号【AIShareLab】回复 python 数据分析 也可获取。

函数

函数应用 def 关键字申明,用 return 关键字返回值:

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

函数能够有一些地位参数(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 的一个性能是:函数能够返回多个值。

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: # 两头通过 clean_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']

这种多函数模式使你能在很高的档次上轻松批改字符串的转换形式。此时的 clean_strings 也更具可复用性!

还能够将函数用作其余函数的参数,比方内置的 map 函数,它用于在一组数据上利用一个函数:

In [176]: for x in map(remove_punctuation, states):
   .....:     print(x)
Alabama 
Georgia
Georgia
georgia
FlOrIda
south   carolina
West virginia

匿名(lambda)函数

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

def short_function(x):
    return x * 2
# lambda 后的 x 就是指自变量
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)

尽管你能够间接编写[x *2for x in ints],然而这里咱们能够十分轻松地传入一个自定义运算给 apply_to_list 函数。

再来看另外一个例子。假如有一组字符串,你想要依据各字符串不同字母的数量对其进行排序:

In [177]: strings = ['foo', 'card', 'bar', 'aaaa', 'abab']

这里,咱们能够传入一个 lambda 函数到列表的 sort 办法:

In [178]: strings.sort(key=lambda x: len(set(list(x))))

In [179]: strings
Out[179]: ['aaaa', 'foo', 'abab', 'bar', 'card']

笔记: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']

表 3 - 2 中列出了一些我常常用到的 itertools 函数。倡议参阅 Python 官网文档,进一步学习。

谬误和异样解决

优雅地解决 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'

你可能留神到 float 抛出的异样不仅是 ValueError:

In [202]: float((1, 2))
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-202-842079ebb635> in <module>()
----> 1 float((1, 2))
TypeError: float() argument must be a string or a number, not 'tuple'

你可能只想解决 ValueError,TypeError 谬误(输出不是字符串或数值)可能是正当的 bug。能够写一个异样类型:

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

而后有:

In [204]: attempt_float((1, 2))
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-204-9bdfd730cead> in <module>()
----> 1 attempt_float((1, 2))
<ipython-input-203-3e06b8379b6b> in attempt_float(x)
      1 def attempt_float(x):
      2     try:
----> 3         return float(x)
      4     except ValueError:
      5         return x
TypeError: float() argument must be a string or a number, not 'tuple'

能够用元组蕴含多个异样:

def attempt_float(x):
    try:
        return float(x)
    except (TypeError, ValueError):
        return x

某些状况下,你可能不想克制异样,你想无论 try 局部的代码是否胜利,都执行一段代码。能够应用 finally:

f = open(path, 'w')

try:
    write_to_file(f)
finally:
    f.close()

这里,文件解决 f 总会被敞开。类似的,你能够用 else 让只在 try 局部胜利的状况下,才执行代码:

f = open(path, 'w')

try:
    write_to_file(f)
except:
    print('Failed')
else:
    print('Succeeded')
finally:
    f.close()

IPython 的异样

如果是在 %run 一个脚本或一条语句时抛出异样,IPython 默认会打印残缺的 调用栈(traceback)在栈的每个点都会有几行上下文

In [10]: %run examples/ipython_bug.py
---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
/home/wesm/code/pydata-book/examples/ipython_bug.py in <module>()
     13     throws_an_exception()
     14
---> 15 calling_things()

/home/wesm/code/pydata-book/examples/ipython_bug.py in calling_things()
     11 def calling_things():
     12     works_fine()
---> 13     throws_an_exception()
     14
     15 calling_things()

/home/wesm/code/pydata-book/examples/ipython_bug.py in throws_an_exception()
      7     a = 5
      8     b = 6
----> 9     assert(a + b == 10)
     10
     11 def calling_things():

AssertionError:

本身就带有文本是绝对于 Python 规范解释器的极大长处。你能够用魔术命令%xmode,从 Plain(与 Python 规范解释器雷同)到 Verbose(带有函数的参数值)管制文本显示的数量。前面能够看到,产生谬误之后,(用 %debug 或 %pdb magics)能够进入 stack 进行预先调试。

正文完
 0