python函数

一、函数基础

1. python函数帮助信息查询方法:

1) help(func)

2) 查阅官网:https://docs.python.org/3/library/functions.html

abs()

delattr()

hash()

memoryview()

set()

all()

dict()

help()

min()

setattr()

any()

dir()

hex()

next()

slice()

ascii()

divmod()

id()

object()

sorted()

bin()

enumerate()

input()

oct()

staticmethod()

bool()

eval()

int()

open()

str()

breakpoint()

exec()

isinstance()

ord()

sum()

bytearray()

filter()

issubclass()

pow()

super()

bytes()

float()

iter()

print()

tuple()

callable()

format()

len()

property()

type()

chr()

frozenset()

list()

range()

vars()

classmethod()

getattr()

locals()

repr()

zip()

compile()

globals()

map()

reversed()

__import__()

complex()

hasattr()

max()

round()

 

 常用的有数据类型转换:int()float()str()bool()hex()…

>>> bin(1)
'0b1'
>>> bin(9999)
'0b10011100001111'
>>> hex(9999)
'0x270f'
>>> int(0x270f)
9999

函数名其实就是指向一个函数对象的引用,完全可以把函数名赋给一个变量,相当于给这个函数起了一个“别名”:

>>> a = abs # 变量a指向abs函数
>>> a(-1) # 所以也可以通过a调用abs函数
1

2. 定义函数

定义一个函数要使用def语句,依次写出函数名、括号、括号中的参数和冒号(:),然后,在缩进块中编写函数体,函数的返回值用return语句返回。

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

数据类型检查用内置函数isinstance()实现。

3. 空函数

空函数的函数体用pass语句,主要用来作占位符,从而避免语法错误。

4. 函数返回值

函数体内部可以用return随时返回函数结果;函数执行完毕也没有return语句时,自动return None

5. 返回多个值

函数的返回值是一个tuple,但一个值的tuple可以省略括号,因而函数返回一个值时没有括号。多个变量可以同时接收一个tuple,按位置赋给对应的值,所以python的函数返回多值就是返回一个tuple,但写起来更方便。

6. 调用python文件中的函数

保存python函数文件为quadratic.py,然后在当前目录下启动python解析器,用from quadratic import my_quadratic来导入my_quadratic()函数,注意quadratic是文件名(不含.py扩展名)。

>>> from quadratic import quadratic
>>> print(quadratic(1,2,1))
-1.0

quadratic.py代码(一元二次方程ax2+bx+c=0)如下:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import math

def quadratic(a, b, c): 
    if not isinstance(a, (int, float)):
        raise TypeError('param type error')
    if not isinstance(b, (int, float)):
        raise TypeError('param type error')
    if not isinstance(c, (int, float)):
        raise TypeError('param type error')
    
    delta=b*b-4*a*c
    if delta < 0:
        return '无解'
    elif delta == 0:
        return (-b + math.sqrt(delta))/(2*a)
    else:
        x1= (-b - math.sqrt(delta))/(2*a)
        x2= (-b + math.sqrt(delta))/(2*a)
        return x1, x2

二、函数参数 

1. 默认参数

 def power(x, n=2)

此时,可通过power(1)power(2, 5)调用power函数来实现幂次操作。

设置默认参数时,有几点要注意:

一是必选参数在前,默认参数在后,否则Python的解释器会报错(思考一下为什么默认参数不能放在必选参数前面);

二是如何设置默认参数。

当函数有多个参数时,把变化大的参数放前面,变化小的参数放后面。变化小的参数就可以作为默认参数。

默认参数降低了函数调用的难度,而一旦需要更复杂的调用时,又可以传递更多的参数来实现。无论是简单调用还是复杂调用,函数只需要定义一个。

默认参数很有用,但使用不当,也会掉坑里。默认参数有个最大的坑,演示如下:

先定义一个函数,传入一个list,添加一个END再返回:

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

当你正常调用时,结果似乎不错:

>>> add_end([1, 2, 3])
[1, 2, 3, 'END']
>>> add_end(['x', 'y', 'z'])
['x', 'y', 'z', 'END']

 当你使用默认参数调用时,一开始结果也是对的:

