Python中使用内层函数的好处,python内层,未经许可,禁止转载!英文


本文由 编橙之家 - 艾凌风 翻译,笑虎 校稿。未经许可,禁止转载!
英文出处:realpython.com。欢迎加入翻译组。

内层函数——它们的优点是什么?

让我们看一下写内层函数的三个常见原因。

记住:在Python中,函数是“一等公民”,这意味着它们和其他对象平起平坐(例如:整型,字符串,列表,模块等)。你可以动态地创建和销毁它们,把它们传递给其他函数,把它们作为值返回,等等。

本文使用Python 3.4.1版本

1.封装

你使用内层函数来保护它们不受函数外部变化的影响,也就是说把它们从全局作用域藏起来。

这里有一个简单的例子来强调这一概念:

Python
def outer(num1):
    def inner_increment(num1):  # hidden from outer code
        return num1 + 1
    num2 = inner_increment(num1)
    print(num1, num2)

inner_increment(10)
# outer(10)

尝试调用inner_increment()函数

Shell
Traceback (most recent call last):
  File "inner.py", line 7, in <module>
    inner_increment()
NameError: name 'inner_increment' is not defined

现在,把inner_increment的调用注释掉,再把对外部函数调用的注释取消,outer(10),把10作为参数传入:

Python
10 11

请记住这仅仅是一个例子,尽管代码得到了期望的结果,但是使用一个前置的下划线把inner_increment()函数变为“私有”函数:_inner_increment()更好。

下面这个递归的例子与使用嵌套函数相比稍微好一些:

Python
def factorial(number):

    # error handling
    if not isinstance(number, int):
        raise TypeError("Sorry. 'number' must be an integer.")
    if not number &gt;= 0:
        raise ValueError("Sorry. 'number' must be zero or positive.")

    def inner_factorial(number):
        if number &lt;= 1:
            return 1
        return number*inner_factorial(number-1)
    return inner_factorial(number)

# call the outer function
print(factorial(4))

同样测试一下这段代码。使用这种设计模式的一个主要优势在于:在外部函数中对全部参数执行了检查,你可以在内部函数中跳过全部的检查过程。

关于这个递归更加详细的解释请看Problem Solving with Algorithms and Data Structures

2.贯彻DRY(Don’t Repeat Yourself )原则

也许你有一个巨型函数,在很多很多地方执行一大段的代码。比如,你可能写了一个函数用来处理文件,并且你希望它既可以接受一个打开文件对象或是一个文件名:

Python
def process(file_name):
    def do_stuff(file_process):
        for line in file_process:
            print(line)
    if isinstance(file_name, str):
        with open(file_name, 'r') as f:
            do_stuff(f)
    else:
        do_stuff(file_name)

同样,通常会把do_stuff()作为一个顶层私有函数,但是如果你想要把它作为一个内层函数隐藏起来,你也可以这样做。

来个实际的例子怎么样?

让我们假设你想知道纽约市全部WiFi热点的数量。而且确实有提供这些信息的原始数据: 数据。

访问这个网站并下载CSV.

Python
def process(file_name):

    def do_stuff(file_process):
        wifi_locations = {}

        for line in file_process:
            values = line.split(',')
            # Build the dict, and increment values
            wifi_locations[values[1]] = wifi_locations.get(values[1], 0) + 1

        max_key = 0
        for name, key in wifi_locations.items():
            all_locations = sum(wifi_locations.values())
            if key &gt; max_key:
                max_key = key
                business = name
        print('There are {0} WiFi hot spots in NYC and {1} has the most with {2}.'.format(
            all_locations, business, max_key))

    if isinstance(file_name, str):
        with open(file_name, 'r') as f:
            do_stuff(f)
    else:
        do_stuff(file_name)

process("NAME_OF_THE.csv")

执行函数:

Python
There are 1251 WiFi hot spots in NYC and Starbucks has the most with 212.

3.闭包和工厂函数

现在我们要谈到使用内层函数最重要的原因了。目前为止我们看到的全部内层函数的例子都是普通函数,只不过它们凑巧嵌套在另外的函数之中了。换句话说,我们本可以用另外的方式定义这些函数(正如我们谈到的那样),并没有什么特别的理由去嵌套它们。

