廖雪峰 Python教程笔记 二-函数

本文深入探讨Python函数的定义、参数类型、高阶函数及函数式编程概念,包括map、reduce、filter、sort等函数应用,以及生成器、装饰器、lambda表达式的使用技巧。

##一、函数
要注意的是,python函数的特点, python中一切都是对象,函数也是对象。这个点要从传统的C,JAVA转过来,一个函数可以像一个对象一样被引用,被赋值,作为参数传递给另一个函数,做返回值,还可以在字典,列表等里面使用。同样的,定义的一个类也是这样。
###1、定义函数
1.1 基本定义
和循环一样,用:和缩进替代{},假设定义一个绝对值函数

def my_abs(x):
    if x >= 0:
        return x
    else:
        return -x

如果在一个语句块中想什么也不做,使用pass,eg:

if a > 0:
   pass

如果不加pass的话会报错

返回语句,由于Python无类型,所以不用声明返回值得类型。因此,更进一步的,返回值得个数也可以不加限定了,直接逗号分隔返回多个返回值,python会打包成tuple类型。

1.2 引用函数
你已经把my_abs()的函数定义保存为abstest.py文件了,那么,可以在该文件的当前目录下启动Python解释器,用from abstest import my_abs来导入my_abs()函数,注意abstest是文件名(不含.py扩展名);

1.3 参数类型检查
由于Python是无类型的,但是方法可能传入任何类型的参数进来,所以方法的定义中需要的地方必须手动检查参数类型,否则就会出错。

def my_abs(x):
    if not isinstance(x, (int, float)):
        raise TypeError('bad operand type')
    if x >= 0:
        return x
    else:
        return -x

用内置函数 isinstance(x, (int, float,…))检查

2 函数的参数

函数也可以赋值给一个变量,
Python的函数参数较为灵活和复杂,有基本参数,默认值参数,可变长参数, 关键字参数, 命名关键字参数。

2.1 基本参数 位置参数

注意一般参数也可以像命名参数一样指定参数名字,但不是必须,命名参数必须指定名字。

2.2 默认值参数

默认参数就是参数含有默认值,调用方法时可以对该参数传递值,也可不对改参数传递值。要求必须把必选参数放在参数序列前面,把默认参数放在参数序列的后面,这样便于编译器处理,也便于使用理解。
默认参数还可以在传参时指定传给谁,在前面加上参数名=xxx即可,表示将实参传递给哪个具体的形参,这样就可以不按定义的默认参数的顺序传递默认参数了。
eg:
print("xcxc", end ="")就是这种调用

使用默认参数可以达到增加函数扩展性和简洁性的效果,比如要对原来的函数增加一个参数,又不影响以前的调用代码,使用默认参数即可。

Python默认值参数的坑 默认参数中的默认值是可变的!!!
假如入定义方法时对默认参数赋的值是一个可变对象,比如

def  add_end(L=[]):
       L.append('END')
       return L

Python内部的处理应该是,在程序初始化是时候,创建一个对象X,并将它复制给L, 然后返回L。但是当你再次使用该函数时,内部不会再创建一个对象,而是使用最开始创建的那个对象X,所以当你第二次调用add_end()时,会有两个’END’,第n次调用时有n个’END’,为避免这种问题,所以默认参数使用的默认值必须使用不可变对象

2.3 可变长参数 list

可变参数,和C,Java里面的一样,就是在方法中声明一个参数,此参数可以代表0-n个参数。
直接称列表参数或许更好
def test(a, *args):
xxxx

可变参数在函数调用时会自动组装成一个tuple。
这时可以传递0-N个参数进去.
还可以把集合类型传递给这个参数,方式如下
l=set[“aaa”,“bbb”, ‘ccc’]
method(*l)
即可

2.4 关键字参数 dict

1、其实前面的可变长参数相当于传递一个列表到函数中,这里的关键字参数就相当于一个dict-map到函数中,直接叫dict参数更好吧应该,如下:

def person(name, age, **kw):
    print(name, age, kw)

person("小明", 5, country = "china", gender = "男")

