防止断更 请务必加首发微信:1716143665
关闭
讲堂
算法训练营
前端训练营
客户端下载
兑换中心
企业版
渠道合作
推荐作者

09 | 不可或缺的自定义函数

2019-05-29 景霄(加微信:642945106 发送“赠送”领取赠送精品课程 发数字“2”获取众筹列表。)
Python核心技术与实战
进入课程

讲述:冯永吉

时长10:36大小9.73M

你好,我是景霄。

实际工作生活中,我曾见到不少初学者编写的 Python 程序,他们长达几百行的代码中,却没有一个函数,通通按顺序堆到一块儿,不仅让人读起来费时费力,往往也是错误连连。

一个规范的值得借鉴的 Python 程序,除非代码量很少(比如 10 行、20 行以下),基本都应该由多个函数组成,这样的代码才更加模块化、规范化。

函数是 Python 程序中不可或缺的一部分。事实上,在前面的学习中,我们已经用到了很多 Python 的内置函数,比如 sorted() 表示对一个集合序列排序,len() 表示返回一个集合序列的长度大小等等。这节课,我们主要来学习 Python 的自定义函数。

函数基础

那么,到底什么是函数,如何在 Python 程序中定义函数呢?

说白了,函数就是为了实现某一功能的代码段,只要写好以后,就可以重复利用。我们先来看下面一个简单的例子:

def my_func(message):
print('Got a message: {}'.format(message))
# 调用函数 my_func()
my_func('Hello World')
# 输出
Got a message: Hello World
复制代码

其中:

  • def 是函数的声明;

  • my_func 是函数的名称;

  • 括号里面的 message 则是函数的参数;

  • 而 print 那行则是函数的主体部分,可以执行相应的语句;

  • 在函数最后,你可以返回调用结果(return 或 yield),也可以不返回。

总结一下,大概是下面的这种形式:

def name(param1, param2, ..., paramN):
statements
return/yield value # optional
复制代码

和其他需要编译的语言(比如 C 语言)不一样的是,def 是可执行语句,这意味着函数直到被调用前,都是不存在的。当程序调用函数时,def 语句才会创建一个新的函数对象,并赋予其名字。

我们一起来看几个例子,加深你对函数的印象:

def my_sum(a, b):
return a + b
result = my_sum(3, 5)
print(result)
# 输出
8
复制代码

这里,我们定义了 my_sum() 这个函数,它有两个参数 a 和 b,作用是相加;随后,调用 my_sum() 函数,分别把 3 和 5 赋于 a 和 b;最后,返回其相加的值,赋于变量 result,并输出得到 8。

再来看一个例子:

def find_largest_element(l):
if not isinstance(l, list):
print('input is not type of list')
return
if len(l) == 0:
print('empty input')
return
largest_element = l[0]
for item in l:
if item > largest_element:
largest_element = item
print('largest element is: {}'.format(largest_element))
find_largest_element([8, 1,-3, 2, 0])
# 输出
largest element is: 8
复制代码

这个例子中,我们定义了函数 find_largest_element,作用是遍历输入的列表,找出最大的值并打印。因此,当我们调用它,并传递列表 [8, 1, -3, 2, 0] 作为参数时,程序就会输出 largest element is: 8

需要注意,主程序调用函数时,必须保证这个函数此前已经定义过,不然就会报错,比如:

my_func('hello world')
def my_func(message):
print('Got a message: {}'.format(message))
# 输出
NameError: name 'my_func' is not defined
复制代码

但是,如果我们在函数内部调用其他函数,函数间哪个声明在前、哪个在后就无所谓,因为 def 是可执行语句,函数在调用之前都不存在,我们只需保证调用时,所需的函数都已经声明定义:

def my_func(message):
my_sub_func(message) # 调用 my_sub_func() 在其声明之前不影响程序执行
def my_sub_func(message):
print('Got a message: {}'.format(message))
my_func('hello world')
# 输出
Got a message: hello world
复制代码

