Python基础入门_4函数

原文连接:mp.weixin.qq.com/s/eszIPhEQD…javascript

Python 基础入门前三篇:php

第四篇内容,此次介绍下函数的基本用法,包括函数的定义、参数的类型、匿名函数、变量做用域以及从模块导入函数的方法,目录以下所示:html


4. 函数

定义:函数是组织好的,可重复使用,用于实现单一或者相关联功能的代码段。java

在 Python 中既有内建函数,好比 print()sum() ,也能够用户自定义函数。python

4.1 定义函数

自定义一个函数须要遵照一些规则:c++

  • 函数代码块必须以 def 关键词开头,而后是函数标识符名称(函数名)和圆括号 ()
  • 圆括号内部用于定义参数,而且传入参数和自变量也是存放在圆括号内;
  • 函数的第一行语句能够选择性地使用文档字符串—用于存放函数说明。
  • 函数内容以冒号起始,而且缩进。
  • return [表达式] 结束函数,选择性地返回一个值给调用方。不带表达式的 return 语句至关于返回 None

一个函数的通常格式以下:git

def 函数名(参数列表):
	函数体
复制代码

默认状况下,参数值和参数名称是按照函数声明中定义的顺序匹配的github

简单的定义和调用函数的例子以下所示:算法

def hello():
    print("Hello, world!")
# 计算面积的函数
def area(width, height):
    return width * height

hello()
width = 2
height = 3
print('width={}, height={}, area={}'.format(width, height, area(width, height)))
复制代码

输出结果:express

Hello, world!
width=2, height=3, area=6
复制代码

上述例子定义了两个函数,第一个是没有参数的 hello(), 而第二个函数定义了两个参数。

4.2 参数传递

在 python 中,类型属于对象,变量是没有类型的

a = [1, 2, 3]
a = "abc"
复制代码

上述代码中,[1,2,3] 是 List 类型,"abc" 是 String 类型,但变量 a 是没有类型的,它仅仅是一个对象的引用(一个指针),能够指向 List 类型,也能够指向 String 类型。

可更改(mutable)与不可更改(immutable)对象

python 中,strings, tuples, numbers 是不可更改对象,而 list, dict 是可修改的对象。

  • 不可变类型:上述例子中 a 先赋值为 5,而后赋值为 10,其实是生成一个新对象,赋值为 10,而后让 a 指向它,而且抛弃了 5,并不是改变了 a 的数值;
  • 可变类型:对于 list 类型,变量 la=[1,2,3],而后令 la[2]=5 ,此时并无改变变量 la,仅仅改变了其内部的数值。

在以前的第二节介绍变量类型中,介绍了如何判断数据类型是否可变,介绍了两种方法:

  • id()
  • hash()

这里用 id() 的方法来作一个简单的例子,代码以下:

# 判断类型是否可变
a = 5
print('a id:{}, val={}'.format(id(a), a))
a = 3
print('a id:{}, val={}'.format(id(a), a))

la = [1, 2, 3]
print('la id:{}, val={}'.format(id(la), la))
la[2] = 5
print('la id:{}, val={}'.format(id(la), la))
复制代码

输出结果,能够发现变量 aid 是发生了变化,而列表变量 laid 没有变化,这证实了 a 的类型 int 是不可变的,而 list 是可变类型。

a id:1831338608, val=5
a id:1831338544, val=3

la id:1805167229448, val=[1, 2, 3]
la id:1805167229448, val=[1, 2, 5]
复制代码

而后在 Python 中进行函数参数传递的时候,根据传递的变量是否可变,也须要分开讨论:

  • 不可变类型:相似 c++值传递,如 整数、字符串、元组。如 fun(a),传递的只是 a 的值,没有影响 a 对象自己。好比在 fun(a)内部修改 a 的值,只是修改另外一个复制的对象,不会影响 a 自己。
  • 可变类型:相似 c++ 的**引用传递,**如 列表,字典。如 fun(la),则是将 la 真正的传过去,修改后 fun 外部的 la 也会受影响。

固然了,Python 中一切都是对象,这里应该说是传递可变对象和不可变对象,而不是引用传递和值传递,但必须注意应该慎重选择传递可变对象的参数,下面会分别给出传递两种对象的例子。

