11.函数

函数

接下来开始学习函数,函数这个知识点很重要,需要认真学习

先来了解下什么是函数?

函数是组织好的,可重复使用的,用来实现单一或相关功能的代码块。

如何使用函数

函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段。

函数能提高应用的模块性,和代码的重复利用率,Python提供了许多内建函数,比如print()。

但也可以自己创建函数,这被叫做用户自定义函数。

定义函数

  • 函数代码块以 def 关键词开头,后接函数标识符名称和圆括号()
  • def关键词必须后跟函数名称和带括号的形式参数列表
  • 构成函数体的语句从下一行开始,并且必须缩进
  • 函数的第一行语句可以选择性地使用文档字符串—用于存放函数说明
  • return语句返回一个函数的值。 return没有表达式参数返回None。从函数的末尾掉落也会返回None

现在以Fibonacci数作为例子:

def fib(n):                # 定义一个函数名称,用来计算fibonacci数
    a,b = 1,1              # 赋值
    for i in range(n):    
        print(a,end=' ')
        a,b = b,a+b
    print()

调用函数

已经知道了如何定义函数,那么该如何调用这个函数,接下来继续以上面的那个例子作为示例,进行讲解

def fib(n):                # 定义一个函数名称,用来计算fibonacci数
    a,b = 1,1              # 赋值
    for i in range(n):    
        print(a,end=' ')
        a,b = b,a+b
    print()

fib(10)                    # 调用函数, 并传入一个参数

看,这样就调用了,是不是很简单

运行结果:

1 1 2 3 5 8 13 21 34 55

函数引用

def outFunc():

    print("outFunc()正在被调用")

# 引用函数
outfunc = outFunc
# 通过引用调用函数
outfunc()

# 调用函数
outFunc()



print(id(outfunc))
print(id(outFunc))

运行结果:

outFunc()正在被调用
outFunc()正在被调用
2357814673888
2357814673888

函数的参数

现在已经都知道了如何定义函数和调用函数了,接下来,讲解一下函数里面的参数,这一点很重要,要认真学喔

形参和实参

参数从调用的角度来说,分为形式参数和实际参数。

形参指的是函数创建和定义过程中小括号里的参数,而实参则指的是函数在被调用的过程中传递进来的参数。

下面举个小例子

def person(name):
    print("I am " + str(name))

person("angle")

在person(name)中name是形参,因为这只是代表一个位置,一个变量名而已;而person("angle")中传递的字符串"angle"是实参,因为这是一个具体的内容,是赋值到变量中的值

关键字参数

由于在调用函数的时候,有时候可能会很粗心不小心把参数位置的顺序给弄错了,从而导致函数无法按照预期实现要求。

因此关键字参数是为了解决这个问题而出现的,有了这个就可以很简单的解决这个潜在的问题了。

举个例子

def person(name,age):
    print('{0}的年龄是{1}'.format(name,age))

person("angle","18")

person("18","angle")

person(age="18",name="angle")

person(name="angle",age="18")

运行结果:

angle的年龄是18
18的年龄是angle
angle的年龄是18
angle的年龄是18

从结果可以看到关键字参数是很有用的,因为参数的顺序一不小心弄错,会导致程序无法完成预期的结果的

默认参数

默认参数可以在参数定义的过程中,为形参进行赋值,当函数调用的时候,不传递实参,则默认使用形参的初始值代替。

举个小栗子

def person(name,age,sex='未知'):
    print('{0}的年龄是{1},性别{2}'.format(name,age,sex))


person(name='angle',age='18')
person(name='angle',age='18',sex='男')

是不是很简单,那么继续往下学习

收集参数
什么是收集参数?

在定义函数的时候,不知道这个函数有多少个参数,因为在工作开发的时候,需要根据实际情况添加和删除参数的,这就造成了参数的不确定,由于添加和删除参数工作有点繁琐,所以这时才有了收集参数,收集参数是为了解决这个问题而出现的,收集参数两种,一种是将参数打包元组,一种是以字典形式打包

args

先来讲下第一种,只需要在参数前面加上一个星号(*)就可以了

def person(*args):
    print('{0}的年龄是{1},性别{2}'.format(args[0],args[1]))


person('angle',18)

运行结果

angle的年龄是18,性别未知

参数在传过去的时候,会进行打包成元组传递过去

如果想要单独额外指定参数,可以使用关键字参数