>>> add_end()
['END'

但是,再次调用add_end()时,结果就不对了:

>>> add_end()
['END', 'END']
>>> add_end()
['END', 'END', 'END']

很多初学者很疑惑,默认参数是[],但是函数似乎每次都“记住了”上次添加了'END'后的list

原因解释如下:

Python函数在定义的时候,默认参数L的值就被计算出来了,即[],因为默认参数L也是一个变量,它指向对象[],每次调用该函数,如果改变了L的内容,则下次调用时,默认参数的内容就变了,不再是函数定义时的[]了。

定义默认参数要牢记一点:默认参数必须指向不变对象!

 要修改上面的例子,我们可以用None这个不变对象来实现:

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

现在,无论调用多少次,都不会有问题:

>>> add_end()
['END']
>>> add_end()
['END']

2. 可变参数

def calc(*numbers):
    sum = 0
    for n in numbers:
        sum = sum + n * n
    return sum

定义可变参数和定义一个listtuple参数相比,仅仅在参数前面加了一个*在函数内部,参数numbers接收到的是一个tuple,因此,函数代码完全不变。但是,调用该函数时,可以传入任意个参数,包括0个参数

如果已经有一个list或者tuple,要调用一个可变参数怎么办?可以这样做:

>>> nums = [1, 2, 3]
>>> calc(nums[0], nums[1], nums[2])

这种写法当然是可行的,问题是太繁琐,所以Python允许你在listtuple前面加一个*号,把listtuple的元素变成可变参数传进去:

>>> nums = [1, 2, 3]
>>> calc(*nums) 

*nums表示把nums这个list的所有元素作为可变参数传进去。这种写法相当有用,而且很常见。

可变参数既可以直接传入:func(1, 2, 3),又可以先组装listtuple,再通过*args传入:func(*(1, 2, 3))

3. 关键字参数

关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict

def person(name, age, **kw):
    print('name:', name, 'age:', age, 'other:', kw)
>>> person('Bob', 35, city='Beijing')
name: Bob age: 35 other: {'city': 'Beijing'}
>>> person('Adam', 45, gender='M', job='Engineer')
name: Adam age: 45 other: {'gender': 'M', 'job': 'Engineer'}

关键字参数有什么用?它可以扩展函数的功能。比如,在person函数里,我们保证能接收到nameage这两个参数,但是,如果调用者愿意提供更多的参数,我们也能收到。试想你正在做一个用户注册的功能,除了用户名和年龄是必填项外,其他都是可选项,利用关键字参数来定义这个函数就能满足注册的需求。

和可变参数类似,也可以先组装出一个dict,然后,把该dict转换为关键字参数传进去:

>>> extra = {'city': 'Beijing', 'job': 'Engineer'}
>>> person('Jack', 24, city=extra['city'], job=extra['job'])
name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'}

当然,上面复杂的调用可以用简化的写法:

>>> extra = {'city': 'Beijing', 'job': 'Engineer'}
>>> person('Jack', 24, **extra)
name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'}

**extra表示把extra这个dict的所有key-value用关键字参数传入到函数的**kw参数,kw将获得一个dict,注意kw获得的dictextra的一份拷贝,对kw的改动不会影响到函数外的extra

关键字参数既可以直接传入:func(a=1, b=2),又可以先组装dict,再通过**kw传入:func(**{'a': 1, 'b': 2})

4. 命名关键字参数

如果要限制关键字参数的名字,就可以用命名关键字参数,例如,只接收cityjob作为关键字参数。这种方式定义的函数如下:

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

和关键字参数**kw不同,命名关键字参数需要一个特殊分隔符**后面的参数被视为命名关键字参数。

调用方式如下:

>>> person('Jack', 24, city='Beijing', job='Engineer')
Jack 24 Beijing Engineer

如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符*了:

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

命名关键字参数必须传入参数名,这和位置参数不同。如果没有传入参数名,调用将报错:

>>> person('Jack', 24, 'Beijing', 'Engineer')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: person() takes 2 positional arguments but 4 were given

由于调用时缺少参数名cityjobPython解释器把这4个参数均视为位置参数,但person()函数仅接受2个位置参数。

命名关键字参数可以有缺省值,从而简化调用:

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

由于命名关键字参数city具有默认值,调用时,可不传入city参数: 

>>> person('Jack', 24, job='Engineer')
Jack 24 Beijing Engineer

使用命名关键字参数时,要特别注意,如果没有可变参数,就必须加一个*作为特殊分隔符。如果缺少*Python解释器将无法识别位置参数和命名关键字参数。

5. 参数组合 

Python中定义函数,可以用必选参数、默认参数、可变参数、关键字参数和命名关键字参数,这5种参数都可以组合使用。但是请注意,参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数。

所以,对于任意函数,都可以通过类似func(*args, **kw)的形式调用它,无论它的参数是如何定义的。

虽然可以组合多达5种参数,但不要同时使用太多的组合,否则函数接口的可理解性很差。 

三、高阶函数

把函数作为参数传入,这样的函数称为高阶函数,函数式编程就是指这种高度抽象的编程范式。

map

map(func, *iterables) --> map object

Iterator map(func, *iterables)

map()函数接收两个参数,一个是函数,一个是Iterablemap将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回。 

reduce

reduce把一个函数作用在一个序列[x1, x2, x3, ...]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算,其效果就是:

reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4) 