首先是传递不可变对象的实例:

# 传递不可变对象的实例
def change_int(a):
    a = 10

b = 2
print('origin b=', b)
change_int(b)
print('after call function change_int(), b=', b)
复制代码

输出结果,传递的变量 b 并无发生改变。

origin b= 2
after call function change_int(), b= 2
复制代码

接着,传递可变对象的例子:

# 传递可变对象的实例
def chang_list(la):
    """ 修改传入的列表参数 :param la: :return: """
    la.append([2, 3])
    print('函数内部: ', la)
    return


la = [10, 30]
print('调用函数前, la=', la)
chang_list(la)
print('函数外取值, la=', la)
复制代码

输出结果,能够看到在函数内部修改列表后,也会影响在函数外部的变量的数值。

调用函数前, la= [10, 30]
函数内部:  [10, 30, [2, 3]]
函数外取值, la= [10, 30, [2, 3]]
复制代码

固然,这里若是依然但愿传递列表给函数,但又不但愿修改列表原本的数值,能够采用传递列表的副本给函数,这样函数的修改只会影响副本而不会影响原件,最简单实现就是切片 [:] ,例子以下:

# 不修改 lb 数值的办法,传递副本
lb = [13, 21]
print('调用函数前, lb=', lb)
chang_list(lb[:])
print('传递 la 的副本给函数 change_list, lb=', lb)
复制代码

输出结果:

调用函数前, lb= [13, 21]
函数内部:  [13, 21, [2, 3]]
传递 lb 的副本给函数 change_list, lb= [13, 21]
复制代码

4.3 参数

参数的类型主要分为如下四种类型:

  • 位置参数
  • 默认参数
  • 可变参数
  • 关键字参数
  • 命名关键字参数
位置参数

**位置参数须以正确的顺序传入函数。调用时的数量必须和声明时的同样。**其定义以下,arg 就是位置参数,docstring 是函数的说明,通常说明函数做用,每一个参数的含义和类型,返回类型等;statement 表示函数内容。

def function_name(arg):
	"""docstring"""
	statement
复制代码

下面是一个例子,包括一个正确调用例子,和两个错误示例

# 位置参数
def print_str(str1, n):
    """ 打印输入的字符串 n 次 :param str1: 打印的字符串内容 :param n: 打印的次数 :return: """
    for i in range(n):
        print(str1)


strs = 'python '
n = 3
# 正确调用
print_str(strs, n)
# 错误例子1
print_str()
# 错误例子2
print_str(n, strs)
复制代码

对于正确例子,输出:

python python python 
复制代码

错误例子1--print_str(),也就是没有传入任何参数,返回错误:

TypeError: print_str() missing 2 required positional arguments: 'str1' and 'n'
复制代码

错误例子1--print_str(n, strs),也就是传递参数顺序错误,返回错误:

TypeError: 'str' object cannot be interpreted as an integer
复制代码
默认参数

默认参数定义以下,其中 arg2 就是表示默认参数,它是在定义函数的时候事先赋予一个默认数值,调用函数的时候能够不须要传值给默认参数。

def function_name(arg1, arg2=v):
	"""docstring"""
	statement
复制代码

代码例子以下:

# 默认参数
def print_info(name, age=18):
    ''' 打印信息 :param name: :param age: :return: '''
    print('name: ', name)
    print('age: ', age)

print_info('jack')
print_info('robin', age=30)
复制代码

输出结果:

name:  jack
age:  18
name:  robin
age:  30
复制代码

注意:默认参数必须放在位置参数的后面,不然程序会报错。

可变参数

可变参数定义以下,其中 arg3 就是表示可变参数,顾名思义就是输入参数的数量能够是从 0 到任意多个,它们会自动组装为元组

def function_name(arg1, arg2=v, *arg3):
	"""docstring"""
	statement
复制代码

这里是一个使用可变参数的实例,代码以下:

# 可变参数
def print_info2(name, age=18, height=178, *args):
    ''' 打印信息函数2 :param name: :param age: :param args: :return: '''
    print('name: ', name)
    print('age: ', age)
    print('height: ', height)
    print(args)
    for language in args:
        print('language: ', language)