def person(*args,sex='未知'):
    print('{0}的年龄是{1},性别{2}'.format(args[0],args[1],sex))


person('angle','18',sex='男')

运行结果

angle的年龄是18,性别男

星号其实既可以打包又可以解包。什么是解包?

举个小栗子

将列表a传入test参数的收集参数*args中

def test(*args):
    print("{} 个参数".format(len(args)))
    print("第二个参数:{}".format(args[1]))

a = [i for i in range(10)]
print(a)

test(a)

运行结果

Traceback (most recent call last):
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
1 个参数
  File "E:/JetBrains/Code_practice_project/test/fibonacci.py", line 8, in <module>
    test(a)
  File "E:/JetBrains/Code_practice_project/test/fibonacci.py", line 3, in test
    print("第二个参数:{}".format(args[1]))
IndexError: tuple index out of range

那么调用test(a)时便会出错,此时需要在a前面加上个星号(*)表示实参需要解包后才能使用

def test(*args):
    print("{} 个参数".format(len(args)))
    print("第二个参数:{}".format(args[1]))

a = [i for i in range(10)]
print(a)

test(*a)

运行结果

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
10 个参数
第二个参数:1
kwargs

另一种收集方式,用两个星号(**)表示

def test(**kwargs):
  for key,value in kwargs.items():
      print("key:{},value:{}".format(key,value))


params = {
    "name":"angle","name":"angle",
    "age":18,
    "sex":"男",
}

test(**params)

运行结果

key:name,value:angle
key:age,value:18
key:sex,value:男

慢慢来解析一下是什么意思?

test(**params)这个是为了收集多个字典参数,打包传递过去
def test(**kwargs)是为了将字典进行解包,和前面讲过的是一样的

函数文档

在程序的时候,有时候为了使代码可读性更高,有时候会在程序旁边进行注释,可是这样影响python代码的美观,这时就可以使用函数文档。

举个例子

def money(dollar):
    """
    美元 --> 人民币
    汇率暂定为6.5
    :param dollar:
    :return: dollar * 6.5
    """
    return dollar * 6.5

m = money(10)

print(m)

运行结果

65.0

这样做还有个好处,这样可以不用打开源码慢慢去找文档注释,这时可以通过特殊属性

__doc__获取(注意doc两边分别两条下划线)
print(money.__doc__)

运行结果

    美元 --> 人民币
    汇率暂定为6.5
    :param dollar:
    :return: dollar * 6.5

在不确定函数的用法的时候,可以使用help()函数来查看函数的文档

print(help(money))

运行结果

Help on function money in module __main__:

money(dollar)
    美元 --> 人民币
    汇率暂定为6.5
    :param dollar:
    :return: dollar * 6.5

None

返回函数

python函数的返回值,可以返回一个值,也能够返回多个值

单个值
def test():
    return 1

print(test())

运行结果

1
多个值
def test():
    return 'angle',18,[1,2,3]

print(test())

运行结果

('angle', 18, [1, 2, 3])

再返回多个值得时候,会打包成元组返回

递归

递归就是不停的调用自己,也就是不停重复做某一个事情

举个小栗子

def f(n):
    if n <= 1:
        return 1
    return f(n-1)*n

n = int(input('请输入一个数字:'))
number = f(n)
print("阶乘结果:{}".format(number))

怎么来理解这个函数了

这里假设输入3

img

运行结果

请输入一个数字:3
阶乘结果:6

匿名函数lambda

Lambda表达式(有时称为lambda表单)用于创建匿名函数

如何使用

先来看看语法格式,然后看下例子

语法
lambda [parameter_list]:expression
实例
>>> add = lambda x,y:x+y
>>> add(3,5)
8

等价于

>>> def add(x,y):
...     return x+y
...
>>> add(3,5)
8

这样感觉是不是很繁琐,还是感觉lambda好用吧

在举一下其他的例子

比如前面讲过的列表排序

列表排序
>>> a = [(1,2),(4,1),(1,3),(2,-5)]
>>> a.sort(key=lambda v:v[0])
>>> a
[(1, 2), (1, 3), (2, -5), (4, 1)]
>>> a.sort(key=lambda v:v[1])
>>> a
[(2, -5), (4, 1), (1, 2), (1, 3)]

内嵌函数和闭包

讲解了什么是函数,如何使用,现在来学习下什么是内嵌函数和闭包