但是当我们谈到闭包的时候,情况就不一样了:你必须要利用嵌套函数。

什么是闭包?

闭包无非是使内层函数在调用时记住它当前环境的状态。初学者经常认为闭包就是内层函数,而且实际上它是由内层函数导致的。闭包在栈上“封闭”了局部变量,使其在栈创建执行结束后仍然存在。

一个例子

Python
def generate_power(number):
    """
    Examples of use:

    >>raise_two = generate_power(2)
    >>raise_three = generate_power(3)
    >>print(raise_two(7))
    128
    >>print(raise_three(5))
    243
    """

    # define the inner function ...
    def nth_power(power):
        return number ** power
    # ... which is returned by the factory function

    return nth_power

此处发生了什么?

  1. generate_power()函数是一个工厂方法——简单说就是意味着它每次调用的时候会创建一个新函数,并返回这个新创建的函数,因此raise_tworaise_three 都是新创建的函数。
  2. 这个新的内层函数做了什么呢?它接受一个单独的参数,power,并且返回number**power
  3. 内层函数从哪得到number的值呢?这就该闭包登场了:nth_power()从外层函数得到power 的值,让我们逐步查看这一过程:
    • 调用外层函数:generate_power(2)
    • 生成一个nth_power()函数,它接受一个单一的参数power
    • 保存nth_power()的状态快照,其中number=2
    • 把这个快照传入generate_power()函数
    • 返回nth_power()函数

换句话说,闭包函数“初始化”了nth_power()函数并将其返回。现在无论你何时调用这个新返回的函数,它都会去查看其私有的快照,也就是包含power=2的那一个。

现实世界

来一个实际例子如何?

Python
def has_permission(page):
    def inner(username):
        if username == 'Admin':
            return "'{0}' does have access to {1}.".format(username, page)
        else:
            return "'{0}' does NOT have access to {1}.".format(username, page)
    return inner

current_user = has_permission('Admin Area')
print(current_user('Admin'))

random_user = has_permission('Admin Area')
print(current_user('Not Admin'))

这是一个简化过的函数,用来检查某个特定用户是否有权限访问特定的页面。你可以很容易的修改它从会话中抓取用户信息,查看他们是否具有访问当前路径的正确凭证。我们可以查询数据库来查看许可情况,然后根据是否具有正确的凭证来返回返回正确的视图,而不是检查user是否等于‘Admin’。

结论

闭包和工厂函数是内层函数最常见也最强大的用途。在大多数情况下,当你看到一个被装饰的函数,装饰器就是一个工程函数,它接受一个函数作为参数,然后返回一个新的函数,其闭包中包含了原函数。停一停。深呼吸。喝口咖啡。再读一遍。

换句话说,装饰器是一个语法糖,用来实现generate_power() 例子中提到的功能

我把这个例子留给你:

Python
def generate_power(exponent):
    def decorator(f):
        def inner(*args):
            result = f(*args)
            return exponent**result
        return inner
    return decorator

@generate_power(2)
def raise_two(n):
    return n

print(raise_two(7))

@generate_power(3)
def raise_three(n):
    return n

print(raise_two(5))

如果你的代码编辑器运行,请并排对比着查看generate_power(exponent)generate_power(number)这两个函数来搞清楚我们所讨论的概念。(比如说Sublime Text有分栏浏览模式(译注:shift+alt+2))

如何你还没有编写这两个函数,打开编辑器并开始编写。对于新手程序员来讲,编码是一项亲手实践的活动,就像学习骑车,你必须去做才行——而且要独立完成。所以请回到手上的任务来。当你输入完成代码后,你可以清楚的看到它们产生了相同的结果,但是这之间是有不同的。对于那些从来没用过装饰器的人来说,注意到这些不同是理解它们的第一步——如果你要走这条路的话。

打赏支持我翻译更多好文章,谢谢!

打赏译者

打赏支持我翻译更多好文章,谢谢!

任选一种支付方式

相关内容

    暂无相关文章

评论关闭