print_info2('robin', 20, 180, 'c', 'javascript')
languages = ('python', 'java', 'c++', 'go', 'php')
print_info2('jack', 30, 175, *languages)
复制代码

输出结果:

name:  robin
age:  20
height:  180
('c', 'javascript')
language:  c
language:  javascript

name:  jack
age:  30
height:  175
('python', 'java', 'c++', 'go', 'php')
language:  python
language:  java
language:  c++
language:  go
language:  php
复制代码

这里须要注意几点:

  1. 首先若是要使用可变参数,那么传递参数的时候,默认参数应该如上述例子传递,不能如print_info2('robin', age=20, height=180, 'c', 'javascript'),这种带有参数名字的传递是会出错的;
  2. 可变参数有两种形式传递:
  • 直接传入函数,如上述例子第一种形式,即 print_info2('robin', 20, 180, 'c', 'javascript');
  • 先组装为列表或者元组,再传入,而且必须带有 * ,即相似 func(*[1, 2,3]) 或者 func(*(1,2,3)),之因此必须带 * ,是由于若是没有带这个,传入的可变参数会多嵌套一层元组,即 (1,2,3) 变为 ((1,2,3))
关键字参数

关键字参数定义以下,其中 arg4 就是表示关键字参数,关键字参数其实和可变参数相似,也是能够传入 0 个到任意多个,不一样的是会自动组装为一个字典,而且是参数前 ** 符号。

def function_name(arg1, arg2=v, *arg3, **arg4):
	"""docstring"""
	statement
复制代码

一个实例以下:

def print_info3(name, age=18, height=178, *args, **kwargs):
    ''' 打印信息函数3,带有关键字参数 :param name: :param age: :param height: :param args: :param kwargs: :return: '''
    print('name: ', name)
    print('age: ', age)
    print('height: ', height)

    for language in args:
        print('language: ', language)
    print('keyword: ', kwargs)


# 不传入关键字参数的状况
print_info3('robin', 20, 180, 'c', 'javascript')
复制代码

输出结果以下:

name:  robin
age:  20
height:  180
language:  c
language:  javascript
keyword:  {}
复制代码

传入任意数量关键字参数的状况:

# 传入任意关键字参数
print_info3('robin', 20, 180, 'c', 'javascript', birth='2000/02/02')
print_info3('robin', 20, 180, 'c', 'javascript', birth='2000/02/02', weight=125)
复制代码

结果以下:

name:  robin
age:  20
height:  180
language:  c
language:  javascript
keyword:  {'birth': '2000/02/02'}

name:  robin
age:  20
height:  180
language:  c
language:  javascript
keyword:  {'birth': '2000/02/02', 'weight': 125}
复制代码

第二种传递关键字参数方法--字典:

# 用字典传入关键字参数
keys = {'birth': '2000/02/02', 'weight': 125, 'province': 'Beijing'}
print_info3('robin', 20, 180, 'c', 'javascript', **keys)
复制代码

输出结果:

name:  robin
age:  20
height:  180
language:  c
language:  javascript
keyword:  {'birth': '2000/02/02', 'province': 'Beijing', 'weight': 125}
复制代码

因此,一样和可变参数类似,也是两种传递方式:

  • 直接传入,例如 func(birth='2012')
  • 先将参数组装为一个字典,再传入函数中,如 func(**{'birth': '2000/02/02', 'weight': 125, 'province': 'Beijing'})
命名关键字参数

命名关键字参数定义以下,其中 *, nkw 表示的就是命名关键字参数,它是用户想要输入的关键字参数名称,定义方式就是在 nkw 前面添加 *, ,这个参数的做用主要是限制调用者能够传递的参数名

def function_name(arg1, arg2=v, *arg3, *,nkw, **arg4):
	"""docstring"""
	statement
复制代码

一个实例以下:

# 命名关键字参数
def print_info4(name, age=18, height=178, *, weight, **kwargs):
    ''' 打印信息函数4,加入命名关键字参数 :param name: :param age: :param height: :param weight: :param kwargs: :return: '''
    print('name: ', name)
    print('age: ', age)
    print('height: ', height)

    print('keyword: ', kwargs)
    print('weight: ', weight)