m = {"country" : "china", "gender" : "男"}
person("小明", 5, **m)

可以看出,直接传递key = value列表的形式,或者传递组装好的map(前面加上 **)进去都行,直接传递的形式更为简洁

2.5 命名关键字参数 不常用应该 知道即可

还有一种叫做命名关键字参数,就是指定关键字参数的key列表

def person(name, age, *, city, job):
    print(name, age, city, job)

语法上就是原来的参数列表后面,加一个间隔符*,然后后面加上可选的关键字列表,也即key的序列,这个也可用一个可变长参数也可代替

命名参数也可以有自己的默认值,就像默认参数一样,不同的是,命名参数传值时必须指明传给那个命名关键字,而默认参数不一定需要,并且所以的不含默认值得命名参数都需要有只,所以命名参数相对于默认参数的优点是,操作更为明确。

2.6 参数的组合

由于有多种参数,他们之间的组合就比较讲究了,不然使用起来很容易混乱和出错。
参数定义的顺序必须是:一般参数、默认参数、可变参数、命名关键字参数和关键字参数。
虽然后面几种可以改变顺序,但是最好不要那样做,不然很容易混乱。
另外, 虽然可以组合多达5种参数,但不要同时使用太多的组合,否则函数接口的可理解性很差。
另外,函数还有这样的语法:
定义

def f1(a, b, c=0, *args, **kw):
    print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw)

然后调用

>>> args = (1, 2, 3, 4)
>>> f1(*args)

如果在list的元素个数足够的情况下了,解释器会逐一取出list的项填入到函数参数中,(不够会报错),对于关键字参数,则使用dict有一样的语法。

2.7 使用小技巧

对于默认值参数,可以使用 解dict语法,直接传递所以参数,不用一个个传例如
def mm(a = 1, b= 2):

调用
dd= {‘a’ = 2, ‘b’= 4)
调用
mm(**dd)
一个小点,传递参数时,一般参数也可以指定参数名,进行传值。

3.4 生成器

用于生成有限或无限个元素,并不是一次生成,而是调用一次,生成一次。可以使用next(g)方法,让它生成下一个元素。也可以在for循环中使用。
创建生成器的方法:
(1)直接使用 g = (x * x for x in range(10)) 这种生成式,把生成列表时用的[]变成()括号,即可创建一个生成器,它不再是一个列表,不能使用列表相关的方法。而是按生成器的来使用。
(2)使用函数对象创建:
比如

def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        yield b
        a, b = b, a + b
        n = n + 1
    return 'done'

调用next()时,遇到yield就返回一次值,然后函数暂停在这里,下次再调用next()函数从这里开始执行,直到遇到下一个yield,然后暂停,返回值。显然,对于循环中的next(),会循环的遇到yield。
含有yield的函数可以称作生成器函数,调用它之后返回的是一个生成器,并不是普通的返回值。比如
m = fib(6);
next(m);
或者
m = fib(b).__next__ #其中的__next_是生成器函数拥有的一个属性
m()
这两种方法让生成器进行下一次执行
可以直接对列表进行相加拼接 如 l[0:2] + s[8:]

这里可看出,Python的函数是能记录状态的,这和C中的函数是不一样的。在Python中它的实质和对象是没有区别的

重点内容

##二、函数式编程
函数式编程接近与数学计算,抽象程度很高,相应的和计算机底层结构差异越大,效率不如C语言等高。
函数式编程就是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用。而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的。

Python对函数式编程提供部分支持。由于Python允许使用变量,因此,Python不是纯函数式编程语言。

###1、高阶函数
函数 == 对象
函数式编程中,函数本身像一个对象一样,只是没有内部数据状态而,而函数名和变量名一样,指向某个函数对象的地址。
a、如f = abs 此时f就代表abs函数,能计算绝对值了。而将abs看做一个变量,它也是可变的!! 注意赋值是不带括号和参数的如果带了,就是执行了。
b、如果执行abs = 10,ads将不再能计算绝对值了,它将变成10这个数字。abs函数丢失了!,此时想要重新可用,需要重启Python交互式环境。
c、函数可以作为参数,返回值,容器元素等,被传递,存储等几乎任何变量使用的地方。

