14-3 函数基础

一、函数是什么

函数是指将一组语句的集合通过一个名字(函数名)封装起来,要想执行这个函数,只需调用其函数名即可。Python提供了许多内建函数,比如print()。也可以自己创建函数,这被叫做用户自定义函数。

函数一词来源于数学,但编程中的「函数」概念,与数学中的函数是有很大不同的。编程中的函数在英文中也有很多不同的叫法。在BASIC中叫做subroutine(子过程或子程序),在Pascal中叫做procedure(过程)和function,在C中只有function,在Java里面叫做method。

意义:

  1. 代码重用
  2. 保持一致性
  3. 可扩展性

 

二、函数的创建

1. 格式

Python 定义函数使用 def 关键字,一般格式如下:

def 函数名(参数列表):
    函数体

# 以下是一个实例:
def hello():
    print('hello')
hello()  # 调用

定义函数时,函数体是不执行的,只是一个声明(会读入内存)。在调用函数时,函数体才执行。

def f(arg, li=[]):
    li.append(arg)
    return li

n1 = f(1)
print(n1)       # [1]
n2 = f(2,[])
print(n2)       # [2]
n3 = f(3)
print(n3)       # [1, 3]

# 解释器从上到下解释代码,解释到函数的定义时就创建了一个列表,且li指向此列表的内存地址
# f(2,[]) 是传入了一个新列表
# f(3) 还是用了解释函数时原列表,因为函数定义的过程并没有被重新做,而是已经在内存中了
补充:面试题

2. 函数名的命名规则与变量命名规则相同

函数名其实就是个变量名,只是它里面存的是函数代码而已。

x = abs                   # 给变量 x 赋值
print(x(-1))              # 屏幕输出:1
print(id(abs), id(x))     # 屏幕输出:1608560 1608560

  

3. 形参和实参