print_info4('robin', 20, 180, birth='2000/02/02', weight=125)
复制代码

输出结果以下:

name:  robin
age:  20
height:  180
keyword:  {'birth': '2000/02/02'}
weight:  125
复制代码

这里须要注意:

  • 加入命名关键字参数后,就不能加入可变参数了
  • 对于命名关键字参数,传递时候必须指明该关键字参数名字,不然可能就被当作其余的参数。
参数组合

经过上述的介绍,Python 的函数参数分为 5 种,位置参数、默认参数、可变参数、关键字参数以及命名关键字参数,而介绍命名关键字参数的时候,能够知道它和可变参数是互斥的,是不能同时出现的,所以这些参数能够支持如下两种组合及其子集组合:

  • 位置参数、默认参数、可变参数和关键字参数
  • 位置参数、默认参数、关键字参数以及命名关键字参数

通常状况下,其实只须要位置参数和默认参数便可,一般并不须要过多的组合参数,不然函数会很难懂。

4.4 匿名函数

上述介绍的函数都属于同一种函数,即用 def 关键字开头的正规函数,Python 还有另外一种类型的函数,用 lambda 关键字开头的匿名函数

它的定义以下,首先是关键字 lambda ,接着是函数参数 argument_list,其参数类型和正规函数可用的同样,位置参数、默认参数、关键字参数等,而后是冒号 :,最后是函数表达式 expression ,也就是函数实现的功能部分。

lambda argument_list: expression
复制代码

一个实例以下:

# 匿名函数
sum = lambda x, y: x + y

print('sum(1,3)=', sum(1, 3))
复制代码

输出结果:

sum(1,3)= 4
复制代码

4.5 变量做用域

Python 中变量是有做用域的,它决定了哪部分程序能够访问哪一个特定的变量,做用域也至关因而变量的访问权限,一共有四种做用域,分别是:

  • L(Local):局部做用域
  • E(Enclosing):闭包函数外的函数中
  • G(Global):全局做用域
  • B(Built-in):内置做用域(内置函数所在模块的范围)

寻找的规则是 L->E->G->B ,也就是优先在局部寻找,而后是局部外的局部(好比闭包),接着再去全局,最后才是内置中寻找。

下面是简单介绍这几个做用域的例子,除内置做用域:

g_count = 0  # 全局做用域
def outer():
    o_count = 1  # 闭包函数外的函数中
    # 闭包函数 inner()
    def inner():
        i_count = 2  # 局部做用域
复制代码

内置做用域是经过一个名为 builtin 的标准模块来实现的,但这个变量名自己没有放入内置做用域,须要导入这个文件才可使用它,使用代码以下,能够查看预约义了哪些变量:

import builtins
print(dir(builtins))
复制代码

输出的预约义变量以下:

['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BlockingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning', 'ChildProcessError', 'ConnectionAbortedError', 'ConnectionError', 'ConnectionRefusedError', 'ConnectionResetError', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False', 'FileExistsError', 'FileNotFoundError', 'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning', 'IndentationError', 'IndexError', 'InterruptedError', 'IsADirectoryError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'NameError', 'None', 'NotADirectoryError', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 'PendingDeprecationWarning', 'PermissionError', 'ProcessLookupError', 'RecursionError', 'ReferenceError', 'ResourceWarning', 'RuntimeError', 'RuntimeWarning', 'StopAsyncIteration', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'TimeoutError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', 'WindowsError', 'ZeroDivisionError', '__build_class__', '__debug__', '__doc__', '__import__', '__loader__', '__name__', '__package__', '__spec__', 'abs', 'all', 'any', 'ascii', 'bin', 'bool', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod', 'compile', 'complex', 'copyright', 'credits', 'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'exec', 'exit', 'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int', 'isinstance', 'issubclass', 'iter', 'len', 'license', 'list', 'locals', 'map', 'max', 'memoryview', 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property', 'quit', 'range', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars', 'zip']
复制代码

注意:只有模块(module),类(class)以及函数(def, lambda)才会引入新的做用域,其余代码块(好比 if/elif/else、try/except、for/while)是不会引入新的做用域,在这些代码块内定义的变量,外部也可使用。