1.1 map/reduce
map就是映射,就想数学中的映射函数一样,是现实世界中一类十分常见的操作处理。map就是对一个序列中所有元素做某种变换,比如路径字符串替换前面的盘符。使用方式如下,先定义一个处理的函数f,然后放到map(f, Iterable)中,最后返回一个lterator惰性计算的序列。

>>> def f(x):
...     return x * x
...
>>> r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> list(r)
[1, 4, 9, 16, 25, 36, 49, 64, 81]

reduce:
reduce 和map的不同点是函数参数个数必须为2,即f(x1, x2),且操作序列的方式不同,它会将函数前一次计算的结果作为参数x1传入,然后取下一个数做x2传入,所以reduce的Iterable的元素个数至少为2,最后计算出一个结果。即
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)

1.2 filter
如名,filter对列表中的元素进行过滤,传入一个返回boolean的函数即可。

1.3 sort
如名,对列表中的元素进行排序,使用方式
sorted([36, 5, -12, 9, -21]) 直接排序

sorted([36, 5, -12, 9, -21], key=abs) 添加排序函数,此排序函数返回一个值,然后根据返回值排序

sorted([36, 5, -12, 9, -21], key=abs, reverse=True) 反向

以上元素能方便的转换成key的,如果有多级参数排序这种不好转换成key的,在Python2里面可以传入cmp函数,但是在Python3里面去掉了,搞了一个很复杂的操作。

2、返回函数
即在函数中创建另一个函数,然后作为返回值返回,此函数并不会立即执行,需要在后面进行显示的的调用之后才会执行。此函数可以访问外部函数的参数和局部变量,本质上是持有它们的引用, 可以访问这个引用,以及改变它所指向的对象内部变量的值,但是不能改变这个引用所指向的地址及对象,只能外部函数改变,这和Java的函数里面的匿名内部类相似,只是没有想Java里面一样final化,Python里面没有这个机制,所有会有后面一个问题q:,比如

def lazy_sum(*args):
    def sum():
        ax = 0
        for n in args:
            ax = ax + n
        return ax
    return sum


f = lazy_sum(1, 3, 5, 7, 9)
print(f())

特点:
1、每次调用外部返回的函数都是不一样的。

>>> f1 = lazy_sum(1, 3, 5, 7, 9)
>>> f2 = lazy_sum(1, 3, 5, 7, 9)
>>> f1==f2
False

问题q:返回的函数是持有内部局部变量的引用,并不是直接拷贝一个,所以如果在下次执行之前,局部变量发生改变,函数持有的数据也是改变了的。比如下面这种:

def count():
    fs = []
    for i in range(1, 4):
        def f():
             return i*i  
        fs.append(f)
    return fs

f1, f2, f3 = count()

>>> f1()
9
>>> f2()
9
>>> f3()
9

#因为在循环中定义的函数,等到后面某个时间执行时,内部函数(闭包)指向的变量的值已经改变了,变成了3。所以输出结果都是9

对于循环内的变量,如果一定要引用,方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变:

def count():
def f(j):
def g():
return j*j
return g
fs = []
for i in range(1, 4):
fs.append(f(i)) # f(i)立刻被执行,因此i的当前值被传入f()
return fs
再看看结果:

>>> f1, f2, f3 = count()
>>> f1()
1
>>> f2()
4
>>> f3()
9

因为传参时会创建一个新的引用,count函数对i的改变,不可能再影响f的j参数了。缺点是代码较长,可利用lambda函数缩短代码。

2、lambda表达式——匿名函数
lambda表达式的格式:lambda 参数列表:一个表达式
参数列表可以有多种参数,当然,它还可以像一个普通的函数一样,可以赋值传递等。
使用lambda可以简化函数的编写,更加快捷
lambda表达式可用来编写跳转表(jump table),就是行为的列表或字典。例如:

L = [(lambda x: x**2),  
    (lambda x: x**3),  
    (lambda x: x**4)]  

