Python核心技术与实战
景霄
Facebook资深工程师
立即订阅
13891 人已学习
课程目录
已完结 46 讲
0/4登录后,你可以任选4讲全文学习。
开篇词 (1讲)
开篇词 | 从工程的角度深入理解Python
免费
基础篇 (14讲)
01 | 如何逐步突破,成为Python高手?
02 | Jupyter Notebook为什么是现代Python的必学技术?
03 | 列表和元组,到底用哪一个?
04 | 字典、集合,你真的了解吗?
05 | 深入浅出字符串
06 | Python “黑箱”:输入与输出
07 | 修炼基本功:条件与循环
08 | 异常处理:如何提高程序的稳定性?
09 | 不可或缺的自定义函数
10 | 简约不简单的匿名函数
11 | 面向对象(上):从生活中的类比说起
12 | 面向对象(下):如何实现一个搜索引擎?
13 | 搭建积木:Python 模块化
14 | 答疑(一):列表和元组的内部实现是怎样的?
进阶篇 (11讲)
15 | Python对象的比较、拷贝
16 | 值传递,引用传递or其他,Python里参数是如何传递的?
17 | 强大的装饰器
18 | metaclass,是潘多拉魔盒还是阿拉丁神灯?
19 | 深入理解迭代器和生成器
20 | 揭秘 Python 协程
21 | Python并发编程之Futures
22 | 并发编程之Asyncio
23 | 你真的懂Python GIL(全局解释器锁)吗?
24 | 带你解析 Python 垃圾回收机制
25 | 答疑(二):GIL与多线程是什么关系呢?
规范篇 (7讲)
26 | 活都来不及干了,还有空注意代码风格?!
27 | 学会合理分解代码,提高代码可读性
28 | 如何合理利用assert?
29 | 巧用上下文管理器和With语句精简代码
30 | 真的有必要写单元测试吗?
31 | pdb & cProfile:调试和性能分析的法宝
32 | 答疑(三):如何选择合适的异常处理方式?
量化交易实战篇 (8讲)
33 | 带你初探量化世界
免费
34 | RESTful & Socket: 搭建交易执行层核心
35 | RESTful & Socket: 行情数据对接和抓取
36 | Pandas & Numpy: 策略与回测系统
免费
37 | Kafka & ZMQ:自动化交易流水线
38 | MySQL:日志和数据存储系统
39 | Django:搭建监控平台
40 | 总结:Python中的数据结构与算法全景
技术见闻与分享 (4讲)
41 | 硅谷一线互联网公司的工作体验
42 | 细数技术研发的注意事项
加餐 | 带你上手SWIG:一份清晰好用的SWIG编程实践指南
43 | Q&A:聊一聊职业发展和选择
结束语 (1讲)
结束语 | 技术之外的几点成长建议
Python核心技术与实战
登录|注册

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