下面是两个例子,一个在函数中新定义变量,另外一个在 if 语句定义的变量,在外部分别调用的结果:

g_count = 0  # 全局做用域
def outer():
    o_count = 1  # 闭包函数外的函数中
    # 闭包函数 inner()
    def inner():
        i_count = 2  # 局部做用域
        
if 1:
    sa = 2
else:
    sa = 3
print('sa=', sa)
print('o_count=', o_count)
复制代码

输出结果,对于在 if 语句定义的变量 sa 是能够正常访问的,可是函数中定义的变量 o_count 会报命名错误 NameError ,提示该变量没有定义。

sa= 2
NameError: name 'o_count' is not defined
复制代码
全局变量和局部变量

全局变量和局部变量的区别主要在于定义的位置是在函数内部仍是外部,也就是在函数内部定义的是局部变量,在函数外部定义的是全局变量。

局部变量只能在其被声明的函数内部访问,而全局变量能够在整个程序范围内访问。调用函数时,全部在函数内声明的变量名称都将被加入到做用域中。以下实例:

# 局部变量和全局变量
total = 3  # 全局变量

def sum_nums(arg1, arg2):
    total = arg1 + arg2  # total在这里是局部变量.
    print("函数内是局部变量 : ", total)
    return total


# 调用 sum_nums 函数
sum_nums(10, 20)
print("函数外是全局变量 : ", total)
复制代码

输出结果:

函数内是局部变量 :  30
函数外是全局变量 :  3
复制代码
global 和 nonlocal 关键字

若是在内部做用域想修改外部做用域的变量,好比函数内部修改一个全局变量,那就须要用到关键字 globalnonlocal

这是一个修改全局变量的例子:

# 函数内部修改全局变量
a = 1

def print_a():
    global a
    print('全局变量 a=', a)
    a = 3
    print('修改全局变量 a=', a)


print_a()
print('调用函数 print_a() 后, a=', a)
复制代码

输出结果:

全局变量 a= 1
修改全局变量 a= 3
调用函数 print_a() 后, a= 3
复制代码

而若是须要修改嵌套做用域,也就是闭包做用域,外部并不是全局做用域,则须要用关键字 nonlocal ,例子以下:

# 修改闭包做用域中的变量
def outer():
    num = 10

    def inner():
        nonlocal num  # nonlocal关键字声明
        num = 100
        print('闭包函数中 num=', num)

    inner()
    print('调用函数 inner() 后, num=',num)


outer()
复制代码

输出结果:

闭包函数中 num= 100
调用函数 inner() 后, num= 100
复制代码

4.6 从模块中导入函数

通常咱们会须要导入一些标准库的函数,好比 ossys ,也有时候是本身写好的一个代码文件,须要在另外一个代码文件中导入使用,导入的方式有如下几种形式:

# 导入整个模块
import module_name
# 而后调用特定函数
module_name.func1()

# 导入特定函数
from module_name import func1, func2

# 采用 as 给函数或者模块指定别名
import module_name as mn
from module_name import func1 as f1

# * 表示导入模块中全部函数
from module_name import *
复制代码

上述几种形式都是按照实际需求来使用,但最后一种方式并不推荐,缘由主要是 Python 中可能存在不少相同名称的变量和函数,这种方式可能会覆盖相同名称的变量和函数。最好的导入方式仍是导入特定的函数,或者就是导入整个模块,而后用句点表示法调用函数,即 module_name.func1()


参考


小结

本文主要是简单介绍了 Python 函数的基础内容,正规函数和匿名函数的定义和用法,参数的5种类型和使用,固然函数还有更多内容,好比高阶函数 map, filter, reduce,还有本文中间提过的闭包函数等,这部分计划放到后续进阶中介绍。

此外,本文的代码都上传到个人 github 上了:

github.com/ccc013/Pyth…

欢迎关注个人微信公众号--算法猿的成长,或者扫描下方的二维码,你们一块儿交流,学习和进步!

往期精彩推荐

机器学习系列
Github项目 & 资源教程推荐
相关文章
相关标签/搜索