** 3、函数装饰器 **
顾名思义,对函数加上一层包装,增加其功能。
例如

def log(func):

	@functools.wraps(func)
    def wrapper(x):
        print('start' + func.__name__)
        ret = func(x)
        print('end' + func.__name__)
        return ret

    return wrapper

@log
def abs(x):
    if x > 0:
        return x
    else:
        return -x


print(abs(-11))

Python里面的函数名可以通过赋值改变其指向,所以其中的@log就相当于依据 abs = log(abs) 此时abs就相当于wapper函数,可在wrapper中接收相同的参数,返回函数值等。
使用装饰器之后,原函数的函数名会改变,Python提供了一个funtools函数进行处理 如上面的@functools.wraps(func)就是把wrapper的函数名称变回去,有些依赖签名的工具需要这个。

如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数,写出来会更复杂。比如,要自定义log的文本:

def log(text):
    def decorator(func):
        def wrapper(*args, **kw):
            print('%s %s():' % (text, func.__name__))
            return func(*args, **kw)
        return wrapper
    return decorator

这个3层嵌套的decorator用法如下:

@log('execute')
def now():
    print('2015-3-25')

执行结果如下:

>>> now()
execute now():
2015-3-25

和两层嵌套的decorator相比,3层嵌套的效果是这样的:

>>> now = log('execute')(now)
我们来剖析上面的语句,首先执行log(‘execute’),返回的是decorator函数,再调用返回的函数,参数是now函数,返回值最终是wrapper函数。这样就能往第三层的函数里面传入相同的参数,并且可以使用第一层的属于Derector的参数了。巧妙的利用内部函数可以访问外部函数参数和局部变量的特性。Python里面函数基本也相当于对象了,外部函数的参数、局部变量被函数对象持有就不会消失。

4、functools提供的一些功能
functools.partial(f, a); 根据已有的函数f,自动传入部分参数,然后以此创建一个新的函数。编码中,通常需要创建某个函数的重载函数,原来函数所有参数都需要调用时传递,重载的只需传递部分的参数,另一部分在重载函数内传递默认值即可。functools.partial就是用来简化这种操作的。

廖雪峰 Java 教程 Java教程 Java快速入门 Java简介 安装JDK 第一个Java程序 Java代码助手 使用IDE 使用IDE练习插件 Java程序基础 Java程序基本结构 变量和数据类型 整数运算 浮点数运算 布尔运算 字符和字符串 数组类型 流程控制 输入和输出 if判断 switch多重选择 while循环 do while循环 for循环 break和continue 数组操作 遍历数组 数组排序 多维数组 命令行参数 面向对象编程 面向对象基础 方法 构造方法 方法重载 继承 多态 抽象类 接口 静态字段和静态方法 包 作用域 classpath和jar 模块 Java核心类 字符串和编码 StringBuilder StringJoiner 包装类型 JavaBean 枚举类 BigInteger BigDecimal 常用工具类 异常处理 Java的异常 捕获异常 抛出异常 自定义异常 使用断言 使用JDK Logging 使用Commons Logging 使用Log4j 使用SLF4J和Logback 反射 Class类 访问字段 调用方法 调用构造方法 获取继承关系 动态代理 注解 使用注解 定义注解 处理注解 泛型 什么是泛型 使用泛型 编写泛型 擦拭法 extends通配符 super通配符 泛型和反射 集合 Java集合简介 使用List 编写equals方法 使用Map 编写equals和hashCode 使用EnumMap 使用TreeMap 使用Properties 使用Set 使用Queue 使用PriorityQueue 使用Deque 使用Stack 使用Iterator 使用Collections IO File对象 InputStream OutputStream Filter模式 操作Zip 读取classpath资源 序列化 Reader Writer PrintStream和PrintWriter 日期与时间 基本概念 Date和Calendar LocalDateTime ZonedDateTime DateTimeFormatter Instant 最佳实践 单元测试 编写JUnit测试 使用Fixture 异常测试 条件测试 参数化测试
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值