另外,Python 函数的参数可以设定默认值,比如下面这样的写法:

def func(param = 0):
...
复制代码

这样,在调用函数 func() 时,如果参数 param 没有传入,则参数默认为 0;而如果传入了参数 param,其就会覆盖默认值。

前面说过,Python 和其他语言相比的一大特点是,Python 是 dynamically typed 的,可以接受任何数据类型(整型,浮点,字符串等等)。对函数参数来说,这一点同样适用。比如还是刚刚的 my_sum 函数,我们也可以把列表作为参数来传递,表示将两个列表相连接:

print(my_sum([1, 2], [3, 4]))
# 输出
[1, 2, 3, 4]
复制代码

同样,也可以把字符串作为参数传递,表示字符串的合并拼接:

print(my_sum('hello ', 'world'))
# 输出
hello world
复制代码

当然,如果两个参数的数据类型不同,比如一个是列表、一个是字符串,两者无法相加,那就会报错:

print(my_sum([1, 2], 'hello'))
TypeError: can only concatenate list (not "str") to list
复制代码

我们可以看到,Python 不用考虑输入的数据类型,而是将其交给具体的代码去判断执行,同样的一个函数(比如这边的相加函数 my_sum()),可以同时应用在整型、列表、字符串等等的操作中。

在编程语言中,我们把这种行为称为多态。这也是 Python 和其他语言,比如 Java、C 等很大的一个不同点。当然,Python 这种方便的特性,在实际使用中也会带来诸多问题。因此,必要时请你在开头加上数据的类型检查。

Python 函数的另一大特性,是 Python 支持函数的嵌套。所谓的函数嵌套,就是指函数里面又有函数,比如:

def f1():
print('hello')
def f2():
print('world')
f2()
f1()
# 输出
hello
world
复制代码

这里函数 f1() 的内部,又定义了函数 f2()。在调用函数 f1() 时,会先打印字符串'hello',然后 f1() 内部再调用 f2(),打印字符串'world'。你也许会问,为什么需要函数嵌套?这样做有什么好处呢?

其实,函数的嵌套,主要有下面两个方面的作用。

第一,函数的嵌套能够保证内部函数的隐私。内部函数只能被外部函数所调用和访问,不会暴露在全局作用域,因此,如果你的函数内部有一些隐私数据(比如数据库的用户、密码等),不想暴露在外,那你就可以使用函数的的嵌套,将其封装在内部函数中,只通过外部函数来访问。比如:

def connect_DB():
def get_DB_configuration():
...
return host, username, password
conn = connector.connect(get_DB_configuration())
return conn
复制代码

这里的函数 get_DB_configuration,便是内部函数,它无法在 connect_DB() 函数以外被单独调用。也就是说,下面这样的外部直接调用是错误的:

get_DB_configuration()
# 输出
NameError: name 'get_DB_configuration' is not defined
复制代码

我们只能通过调用外部函数 connect_DB() 来访问它,这样一来,程序的安全性便有了很大的提高。

第二,合理的使用函数嵌套,能够提高程序的运行效率。我们来看下面这个例子:

def factorial(input):
# validation check
if not isinstance(input, int):
raise Exception('input must be an integer.')
if input < 0:
raise Exception('input must be greater or equal to 0' )
...
def inner_factorial(input):
if input <= 1:
return 1
return input * inner_factorial(input-1)
return inner_factorial(input)
print(factorial(5))
复制代码

这里,我们使用递归的方式计算一个数的阶乘。因为在计算之前,需要检查输入是否合法,所以我写成了函数嵌套的形式,这样一来,输入是否合法就只用检查一次。而如果我们不使用函数嵌套,那么每调用一次递归便会检查一次,这是没有必要的,也会降低程序的运行效率。

实际工作中,如果你遇到相似的情况,输入检查不是很快,还会耗费一定的资源,那么运用函数的嵌套就十分必要了。