filter

filter()也接收一个函数和一个序列。和map()不同的是,filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素。可见用filter()这个高阶函数,关键在于正确实现一个“筛选”函数。

注意到filter()函数返回的是一个Iterator,也就是一个惰性序列,所以要强迫filter()完成计算结果,需要用list()函数获得所有结果并返回list

sort

sorted()也是一个高阶函数。用sorted()排序的关键在于实现一个映射函数。

sorted()默认排序数字队列,key用来作用于list每个元素,第三个参数reverse用于确定是否倒叙。

>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True)
['Zoo', 'Credit', 'bob', 'about'

匿名函数lambda

匿名函数lambda x: x * x实际上就是:

def f(x):
    return x * x

关键字lambda表示匿名函数,冒号前面的x表示函数参数。匿名函数有个限制,就是只能有一个表达式,不用写return返回值就是该表达式的结果。

用匿名函数有个好处,因为函数没有名字,不必担心函数名冲突。此外,匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数;同样,匿名函数可以作为返回值返回。

装饰器

在函数调用前后自动打印日志,但又不希望修改函数的定义,这种在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。

decorator就是一个返回函数的高阶函数。所以,我们要定义一个能打印日志的decorator,可以定义如下:

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

注:函数对象有一个__name__属性,可以拿到函数的名字。

观察上面的log,因为它是一个decorator,所以接受一个函数作为参数,并返回一个函数。我们要借助Python@语法糖,把decorator置于函数的定义处:

@log等价于再次赋值:now = log(now),现在直接调用now()即可包含装饰器函数。

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

调用now()函数,不仅会运行now()函数本身,还会在运行now()函数前打印一行日志

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

如果decorator本身需要传入参数(log中打印参数,参数是装饰器要增加的参数,而非函数),那就需要编写一个返回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

因为返回的那个wrapper()函数名字就是'wrapper',所以,需要把原始函数的__name__等属性复制到wrapper()函数中,否则,有些依赖函数签名的代码执行就会出错。

不需要编写wrapper.__name__ = func.__name__这样的代码,Python内置的functools.wraps就是干这个事的,所以,一个完整的decorator的写法如下:

import functools

def log(func):
    @functools.wraps(func)
    def wrapper(*args, **kw):
        print('call %s():' % func.__name__)
        return func(*args, **kw)
    return wrapper 

或者针对带参数的decorator

import functools

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

 

参考:

1. 函数式编程 

https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/0014317848428125ae6aa24068b4c50a7e71501ab275d52000

2. Python 函数装饰器  https://www.runoob.com/w3cnote/python-func-decorators.html

3. python进阶-装饰器 https://eastlakeside.gitbook.io/interpy-zh/

posted @ 2019-04-16 23:22  yuxi_o  阅读(305)  评论(0编辑  收藏  举报