内嵌函数

内嵌函数其实就是一个函数在其内部在套一个函数,和嵌套循环一样,又可以叫做内部函数

示例:

def outFunc(name):

    print("outFunc()正在被调用")

    def inFunc(name):

        print("inFunc()正在被调用")
        return name

    return inFunc(name)


print(outFunc("angle"))

运行结果:

outFunc()正在被调用
inFunc()正在被调用
angle

是不是很简单,但是这个只能调用outFunc()函数,而inFunc()函数只能在outFunc()函数中调用,而在outFunc()函数外调用,会进行报错,因为inFunc()函数的作用域只在outFunc()函数内才生效

def outFunc(name):

    print("outFunc()正在被调用")

    def inFunc(name):

        print("inFunc()正在被调用")
        return name

    return inFunc(name)

inFunc("angle")

运行结果:

def outFunc(name):

    print("outFunc()正在被调用")

    def inFunc(name):

        print("inFunc()正在被调用")
        return name

    return inFunc(name)

inFunc("angle")
闭包

python中的闭包从表现形式上定义为:如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包。

举个小栗子

def outFunc(param1):

    def inFunc(param2):

        print("inFunc()函数,param2参数值为{}".format(param2))

        return param1 * param2

    # 注意,这里返回的闭包的结果
    return inFunc

test = outFunc(3)
# 这里其实调用的是inFunc(param2),参数5传给的是param2
result = test(5)
print(result)

运行结果:

inFunc()函数,param2参数值为5
15

可以将

test = outFunc(3)
result = test(5)

缩写为

result = outFunc(3)(5)

这两者之间是等价的

def outFunc(param1):

    def inFunc(param2):

        print("inFunc()函数,param2参数值为{}".format(param2))

        return param1 * param2
    return inFunc

result = outFunc(3)(5)
print(result)

运行结果

inFunc()函数,param2参数值为5
15

装饰器

什么是装饰器?

装饰器是一个很著名的设计模式,经常被用于有切面需求的场景,较为经典的有插入日志、性能测试、事务处理等。

装饰器有什么用?

抽离出大量函数中与函数功能本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能,也称之为扩展功能。

先看一个简单的例子:

def foo():
    print("this is a foo")

foo()

现在有一个新的需求,希望可以记录下函数的执行日志,于是在代码中添加日志代码

import logging


def foo():
    print("this is a foo")

    logging.warning("foo is running")


foo()

可是如果函数 bar()、bar2() 也有类似的需求,怎么做?再写一个 logging 在 bar 函数里?这样就造成大量雷同的代码,为了减少重复写代码,我们可以这样做,重新定义一个新的函数:专门处理日志 ,日志处理完之后再执行真正的业务代码

import logging


def use_logging(func):
    logging.warning("foo is running")
    func()

def foo():
    print("this is a foo")

use_logging(foo)

这样做逻辑上是没问题的,功能是实现了,但是调用的时候不再是调用真正的业务逻辑 foo 函数,而是换成了 use_logging 函数,这就破坏了原有的代码结构, 现在不得不每次都要把原来的那个 foo 函数作为参数传递给 use_logging 函数,那么有没有更好的方式的呢?当然有,答案就是装饰器。

简单装饰器
import logging


def use_logging(func):

    def wrapper():
        logging.warning("foo is running")
        # 注意,这把foo()函数当做参数传递进来了,执行func()相当于执行foo()
        return func()

    return wrapper


def foo():
    print("this is a foo")

# 因为装饰器use_logging(foo)返回的是函数对象wrapper,这条语句相当于 foo = wrapper
foo = use_logging(foo)
# foo = wrapper
# 执行foo()就相当于执行wrapper()
foo()
# wrapper()

# 上面的两句等价于下面的语句
# use_logging(foo)()

use_logging 就是一个装饰器,它一个普通的函数,它把执行真正业务逻辑的函数 func 包裹在其中,看起来像 foo 被 use_logging 装饰了一样,use_logging 返回的也是一个函数,这个函数的名字叫 wrapper。在这个例子中,函数进入和退出时 ,被称为一个横切面,这种编程方式被称为面向切面的编程。

@语法糖

@符号是装饰器的语法糖

借鉴维基百科的看下什么是语法糖?