函数变量作用域

Python 函数中变量的作用域和其他语言类似。如果变量是在函数内部定义的,就称为局部变量,只在函数内部有效。一旦函数执行完毕,局部变量就会被回收,无法访问,比如下面的例子:

def read_text_from_file(file_path):
with open(file_path) as file:
...
复制代码

我们在函数内部定义了 file 这个变量,这个变量只在 read_text_from_file 这个函数里有效,在函数外部则无法访问。

相对应的,全局变量则是定义在整个文件层次上的,比如下面这段代码:

MIN_VALUE = 1
MAX_VALUE = 10
def validation_check(value):
if value < MIN_VALUE or value > MAX_VALUE:
raise Exception('validation check fails')
复制代码

这里的 MIN_VALUE 和 MAX_VALUE 就是全局变量,可以在文件内的任何地方被访问,当然在函数内部也是可以的。不过,我们不能在函数内部随意改变全局变量的值。比如,下面的写法就是错误的:

MIN_VALUE = 1
MAX_VALUE = 10
def validation_check(value):
...
MIN_VALUE += 1
...
validation_check(5)
复制代码

如果运行这段代码,程序便会报错:

UnboundLocalError: local variable 'MIN_VALUE' referenced before assignment
复制代码

这是因为,Python 的解释器会默认函数内部的变量为局部变量,但是又发现局部变量 MIN_VALUE 并没有声明,因此就无法执行相关操作。所以,如果我们一定要在函数内部改变全局变量的值,就必须加上 global 这个声明:

MIN_VALUE = 1
MAX_VALUE = 10
def validation_check(value):
global MIN_VALUE
...
MIN_VALUE += 1
...
validation_check(5)
复制代码

这里的 global 关键字,并不表示重新创建了一个全局变量 MIN_VALUE,而是告诉 Python 解释器,函数内部的变量 MIN_VALUE,就是之前定义的全局变量,并不是新的全局变量,也不是局部变量。这样,程序就可以在函数内部访问全局变量,并修改它的值了。

另外,如果遇到函数内部局部变量和全局变量同名的情况,那么在函数内部,局部变量会覆盖全局变量,比如下面这种:

MIN_VALUE = 1
MAX_VALUE = 10
def validation_check(value):
MIN_VALUE = 3
...
复制代码

在函数 validation_check() 内部,我们定义了和全局变量同名的局部变量 MIN_VALUE,那么,MIN_VALUE 在函数内部的值,就应该是 3 而不是 1 了。

类似的,对于嵌套函数来说,内部函数可以访问外部函数定义的变量,但是无法修改,若要修改,必须加上 nonlocal 这个关键字:

def outer():
x = "local"
def inner():
nonlocal x # nonlocal 关键字表示这里的 x 就是外部函数 outer 定义的变量 x
x = 'nonlocal'
print("inner:", x)
inner()
print("outer:", x)
outer()
# 输出
inner: nonlocal
outer: nonlocal
复制代码

如果不加上 nonlocal 这个关键字,而内部函数的变量又和外部函数变量同名,那么同样的,内部函数变量会覆盖外部函数的变量。

def outer():
x = "local"
def inner():
x = 'nonlocal' # 这里的 x 是 inner 这个函数的局部变量
print("inner:", x)
inner()
print("outer:", x)
outer()
# 输出
inner: nonlocal
outer: local
复制代码

闭包

这节课的第三个重点,我想再来介绍一下闭包(closure)。闭包其实和刚刚讲的嵌套函数类似,不同的是,这里外部函数返回的是一个函数,而不是一个具体的值。返回的函数通常赋于一个变量,这个变量可以在后面被继续执行调用。

举个例子你就更容易理解了。比如,我们想计算一个数的 n 次幂,用闭包可以写成下面的代码:

def nth_power(exponent):
def exponent_of(base):
return base ** exponent
return exponent_of # 返回值是 exponent_of 函数
square = nth_power(2) # 计算一个数的平方
cube = nth_power(3) # 计算一个数的立方
square
# 输出
<function __main__.nth_power.<locals>.exponent(base)>
cube
# 输出
<function __main__.nth_power.<locals>.exponent(base)>
print(square(2)) # 计算 2 的平方
print(cube(2)) # 计算 2 的立方
# 输出
4 # 2^2
8 # 2^3
复制代码

这里外部函数 nth_power() 返回值,是函数 exponent_of(),而不是一个具体的数值。需要注意的是,在执行完square = nth_power(2)cube = nth_power(3)后,外部函数 nth_power() 的参数 exponent,仍然会被内部函数 exponent_of() 记住。这样,之后我们调用 square(2) 或者 cube(2) 时,程序就能顺利地输出结果,而不会报错说参数 exponent 没有定义了。

看到这里,你也许会思考,为什么要闭包呢?上面的程序,我也可以写成下面的形式啊!

def nth_power_rewrite(base, exponent):
return base ** exponent
复制代码

确实可以,不过,要知道,使用闭包的一个原因,是让程序变得更简洁易读。设想一下,比如你需要计算很多个数的平方,那么你觉得写成下面哪一种形式更好呢?

# 不适用闭包
res1 = nth_power_rewrite(base1, 2)
res2 = nth_power_rewrite(base2, 2)
res3 = nth_power_rewrite(base3, 2)
...
# 使用闭包
square = nth_power(2)
res1 = square(base1)
res2 = square(base2)
res3 = square(base3)
...
复制代码

显然是第二种,是不是?首先直观来看,第二种形式,让你每次调用函数都可以少输入一个参数,表达更为简洁。

其次,和上面讲到的嵌套函数优点类似,函数开头需要做一些额外工作,而你又需要多次调用这个函数时,将那些额外工作的代码放在外部函数,就可以减少多次调用导致的不必要的开销,提高程序的运行效率。

另外还有一点,我们后面会讲到,闭包常常和装饰器(decorator)一起使用。

总结

这节课,我们一起学习了 Python 函数的概念及其应用,有这么几点你需要注意:

  1. Python 中函数的参数可以接受任意的数据类型,使用起来需要注意,必要时请在函数开头加入数据类型的检查;

  2. 和其他语言不同,Python 中函数的参数可以设定默认值;

  3. 嵌套函数的使用,能保证数据的隐私性,提高程序运行效率;

  4. 合理地使用闭包,则可以简化程序的复杂度,提高可读性。

思考题

最后给你留一道思考题。在实际的学习工作中,你遇到过哪些使用嵌套函数或者是闭包的例子呢?欢迎在下方留言,与我讨论,也欢迎你把这篇文章分享给你的同事、朋友。

© 加微信:642945106 发送“赠送”领取赠送精品课程 发数字“2”获取众筹列表。
上一篇
08 | 异常处理:如何提高程序的稳定性?
下一篇
10 | 简约不简单的匿名函数
 写留言