景霄 2019-05-29
你好,我是景霄。
实际工作生活中,我曾见到不少初学者编写的 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),也可以不返回。
总结一下,大概是下面的这种形式:
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《Python核心技术与实战》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(55)

  • 不瘦到140不改名
    其实函数也可以看做成是一个变量,函数名就是变量名,函数体就是值。函数虽然在不被调用的情况下不会执行,但是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())
    2019-05-29
    4
    51
  • pyhhou
    闭包必须使用嵌套函数,一看到闭包我首先想到的是 JavaScript 里面的回调函数。闭包这里看似仅仅返回了一个嵌套函数,但是需要注意的是,它其实连同嵌套函数的外部环境变量也一同保存返回回来了(例子中的 exponent 变量),这个环境变量是在调用其外部函数时设定的,这样一来,对于一些参数性,不常改变的设定变量,我们可以通过这个形式来设定,这样返回的闭包函数仅需要关注那些核心输入变量,节省了效率,这样做也大大减少了全局变量的使用,增加代码可读性的同时,也会让代码变得更加的安全
    2019-05-29
    22
  • farFlight
    谢谢老师,这节课的内容非常有意思!
    有两个问题:
    1. python自己判断类型的多态和子类继承的多态Polymorphism应该不是一个意思吧?
    2. 函数内部不能直接用+=等修改全局变量,但是对于list全局变量,可以使用append, extend之类修改,这是为什么呢?
    2019-05-29
    1
    11
  • 逍遥思
    一开始看完,对闭包的概念有了,但比较糙,不知道闭包究竟指的是哪个变量。
    这篇文章对大家理解闭包有一定帮助:https://zhuanlan.zhihu.com/p/26934085
    2019-05-30
    10
  • Brigand
    我建议,以后文中不要放代码,放代码截图,有需要代码去github,这样移动端体验会好点。
    2019-05-29
    2
    7
  • 进击的菜鸟运维
    老师,您说的“函数的调用和声明哪个在前哪个在后是无所谓的。”请问这句话怎么理解呢?
    如下是会报异常NameError: name 'f' is not defined:
    f()
    def f():
        print("test")

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

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

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

    2019-05-29
    4
  • William
    快排封装,增加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)
    ```

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

    2019-05-29
    3
  • Aspirin
    关于闭包,我想到一个案例,就是卷积神经网络模型的实现。我们知道,在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
    3
  • 路伴友行
    我有个项目需要将很多不规则的列表展平
    但没有找到推荐的方式,就自己写了个
    希望大佬们多多指出缺点,谢谢

    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))
    2019-05-29
    3
  • michel
    关于函数申明及调用关系,以及变量范围,做了几个测试,终于理解的比较透彻了。

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

    更详细的测试过程及分析,记录在博客中:https://www.jianshu.com/p/3c7f13cc6f8d
    2019-05-30
    2
  • KaitoShy
    a = {'shanghai':50000, 'hangzhou':300000}

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

    b = 'dfff'
    def func_a():
        b += 'ddd';
    func_a()
    print(b)
    第一个改变了他的值,第二个确没有。是因为字典和列表是直接操作内存的?而变量的操作是重新生产一块内存?
    2019-05-29
    1
    2
  • 路伴友行
    顺便我想多问一句,在Python里是不推荐使用递归的,是因为Python没有对递归做优化,那使用 yield from 来代替递归会不会好些呢?
    其实我上一个例子就是一个尝试,我之前只尝试过打印栈信息,只看到有2层,就是不清楚有些其他什么弊端。

    作者回复: 你说的没错

    2019-05-29
    1
  • third
    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. 全局变量在任何地方都可以访问,但是访问之前你必须得定义赋值他啊

    2019-05-29
    1
  • SCAR
    老师函数嵌套的作用二的例子,如果是在大量的调用函数时,可能还是分开检查和递归比较好,因为嵌套内函数是函数的一个local变量,在大量调用函数的时候,local变量是不断产生和销毁的,这会非常费时间,它可能会反噬掉一次类型检查节省下来的时间。看下面我贴出的计算1百万次100阶乘的时间,所以还是要根据具体情况来定,当然大部分时候函数不会这么大量调用。

    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)

    def factorial_1(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_1(input):
        if input <= 1:
            return 1
        return input*inner_factorial_1(input-1)

    %%time
    for i in range(1000000):
        factorial(100)
    CPU times: user 21.6 s, sys: 11.6 ms, total: 21.6 s
    Wall time: 21.7 s


    %%time
    for i in range(1000000):
        factorial_1(100)
        inner_factorial_1(100)
    CPU times: user 19.7 s, sys: 12 ms, total: 19.7 s
    Wall time: 19.7 s

    作者回复: 这个case by case,需要注意的是有些时候一些validation check的cost很高,比如机器学习里面我们会对训练数据(>= 1000 million的样本)做一些统计等等

    2019-05-29
    1
  • 潇洒哥er
    看到评论区经常有同学在问手机用什么软件写代码,推荐一下:
    苹果系统的:Pythonista 3
    安卓系统的:PyDroid3
    两个都有用,但感觉苹果的pythonista 特别的好用,打一半提示一半,半智能,自动格式化。
    2019-05-29
    1
    1
  • Paul Shan
    在面向对象出来之前,函数都是主要的组织代码的工具与。函数的嵌套赋予函数内组织代码的方便,可以隐藏内部实现,可以处理紧密关联的功能,避免多重检查的作用。

    函数内部对全局变量是只读的,要写全局变量需要额外的声明。嵌套域也类似。

    闭包起到了的分步组装函数的作用。个人以为文中的平方函数不适合闭包,定义两个函数即可,一个是通用指数函数,一个是求平方函数,让求平方的函数调用通用指数函数。

    2019-11-14
  • 梁文涛
    没想到连闭包都讲,真的是干货满满,32个赞

    作者回复: 谢谢支持

    2019-09-30
  • 自由民
    总结:函数参数可以接受任意类型,但要注意可能出错。函数参数可以设置默认值。函数可以嵌套,好处是安全,一些每次需要执行的操作只用执行一次,比较高效。在函数内部修改外部变量需要进行global或nonlocal声明,否则修改只在函数内部有效。闭包是嵌套函数的外层函数返回函数而不是值,返回的函数可以在后期执行。使用闭包的好处是使程序简洁高效。
    思考题:在有一些共同的步骤要执行且只需执行一次是使用,共同部分放在外层。至于闭包,以前当然听过,从来没具体了解过。使用场合没想到有啥,看看其它同学的留言?
    课程的练习代码: https://github.com/zwdnet/PythonPractice
    2019-09-24
  • 夜雨声烦
    使用了闭包就可以将某个函数中的变量与外部链接起来,跟封装有些像,这样可以将数据与多个方法联系在一起。
    2019-09-19
收起评论
55
返回
顶部