语法糖(Syntactic sugar),也译为糖衣语法,是由英国计算机科学家彼得·蘭丁发明的一个术语,指计算机语言中添加的某种语法,这种语法对语言的功能没有影响,但是更方便程序员使用。 语法糖让程序更加简洁,有更高的可读性

import logging


def use_logging(func):

    def wrapper():
        logging.warning("foo is running")
        return func()

    return wrapper

@use_logging
def foo():
    print("this is a foo")


foo()

有了@,就可以省去foo = use_logging(foo)这一句了,能够直接调用foo()得到想要的答案,并且提高了程序的可重复利用性,并增加了程序的可读性

如何传参

传入一个参数

import logging


def use_logging(func):

    # 传入参数name
    def wrapper(name):
        logging.warning("{} is running".format(name))
        return func(name)

    return wrapper

@use_logging
def foo(name):
    print("this is a {}".format(name))


foo("foo")

传入多个参数

import logging


def use_logging(func):

    def wrapper(*args,**kwargs):
        logging.warning("{} is running".format(func.__name__))
        return func(*args,**kwargs)

    return wrapper

@use_logging
def foo(name,age,**params):
    print("name:{},age:{},sex:{}".format(name,age,params.get("sex")))


params = {
    "sex":"男"
}

foo("angle","18",**params)
带参数的装饰器

装饰器还有更大的灵活性,例如带参数的装饰器,在上面的装饰器调用中,该装饰器接收唯一的参数就是执行业务的函数 foo 。装饰器的语法允许在调用时,提供其它参数,比如@decorator(a)。这样,就为装饰器的编写和使用提供了更大的灵活性。比如,可以在装饰器中指定日志的等级,因为不同业务函数可能需要的日志级别是不一样的。

import logging


def use_logging(level):

    def decorator(func):

        def wrapper(*args,**kwargs):

            if level == "warning":
                logging.warning("{} is running".format(func.__name__))
            elif level == "info":
                logging.info("{} is running".format(func.__name__))

            return func(*args,**kwargs)

        return wrapper

    return decorator

@use_logging(level="info")
def foo(name,age,**params):
    print("name:{},age:{},sex:{}".format(name,age,params.get("sex")))


params = {
    "sex":"男"
}

foo("angle","18",**params)

上面的 use_logging 是允许带参数的装饰器。它实际上是对原有装饰器的一个函数封装,并返回一个装饰器。我们可以将它理解为一个含有参数的闭包。当我 们使用@use_logging(level="warn")调用的时候,Python 能够发现这一层的封装,并把参数传递到装饰器的环境中。

@use_logging(level="warn")`等价于`@decorator

等价于如下

import logging


def use_logging(level):

    def decorator(func):

        def wrapper(*args,**kwargs):

            if level == "warning":
                logging.warning("{} is running".format(func.__name__))
            elif level == "info":
                logging.info("{} is running".format(func.__name__))

            return func(*args,**kwargs)

        return wrapper

    return decorator

def foo(name,age,**params):
    print("name:{},age:{},sex:{}".format(name,age,params.get("sex")))


params = {
    "sex":"男"
}

# 调用最外围函数
test = use_logging(level="warning")
# test = decorator
test(foo)("angle","18",**params)
类装饰器

没错,装饰器不仅可以是函数,还可以是类,相比函数装饰器,类装饰器具有灵活度大、高内聚、封装性等优点。使用类装饰器主要依靠类的__call__方法,当使用 @ 形式将装饰器附加到函数上时,就会调用此方法。

class Foo(object):
    def __init__(self, func):
        self._func = func

    def __call__(self):
        print ('class decorator runing')
        self._func()
        print ('class decorator ending')

@Foo
def bar():
    print ('bar')

bar()
functools.wraps

使用装饰器极大地复用了代码,但是他有一个缺点就是原函数的元信息不见了,比如函数的docstring、name、参数列表,先看例子:

# 装饰器
def logged(func):
    def with_logging(*args, **kwargs):
        print( func.__name__)      # 输出 'with_logging'
        print (func.__doc__)       # 输出 None
        return func(*args, **kwargs)
    return with_logging

# 函数
@logged
def f(x):
   """does some math"""
   return x + x * x

logged(f)

不难发现,函数 f 被with_logging取代了,当然它的docstring,__name__就是变成了with_logging函数的信息了。好在我们有functools.wraps,wraps本身也是一个装饰器,它能把原函数的元信息拷贝到装饰器里面的 func 函数中,这使得装饰器里面的 func 函数也有和原函数 foo 一样的元信息了。