1716143665 拼课微信(45)

  • 不瘦到140...
    2019-05-29
    17
    其实函数也可以看做成是一个变量,函数名就是变量名,函数体就是值。函数虽然在不被调用的情况下不会执行,但是python解释器会做一些变量检测、或者类型检测,比如是不是有yield,如果有那么就会被标记为生成器,这个在编译成字节码的时候就已经确定了。而有些东西则是只有在解释执行的时候才会被发现。
    比如说:
    x = 1


    def foo():
        print(x)
        x = 10
    foo() # UnboundLocalError: local variable 'x' referenced before assignment
    为什么会有这种结果,因为python寻找变量的时候,会按照本地作用域、闭包、全局、内置这种顺序去查找,当看到x=1的时候,python解释器就知道函数体内部创建了变量x,这是在编译的时候就已经确定了,于是在print的时候也会从本地查找,但是print(x)语句在x=10的上面,这是在执行的时候才发现的,于是报了个错:提示局部变量x在赋值之前就已经被引用了。

    x = 1


    def foo():
        x += 1

    foo() # UnboundLocalError: local variable 'x' referenced before assignment
    这也是同样的道理,x += 1,相当于x = x+1,相当于将x的值和1进行相加然后再让x重新指向它,同样在编译的时候就知道函数内部创建了x这个变量,因此在执行x+1的时候,会从本地查找,但是发现本地此时还没有x,于是引发了同样的错误。
    因此如果想在函数体内部修改全局变量,对于immutable类型,一定要使用global关键字,表示外部的变量和函数内部的变量是同一个变量,如果是mutable类型,比如list、dict,支持本地修改的话,那么可以不用使用global关键字,因为它们支持本地修改

    关于python的传参,python和golang不一样,golang只有值传递,而python只有引用传递,无论是什么类型,python传的永远都是引用。
    x = 1
    def foo(x):
        x = 2
    foo(x)
    print(x) # 1
    传递x的时候,相当于传递了引用,函数的x通过外部的x找到了值为1的内存地址,相当于值为1的这片内存被贴了两个标签,当x=2的时候,那么会重新开辟一块内存,存储的值为2,然后让函数内部的x重新指向,但是外部的x该指向谁还是指向谁,所以外部的x是不会受影响的。但如果是列表,支持本地操作,外部和内部的变量指向同一个列表的话,那么内部变量进行append等本地操作是会影响外部的,因为它们指向同一片内存区域,并且是本地修改,而不是重新赋值


    思考题:
    from functools import wraps


    def login_required(func):
        @wraps(func)
        def inner(*args, **kwargs):
            user = input("请输入账号: ")
            passwd = input("请输入密码: ")
            if user == "bilibili" and passwd == "bilibili":
                return func(*args, **kwargs)
            return "页面去火星了"
        return inner


    @login_required
    def login():
        return "欢迎来到bilibili"


    print(login())
    展开
  • pyhhou
    2019-05-29
    15
    闭包必须使用嵌套函数,一看到闭包我首先想到的是 JavaScript 里面的回调函数。闭包这里看似仅仅返回了一个嵌套函数,但是需要注意的是,它其实连同嵌套函数的外部环境变量也一同保存返回回来了(例子中的 exponent 变量),这个环境变量是在调用其外部函数时设定的,这样一来,对于一些参数性,不常改变的设定变量,我们可以通过这个形式来设定,这样返回的闭包函数仅需要关注那些核心输入变量,节省了效率,这样做也大大减少了全局变量的使用,增加代码可读性的同时,也会让代码变得更加的安全
    展开
  • farFlight
    2019-05-29
    10
    谢谢老师,这节课的内容非常有意思!
    有两个问题:
    1. python自己判断类型的多态和子类继承的多态Polymorphism应该不是一个意思吧?
    2. 函数内部不能直接用+=等修改全局变量,但是对于list全局变量,可以使用append, extend之类修改,这是为什么呢?
    展开
  • Brigand
    2019-05-29
    3
    我建议,以后文中不要放代码,放代码截图,有需要代码去github,这样移动端体验会好点。
  • 进击的菜鸟...
    2019-05-29
    3
    老师,您说的“函数的调用和声明哪个在前哪个在后是无所谓的。”请问这句话怎么理解呢?
    如下是会报异常NameError: name 'f' is not defined:
    f()
    def f():
        print("test")
    展开

    作者回复: 文中已经更新了。可能之前表达的不准确,意思是主程序调用函数时,必须保证这个函数此前已经定义过,但是,如果我们在函数内部调用其他函数,函数间哪个声明在前、哪个在后就无所谓,因为def是可执行语句,函数调用前都不存在,我们只需保证调用时,所需的函数都已经声明定义

  • Vincent
    2019-05-29
    3
    关于嵌套函数:“我们只能通过调用外部函数 connect_DB() 来访问它,这样一来,程序的安全性便有了很大的提高。” 这个怎么就安全了呢?这个安全指的是什么安全呢?
    展开

    作者回复: 数据库的用户名密码等一些信息不会暴露在外部的API中

  • William
    2019-05-29
    2
    快排封装,增加index参数会用到嵌套。
    ```python
    def quickSort(arr):
        def partition(arr, left, right):
            pivot = arr[left]
            while left < right:
                while left < right and arr[right] > pivot:
                    right -= 1
                if left < right:
                    arr[left] = arr[right]
                while left < right and arr[left] < pivot:
                    left += 1
                if left < right:
                    arr[right] = arr[left]
            arr[left] = pivot
            return left
        def innerQuickSort(arr, left, right):
            stack = []
            stack.append(left)
            stack.append(right)
            while len(stack) > 0:
                right = stack.pop()
                left = stack.pop()
                pivotIndex = partition(arr, left, right)
                if pivotIndex + 1 < right:
                    stack.append(pivotIndex+1)
                    stack.append(right)
                if left + 1 < pivotIndex:
                    stack.append(left)
                    stack.append(pivotIndex - 1)
        innerQuickSort(arr, 0, len(arr)-1)

    arr = [394, 129, 11, 39, 28]
    quickSort(arr)
    print(arr)
    ```
    展开

    作者回复: 嗯嗯,学习很细心

  • Aspirin
    2019-05-29
    2
    关于闭包,我想到一个案例,就是卷积神经网络模型的实现。我们知道,在CNN模型推理时,所有卷积层或全连接层的权重weights都是已知的、确定的,也就是说我们实例化一个模型之后,所有layer的weights都是确定好的,只需要处理不同的输入就可以了。所以,我们可以写一个闭包函数,输入不同的权重,返回使用该权重进行卷积运算的函数即可。伪代码如下:
    不使用闭包:
    ```
    for img in imgs:
        x = conv2d(img, weights1)
        x = conv2d(x, weights2)
    ...
    ```
    使用闭包:
    ```
    conv_layer1 = conv_layer(weights1)
    conv_layer2 = conv_layer(weights2)
    for img in imgs:
        x = conv_layer1(img)
        x = conv_layer2(x)
    ...
    ```
    展开
  • 路伴友行
    2019-05-29
    2
    我有个项目需要将很多不规则的列表展平
    但没有找到推荐的方式,就自己写了个
    希望大佬们多多指出缺点,谢谢

    def getSmoothList(lst):
        def gen_expand(_list):
            for item in _list:
                if isinstance(item, (list, tuple)):
                    yield from gen_expand(item)
                else:
                    yield item
        return list(gen_expand(lst))
    展开
  • KaitoShy
    2019-05-29
    2
    a = {'shanghai':50000, 'hangzhou':300000}

    def func():
        a['beijing'] = 17500
        
    func()
    print(a)

    b = 'dfff'
    def func_a():
        b += 'ddd';
    func_a()
    print(b)
    第一个改变了他的值,第二个确没有。是因为字典和列表是直接操作内存的?而变量的操作是重新生产一块内存?
    展开
  • michel
    2019-05-30
    1
    关于函数申明及调用关系,以及变量范围,做了几个测试,终于理解的比较透彻了。

    def本身就是一个申明,如果不执行,不涉及对对象的引用,则不会报错,即使在函数内部引用了一个不存在的变量。关键在于执行的时候,被引用的变量或者函数是否被加载。

    更详细的测试过程及分析,记录在博客中:https://www.jianshu.com/p/3c7f13cc6f8d
    展开
  • 付剑津
    2019-05-30
    1
    一开始看完,对闭包的概念有了,但比较糙,不知道闭包究竟指的是哪个变量。
    这篇文章对大家理解闭包有一定帮助:https://zhuanlan.zhihu.com/p/26934085
  • 路伴友行
    2019-05-29
    1
    顺便我想多问一句,在Python里是不推荐使用递归的,是因为Python没有对递归做优化,那使用 yield from 来代替递归会不会好些呢?
    其实我上一个例子就是一个尝试,我之前只尝试过打印栈信息,只看到有2层,就是不清楚有些其他什么弊端。

    作者回复: 你说的没错

  • third
    2019-05-29
    1
    1.Python中...是啥意思?发现在代码中运行没有错误。也没有百度到

    2.#不是说全局变量可以在文件的任意地方都可以被访问吗?,我试了下,去掉x的赋值,就可以访问了。这是什么原因呢?
    #x=10
    def outer():
        print(x)
        x = "local"
        def inner():
            nonlocal x # nonlocal 关键字表示这里的 x 就是外部函数 outer 定义的变量 x
            x = 'nonlocal'
            print("inner:", x)
        inner()
        print("outer:", x)
    x=10
    outer()

    #报错Traceback (most recent call last):
    # File "D:/软件/Python/Lib/idlelib/新p/学习分析/写着玩.py", line 11, in <module>
    # outer()
    # File "D:/软件/Python/Lib/idlelib/新p/学习分析/写着玩.py", line 2, in outer
    # print(x)
    # UnboundLocalError: local variable 'x' referenced before assignment

    展开

    作者回复: 1. 我只是用‘...’表示省略
    2. 全局变量在任何地方都可以访问,但是访问之前你必须得定义赋值他啊

  • 潇洒哥er
    2019-05-29
    1
    看到评论区经常有同学在问手机用什么软件写代码,推荐一下:
    苹果系统的:Pythonista 3
    安卓系统的:PyDroid3
    两个都有用,但感觉苹果的pythonista 特别的好用,打一半提示一半,半智能,自动格式化。
    展开
  • 且听疯吟
    2019-06-06
    闭包是不是可以理解为C语言里面的一个函数,返回的就是一个函数指针?
    可以用一个指针接受函数指针。
  • 小侠龙旋风
    2019-06-06
    1.Python 中函数的参数可以接受任意的数据类型,使用起来需要注意,必要时请在函数开头加入数据类型的检查;
    2.和其他语言不同,Python 中函数的参数可以设定默认值;
    3.嵌套函数的使用,能保证数据的隐私性,提高程序运行效率;
    4.合理地使用闭包,则可以简化程序的复杂度,提高可读性。

    另外,在Python中函数是一等公民
    展开
  • 蚂蚁
    2019-06-05
    "其实函数也可以看做成是一个变量,函数名就是变量名,函数体就是值。函数虽然在不被调用的情况下不会执行,但是python解释器会做一些变量检测、或者类型检测,比如是不是有yield,如果有那么就会被标记为生成器,这个在编译成字节码的时候就已经确定了。而有些东西则是只有在解释执行的时候才会被发现。
    比如说:
    x = 1


    def foo():
        print(x)
        x = 10
    foo() # UnboundLocalError: local variable 'x' referenced before assignment
    为什么会有这种结果,因为python寻找变量的时候,会按照本地作用域、闭包、全局、内置这种顺序去查找,当看到x=1的时候,python解释器就知道函数体内部创建了变量x,这是在编译的时候就已经确定了,于是在print的时候也会从本地查找,但是print(x)语句在x=10的上面,这是在执行的时候才发现的,于是报了个错:提示局部变量x在赋值之前就已经被引用了。"
    这里为什么当看到x=1的时候 python解释器就知道函数体内部创建了变量x
    展开
  • while (1...
    2019-06-05
    “如果不加上 nonlocal 这个关键字,而内部函数的变量又和外部函数变量同名,那么同样的,内部函数变量会覆盖外部函数的变量。“ 这个是不覆盖吧
  • wanj
    2019-06-01
    默认值参数再有新的参数也必须是带有默认值的,另外*args **kargs也比较常用,希望能够补充讲解
收藏