形参:形式参数,不是实际存在,是虚拟变量。在定义函数和函数体的时候使用形参,目的是在函数调用时接收实参(实参个数,类型应与实参一一对应。

实参:实际参数,调用函数时传给函数的参数,可以是常量,变量,表达式,函数,传给形参。

区别:形参是虚拟的,不占用内存空间,形参变量只有在被调用时才分配内存单元,实参是一个变量,占用内存空间,数据传送单向,实参传给形参,不能形参传给实参。

英文词义:
parameter  可数名词,词义是限制因素、决定因素。
argument   这里是可数名词用法,词义是论据、论点、依据。

在编程中:
1、用法规范
   While defining method, variables passed in the method are called parameters.
   当定义方法时,传递到方法中的变量称为参数.
   While using those methods, values passed to those variables are called arguments.
   当调用方法时,传给变量的值称为引数.
2、简略描述为:parameter=形参(formal parameter), argument=实参(actual parameter)
3. 在不很严格的情况下,现在二者可以混用,一般用argument,而parameter则比较少用。
parameter与argument用法区别

 

三、函数的参数

(一)参数的类型

1、定长参数

即在函数定义时进行了声明,在调用函数时必须传入的参数。相当于在函数参数输入处(即接口上)已经占好了坑,调用函数时必须要输入对应的实参。

(1)两种实参传入方式

    • 基于相对位置的输入:须按声明的形参顺序,依次传入对应的实参,且传入的数量必须和声明时的一样。
    • 基于参数名的输入:允许传入实参的顺序与声明时不一致,因为 Python 解释器能够用参数名匹配到参数值。
def f(name, age, job):
    print('Name:%s Age:%d, Job:%s' %(name, age, job))

f('alex', 18, 'it')                    # 基于相对位置的输入
f(age=18, name='alex', job='it')       # 基于参数名的输入

(2) 默认参数

调用函数时,缺省参数的值如果没有传入,则被认为是默认值。默认参数必须跟在非默认参数之后,否则报错。

def f(name, age=18, job='it'):
    print('Name:%s Age:%d, Job:%s' %(name, age, job))

f('alex', 18)                          # 输出 Name:alex Age:18, Job:it

如果默认参数是一个可修改的容器比如一个列表、集合或者字典,可以使用None作为默认值,就像下面这样:

 

# Using a list as a default value
def spam(a, b=None):
    if b is None:
        b = []
    ...

 

2、 不定长参数

(1)tuple型不定长参数(位置参数):形参(常用args,当然也可以换别的)前加*,允许你传入0个或任意个没有名字的实参,这些实参被自动组装为一个tuple,然后传入函数。

def f1(*args):
    print (args)

f1(11, 22, 'jack')                     # 输出为:(11, 22, 'jack')

li = [11, 22, 'jack']
f1(li)                                 # 输出为:([11, 22, 'Jack'],)
f1(*li)                                # 输出为:(11, 22, 'Jack')

(2)dict型不定长参数(关键字参数):形参(常用kwargs,当然也可以换别的)前加**,允许你传入0个或任意个含关键字的实参,这些关键字实参被自动组装为一个dict,然后传入函数。 

def f1(**kwargs):
    print(kwargs)

f1(name='Alex', age=9)                 # 输出为:{'name': 'Alex', 'age': 9}

dic = {'name': 'Jack', 'age': 10 ,'city': 'beijing'}
f1(i=dic)                              # 输出为:{'i': {'name': 'Jack', 'age': 10, 'city': 'beijing'}}
f1(**dic)                              # 输出为:{'name': 'Jack', 'age': 10, 'city': 'beijing'}

(3)tuple型和dict型不定长参数组合——“万能参数”

def f1(*args, **kwargs):               
    print(args, kwargs)

f1(11, 22, 33,  k1 = 'v1', k2 = 'v2')  # 输出为 (11, 22, 33) {'k1': 'v1', 'k2': 'v2'}

 3、各种类型参数的组合使用

def f(name,age=18,*args,**kwargs):       # 不同类型参数的混用
    print('Name:%s' % name)              # 输出为 Name:alex
    print('age:%d' % age)                # 输出为 age:18
    print('args:',args)                  # 输出为 ()
    print('kwargs:',kwargs)              # 输出为 kwargs: {'hobby': 'girl', 'nationality': 'Chinese', 'ability': 'Python'}

f('alex',hobby='girl',nationality='Chinese',ability='Python')

 一个*参数只能出现在函数定义中最后一个位置参数后面,而 **参数只能出现在最后一个参数。有种例外,就是—— 强制关键字参数(Keyword-only arguments)。

强制关键字参数放到某个*参数或者单个*后面:

def recv(maxsize, *, block):           # 放在单个*后面
    pass

recv(1024, True)                       # TypeError
recv(1024, block=True)                 # Ok         读代码是看到block=True时,比只看到True,更容易清楚其意义。这也就是强制关键字参数的意义所在。
def mininum(*values, clip=None):       # 放在*参数之后面
    m = min(values)
    if clip is not None:
        m = clip if clip > m else m
    return m

mininum(1, 5, 2, -5, 10)                # Returns -5
mininum(1, 5, 2, -5, 10, clip=0)        # Returns 0

(二)其他

1、给函数参数添加元信息

即给函数的参数写注解,提示程序员应该怎样正确使用这一函数。

def add(x:int, y:int) -> int:  # 输入的x、y及返回的值都是int类型
    return x + y

# 函数被调用时完全等价于:
def add(x, y):
    return x + y

 

 

四、函数的返回值

要想获取函数的执行结果,就可以用return语句把结果返回

1. 函数在执行过程中只要遇到return语句,就会停止执行并返回结果,so 也可以理解为2.return 语句代表着函数的结束

3. 如果未在函数中指定return,那这个函数的返回值为None 

4. return多个对象,解释器会把这多个对象组装成一个元组作为一个一个整体结果输出。

 

五、作用域

1. 作用域介绍

在Python中,只有模块(module),类(class)以及函数(def、lambda)才会引入新的作用域。

python中的作用域分4种情况:

L:local                  局部作用域,即函数中定义的变量;
E:enclosing              嵌套的父级函数的局部作用域,即包含此函数的上级函数的局部作用域,但不是全局的;当然,local和enclosing是相对的,enclosing变量相对上层来说也是local。
G:global                 全局变量,就是模块级别定义的变量;
B:built-in               系统固定模块里面的变量,比如int, bytearray等。

搜索变量的优先级顺序依次是:局部作用域>外层作用域>当前模块中的全局>python内置作用域,也就是LEGB。

x = int(2.9)              # built-in
g_count = 0               # global
def outer():
    o_count = 1           # enclosing
    def inner():
        i_count = 2       # local
        print(o_count)
    # print(i_count)      NameError: name 'i_count' is not defined
    inner()
outer()
# print(o_count)          NameError: name 'o_count' is not defined

 2. 作用域的修改

x=6
def f2():
    print(x)    # 报错 local variable 'x' referenced before assignment
    x=5
f2()

'''
报错的原因在于print(x)时,解释器会在局部作用域找x,发现在x=5处才定义的x,怎么能先去调用呢?所以报错
为什么能找到x=5?尽管函数还没执行到这一句,但是函数本身已经加载到内存了。

上例说明:局部作用域只能查询局部变量

'''

 当内部作用域想修改外部作用域的变量时怎么办?就要用到global和nonlocal关键字了

(1)global 关键字

当修改的变量是在全局作用域(global作用域)上的,就要使用global先声明一下,代码如下: 

x = 6
def f2():
    global x
    print(x)
    x = 5

f2()
print(x) 

(2)nonlocal 关键字

global关键字声明的变量必须在全局作用域上,不能嵌套作用域上,当要修改嵌套作用域(enclosing作用域,外层非全局作用域)中的变量时,用nonlocal关键字。

def outer():
    count = 10
    def inner():
        nonlocal count
        print(count)
        count = 20
    inner()
    print(count)
outer()
#print(count)    NameError: name 'count' is not defined

注意:对于一个变量,内部作用域变量先声明就会覆盖外部变量的声明,不声明可以直接使用作用域变量的值,但不能改。 

posted @ 2017-06-07 22:14  seaidler  阅读(201)  评论(0)    收藏  举报