from functools import wraps
def logged(func):
    @wraps(func)
    def with_logging(*args, **kwargs):
        print func.__name__      # 输出 'f'
        print func.__doc__       # 输出 'does some math'
        return func(*args, **kwargs)
    return with_logging

@logged
def f(x):
   """does some math"""
   return x + x * x
装饰器顺序

假如一个函数同时有多个装饰器如下

def a(func):

    def wrapper(*args,**kwargs):
        print("a")
        return func(*args,**kwargs)

    return wrapper


def b(func):
    def wrapper(*args, **kwargs):
        print("b")
        return func(*args, **kwargs)

    return wrapper


def c(func):
    def wrapper(*args, **kwargs):
        print("c")
        return func(*args, **kwargs)

    return wrapper


def d(func):
    def wrapper(*args, **kwargs):
        print("d")
        return func(*args, **kwargs)

    return wrapper

@a
@b
@c
@d
def e1():
    print("e1")

def e2():
    print("e2")

e1()
print("-"*30)
a(b(c(d(e2))))()

运行结果

a
b
c
d
e1
------------------------------
a
b
c
d
e2

执行顺序是从里到外,最先调用最里层的装饰器,最后调用最外层的装饰器

高级函数

接下来讲下高级函数,这些都是python的内置函数

filter
简述

filter()函数用于过滤序列,过滤掉不符合条件的元素,返回一个迭代器,产生可迭代对象,如果转换为列表,需要使用list()。

该接收两个参数,第一个为函数,第二个为序列,序列的每个元素作为参数传递给函数进行判,然后返回 True 或 False,最后将返回 True 的元素放到新列表中。是真的。 如果function为None,则返回true的项。

语法
filter(function or None,iterable) - > filter对象
参数
  • function:函数
  • iterable:可迭代的对象
返回值

返回一个可迭代对象

实例
>>> def isOdd(n):
...     return n%2 == 1
...
>>> test = filter(isOdd,[1,2,3,4,5,6,7])
>>> new_test = list(test)
>>> new_test
[1, 3, 5, 7]
sorted
简述

sorted()函数从iterable中的项返回一个新的排序列表

语法
sorted(iterable,*,key = None,reverse = False )
参数
  • iterable -- 可迭代对象。
  • key指定一个参数的函数,用于从每个列表元素中提取比较键:key=str.lower。默认值为None (直接比较元素)。
  • reverse是一个布尔值。如果设置为True,则对列表元素进行排序,并且反转
返回值

一个参数返回对象类型, 三个参数,返回新的类型对象

实例
>>> a = [5,6,7,3,6]
>>> a
[5, 6, 7, 3, 6]
>>> sorted(a)
[3, 5, 6, 6, 7]
>>> sorted(a,reverse=True) # 反转
[7, 6, 6, 5, 3]
>>> a.sort() # 注意sort只为list定义,而sorted()函数可以接收任何的iterable
>>> a        
[3, 5, 6, 6, 7]

>>> test = [4,3,8,7,6,1,0,2]
>>> test
[4, 3, 8, 7, 6, 1, 0, 2]

# 利用key进行排序
>>> result = sorted(test,key=lambda x:x)
>>> result
[0, 1, 2, 3, 4, 6, 7, 8]
>>> result = sorted(test,key=lambda x:-x)
>>> result
[8, 7, 6, 4, 3, 2, 1, 0]

温馨提示

sort 与 sorted 区别:

sort 是应用在 list 上的方法,sorted 可以对所有可迭代的对象进行排序操作。

list 的 sort 方法返回的是对已经存在的列表进行操作,而内建函数 sorted 方法返回的是一个新的 list,而不是在原来的基础上进行的操作。

map
简述

map() 会根据提供的函数对指定序列做映射。

第一个参数 function 以参数序列中的每一个元素调用 function 函数,返回包含每次 function 函数返回值的新列表。

语法
map(func, *iterables)
参数
  • function:函数
  • iterable:一个或多个序列
返回值

返回迭代器

实例
>>> def func(x):
...     return x*2
...
>>> map(func,[1,2,3,4,5])
<map object at 0x000001B76E4DABA8>
>>> list(map(func,[1,2,3,4,5]))
[2, 4, 6, 8, 10]
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值