函数

函数的定义

在python中使用def语句来定义函数。

def 函数名(参数1,参数2,···):
  '''注释'''#可有可无
  函数体

注意事项:

  1. 函数形参不需要声明类型,也不需要指定函数返回值类型
  2. 即使该函数不需要接收任何参数,也必须保留一对空的圆括号
  3. 括号后面的冒号必不可少
  4. 函数体相对于def关键字必须保持一定的空格缩进
  5. Python允许嵌套定义函数

在Python中,定义函数时也不需要声明函数的返回值类型,而是使用return语句结束函数执行的同时返回任意类型的值,函数返回值类型与return语句返回表达式的类型一致。

不论return语句出现在函数的什么位置,一旦得到执行将直接结束函数的执行。

如果函数没有return语句、有return语句但是没有执行到或者执行了不返回任何值的return语句,解释器都会认为该函数以return None结束,即返回空值

函数的形参与实参

形参

形参的形式 含义
a 正常参数名,只对应一个实参,如果没有默认值则必须要匹配实参。
*a 用来接受多个位置参数并将其放在一个元组中,可为空,所有未命名的实参(包括以变量作为实参)并且没有匹配的位置都会被放入这个元组中。但是是在先匹配了他前面位置的参数的情况下。它后面的参数只能通过关键参数命名,因为后面的位置参数都会被算入该参数中。
**a 接受多个关键参数并存放到字典中,可为空,所以所有命名的实参并且没有匹配到函数中其他关键参数时,都会被放在这个字典中.后面不能再有其他参数

形参可以设置默认值。如果在调用函数时不赋予该参数值,则会应用默认值。使用函数名.__defaults__可以查看函数当前各个默认参数的默认值,只能查看有默认值的参数。

在定义有默认值参数的函数时,所有的默认值参数必须放在右边,普通的位置参数放在左边,不允许默认参数右边出现普通的位置参数,但是另外两种形式的形参可以在右边出现。

但是,默认参数只会在函数定义时,解释和初始化一次值,如果在函数的重复调用过程中,一直没有为默认参数进行值传递,并且在函数体中有对默认参数值进行修改的情况,那么默认参数的默认值会随着函数的不断调用而不断变化。可能会导致很严重的逻辑错误。

实参

实参的形式 含义
位置参数 通过位置一一对应,所有位置参数按照位置顺序匹配参数。
关键词参数 通过参数名一一对应
*序列(序列解包) 对字典获得的是键值,作为位置参数,会被优先匹配
**序列(序列解包,一般是字典) 键为关键参数名,值为关键参数值,键名必须是函数中的关键参数名或者形参中含有**a形式的参数

注意:调用函数时对实参序列使用一个星号*进行解包后的实参将会被当做普通位置参数对待,并且会在关键参数和使用两个星号**进行序列解包的参数之前进行处理。不管他们之间的位置如何。

实参与形参的值传递

形参实际上获得的是实参的引用,从而获得了实参对应的值。所以一般修改形参不会改变实参,只是形参的引用发生改变而实参的引用并没有发生变化。但是这又分为实参是可变对象还是比可变对象。如果实参是不可变对象(数值,元组,字符串),那么形参无论怎么变化都不会引起实参的变化。如果实参是可变对象(列表,集合,字典,元组,序列,数据框),那么当你对序列中的元素进行增删改等操作时,形参和实参的引用还是一致的,但是形参对所对应对象的值作了修改。所以实参的对应值也改变。而如果是给形参重新赋值,也就是改变了形参的引用,这时候就不会影响到实参。

实参 形参
不可变对象 不管如何变都不会影响实参
可变对象 只有在对序列中的元素进行操作时会影响实参
在对形参做其他赋值等无关序列元素的操做时不会影响实参

实参与形参的匹配

普通位置参数和*等级相同,按照位置顺序匹配;另外两种得等他们匹配完在匹配。

如果有*a的形参,那么前面的参数必然会优先被位置参数匹配(即使已有默认值),也就是要想*a不为空,那么必须有超过前面参数数量的位置参数,并且前面的所有参数也不能有被关键参数命名的情况,否则会重复赋值。后面的参数必须通过关键词参数。

**a的形参因为是对应关键参数,所以对位置没有要求。

总结来说就是先匹配位置参数在匹配关键参数,如果有*形参,前面的参数匹配完后(可以是通过关键参数,但是一定不能有超过该关键参数位置的位置参数),则会继而匹配它。如果有**参数,他会将所有非参数键名的实参收纳进来。

函数的变量作用域

变量起作用的代码范围称为变量的作用域,不同作用域内变量名可以相同,互不影响。

在函数内部定义的普通变量只在函数内部起作用,称为局部变量。当函数执行结束后,局部变量自动删除,不再可以使用。

局部变量的引用比全局变量速度快,应优先考虑使用。

全局变量是在函数的外部存在于全局的变量,如果想在函数内部使用全局变量或者创建全局变量应该怎么办呢?

分类 结果
有全局变量,函数中任意位置没有同名的局部变量 则变量被认为是全局变量,可以使用,但是不可修改全局变量的值,可以对可变类型的元素操作。
有全局变量,函数中存在同名的局部变量,并且没有使用global声明 则函数中的变量被认为是局部变量**(在某个作用域内任意位置只要有为变量赋值的操作,该变量在这个作用域内就是局部变量,除非使用global进行了声明)**,任何关于该变量的操作都必须在赋值操作或者说定义过该变量之后,否则报错。
有全局变量,函数中有global声明 则变量被认为是全局变量,并且可以进行任何操作,包括赋值(但必须在global声明之后)。
无全局变量,函数中有global声明 则创建了一个新的全局变量,并且函数结束后生成了一个新的全局变量。

函数中查找变量的顺序是local,nonlocal,global

函数嵌套定义、可调用对象与修饰器

一个函数中可以继续定义其他的函数。

函数属于Python可调用对象之一,由于构造方法的存在,类也是可调用的。像list()、tuple()、dict()、set()这样的工厂函数实际上都是调用了类的构造方法。另外,任何包含__call__()方法的类的对象也是可调用的。

修饰器(decorator)是函数嵌套定义的另一个重要应用。修饰器本质上也是一个函数,只不过这个函数接收其他函数作为参数并对其进行一定的改造之后使用新函数替换原来的函数。

Python面向对象程序设计中的静态方法、类方法、属性等也都是通过修饰器实现的。

##下面的代码使用函数的嵌套定义实现了可调用对象的定义:

def linear(a, b):
    def result(x):              #在Python中,函数是可以嵌套定义的
        return a * x + b
    return result               #返回可被调用的函数

## 下面的代码演示了可调用对象类的定义:

class linear:
    def __init__(self, a, b):
        self.a, self.b = a, b
    def __call__(self, x):         #这里是关键
        return self.a * x + self.b

## 使用上面的嵌套函数和类这两种方式中任何一个,都可以通过以下的方式来定义一个可调用对象:

taxes = linear(0.3, 2)

## 然后通过下面的方式来调用该对象:

taxes(5)
3.5
def before(func):                       #定义修饰器
    def wrapper(*args, **kwargs):
        print('Before function called.')
        return func(*args, **kwargs)
    return wrapper

def after(func):                        #定义修饰器
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        print('After function called.')
        return result
    return wrapper

@before
@after
def test():                             #同时使用两个修饰器改造函数
    print(3)
##调用被修饰的函数
test()
Before function called.
3
After function called.

生成器函数

包含yield语句的函数可以用来创建生成器对象,这样的函数也称生成器函数。

yield语句与return语句的作用相似,都是用来从函数中返回值。与return语句不同的是,return语句一旦执行会立刻结束函数的运行,而每次执行到yield语句并返回一个值之后会暂停或挂起后面代码的执行,下次通过生成器对象的__next__()方法、内置函数next()、for循环遍历生成器对象元素或其他方式显式“索要”数据时恢复执行。

生成器具有惰性求值的特点,适合大数据处理。

def f():
    a, b = 1, 1            #序列解包,同时为多个元素赋值
    while True:
        yield a            #暂停执行,需要时再产生一个新元素
        a, b = b, a+b      #序列解包,继续生成新元素
a = f()                #创建生成器对象
for i in range(10):    #斐波那契数列中前10个元素
    print(a.__next__(), end=' ')
1 1 2 3 5 8 13 21 34 55 
for i in range(10):    #再从斐波那契数列中取10个元素
    print(a.__next__(), end=' ')
89 144 233 377 610 987 1597 2584 4181 6765 
def f():
    yield from 'abcdefg'        #使用yield表达式创建生成器

x = f()
next(x)
next(x)
for item in x:              #输出x中的剩余元素
    print(item, end=' ')
c d e f g 

lambda函数

lambda表达式可以用来声明匿名函数,也就是没有函数名字的临时使用的小函数,尤其适合需要一个函数作为另一个函数参数的场合。也可以定义具名函数。

lambda表达式只可以包含一个表达式,该表达式的计算结果可以看作是函数的返回值,不允许包含复合语句,但在表达式中可以调用其他函数

lambda函数可以给变量赋予默认值,

data = list(range(20))           #创建列表
data.sort(key=lambda x: x);print(data)      #和不指定规则效果一样
data.sort(key=lambda x: len(str(x)))     #按转换成字符串以后的长度排序
print(data)
data.sort(key=lambda x: len(str(x)), reverse=True)#降序排序
print(data)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
[10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
import random
x = [[random.randint(1,10) for j in range(5)] for i in range(5)]
                                    #使用列表推导式创建列表
                                    #包含5个子列表的列表
                                    #每个子列表中包含5个1到10之间的随机数
for item in x:
    print(item)
    
y = sorted(x, key=lambda item: (item[1], item[4]))
                               #按子列表中第2个元素升序、第5个元素升序排序
for item in y:
    print(item)	
[3, 1, 6, 9, 4]
[3, 8, 5, 10, 4]
[8, 4, 5, 8, 2]
[10, 9, 4, 2, 8]
[5, 6, 7, 2, 9]
[3, 1, 6, 9, 4]
[8, 4, 5, 8, 2]
[5, 6, 7, 2, 9]
[3, 8, 5, 10, 4]
[10, 9, 4, 2, 8]
上一页
下一页