—— Python基础教程之自定义函数
在Python中,函数是一种组织代码的方式,它允许你将代码块封装起来,并在需要的时候调用它。这样做不仅可以帮助你避免重复代码,还可以使代码更加模块化和易于维护
Python有许多内置函数,这些函数是随时可用的,不需要任何特殊的模块导入。这些内置函数提供了许多常见任务的便捷方法,如类型转换、数值计算、序列操作等。
合理使用内置函数函数可以帮助你简化代码,并且提高编程效率。每个内置函数都有其特定的用途,学会合理利用它们是成为一个高效Python程序员的关键。
Python自定义函数是用户根据自己的需求定义的函数。它们不同于Python的内置函数,如 print()、len() 等,自定义函数允许用户创建特定于其程序的操作。通过定义自己的函数,用户可以封装代码执行特定任务,使代码更加模块化,提高代码的重用性和可读性。
可通过使用 def 关键字来定义一个函数,然后指定一个函数名以及可选的参数列表。下面是示例以及具体步骤:
# 定义一个函数,greet 是一个函数名,name 是一个参数
def greet(name):
"""这是一个打招呼的函数"""
return f"Hello, {name}!"
# 调用函数
print(greet("Alice")) # 输出: Hello, Alice!
"""
创建一个简单的自定义函数的步骤:
1.使用 def 关键字开始函数定义。
2.指定函数名,遵循命名规则。
3.在圆括号中指定参数列表,参数之间用逗号分隔。
4.使用冒号标记函数体的开始。
5.缩进函数体内的代码。
6.使用 return 语句返回值(如果需要)。
"""
1.使用 def 关键字开始定义函数
2.紧跟在 def 后面的是函数的名称,然后是括号
3.括号内可以包含参数,参数之间用逗号分隔。参数是可选的;如果函数不需要参数,只需一个空的括号对
4.参数列表后面是一个冒号,表示函数体的开始
5.函数体是下一行开始,并且必须缩进
6.使用 return 语句来返回值。如果没有 return 语句,函数默认返回 None
7.函数也可以有多个参数,可以有默认值(这样它们就是可选的),并且可以返回多个值(使用元组或其他数据结构)
在Python中,callable() 函数用于检查一个对象是否可以被调用。一个对象如果定义了 call() 方法,或者是一个内置函数、内置方法、方法、类等,那么它就是可调用的。如果对象可以像函数一样被调用,callable() 函数就会返回 True,否则返回 False。
用法示例:
# 检查内置函数是否可调用
print(callable(print)) # 输出: True
# 检查普通函数是否可调用
def my_function():
pass
print(callable(my_function)) # 输出: True
# 检查类是否可调用
class MyClass:
def __call__(self):
pass
print(callable(MyClass)) # 输出: True
# 检查类实例是否可调用
my_class_instance = MyClass()
print(callable(my_class_instance)) # 输出: True
# 检查普通对象是否可调用
class MyNonCallableClass:
pass
my_non_callable_instance = MyNonCallableClass()
print(callable(my_non_callable_instance)) # 输出: False
a.检查可调用性:
callable() 允许你检查任何Python对象是否可以被调用,这对于动态执行和高级函数操作非常有用。
b.动态编程:
在编写需要处理不同类型对象的代码时,callable() 可以帮助你确定哪些对象可以作为函数被调用。
c.增强代码的健壮性:
在尝试调用对象之前检查其可调用性,可以避免运行时错误,使得代码更加健壮。
d.实现回调和高阶函数:
在设计接受函数作为参数的函数时,callable() 可以用来验证传入的参数是否满足调用的要求。
Python的文档字符串(docstrings)是一种特殊的字符串字面量,它出现在模块、函数、类或方法定义的首部,用来描述该模块、函数、类或方法的功能和用法。
文档字符串放在函数内第一行,并作为函数的一部分存储起来。
档字符串是多行字符串,使用三个引号(单引号 ‘’’ 或双引号 “”")来定义。
def add(a, b):
"""计算并返回两个数的和。
参数:
a (int): 第一个加数。
b (int): 第二个加数。
返回:
int: 两个数的和。
"""
return a + b
文档字符串应该包含以下内容:
1.简短的描述函数做什么
2.参数列表及其描述
3.返回值的描述
4.错误或异常的描述
5.以及其他关键信息
def add(a, b):
"""Add two numbers and return the result."""
return a + b
#使用内置函数help访问文档字符串
help(add)
def add(a, b):
"""Add two numbers and return the result."""
return a + b
#直接打印 __doc__ 属性
print(add.__doc__)
提供内置文档:
文档字符串作为代码的一部分,为函数、类、模块和方法提供了即时的文档。这使得开发者可以通过简单的命令(如 help() 函数或 .doc 属性)来获取有关对象的描述和使用说明。
提高代码可读性:
良好的文档字符串可以使其他开发者更容易理解代码的目的和用法,这对于团队协作和代码维护至关重要。
促进代码维护:
随着项目的发展和代码库的增长,文档字符串提供了关于代码如何使用和期望行为的宝贵信息,有助于新成员快速上手和现有成员进行维护。
自动生成文档:
使用工具如Sphinx可以从文档字符串自动生成项目文档,这样可以确保文档与代码同步更新。
编程最佳实践:
编写文档字符串是一种良好的编程习惯,它鼓励开发者在编写代码时就考虑到代码的文档化,有助于提升代码质量。
测试代码:
Python的某些测试框架,如doctest,可以直接从文档字符串中提取代码示例并执行测试,确保代码示例始终是正确的。
交互式开发:
在交互式环境中,如Python Shell或Jupyter Notebook,文档字符串使得用户能够快速获取函数或类的信息,而不必查阅外部文档。
形参(Formal Parameters),又称为形式参数,是在函数定义时列出的变量名。它们仅仅是占位符,代表了函数可以接受的数据。当函数被调用时,形参会被实际传入的值(实参)所替代。形参的主要作用是在函数内部作为变量使用,用于接收传递给函数的值。
形参的值在函数被调用时确定,它们只在函数内部有效,在函数外部是不可见的。这意味着,形参的变化不会影响到函数外部的同名变量。
实参(Actual Parameters),又称为实际参数,是在函数调用时传递给函数的具体值。实参的值会被用来替换函数定义中的形参。实参可以是常量、变量、表达式或者函数调用的结果等。
下面是一个简单的例子,说明了形参和实参的概念:
# 定义一个函数,x 和 y 是形参
def add(x, y):
return x + y
# 调用函数,1 和 2 是实参
result = add(1, 2)
print(result) # 输出: 3
'''
在这个例子中,函数 add 定义了两个形参:x 和 y。
当函数被调用 add(1, 2) 时,数字 1 和 2 是实参,
它们的值被传递给了形参 x 和 y,
并在函数内部用于计算和返回结果。
'''
实参和形参的个数通常需要相匹配,除非函数定义时使用了默认参数、可变参数列表或关键字参数等高级特性。
关键字参数(Keyword Arguments)允许你在调用函数时指定参数名,这样你可以不用关心参数的顺序,只需要按照参数名来传递参数值。
def greet(name, message="Hello"):
return f"{message}, {name}!"
# 使用关键字参数调用函数
print(greet(name="Alice", message="Good morning"))
# 输出: Good morning, Alice!
print(greet(message="Good evening", name="Bob"))
# 输出: Good evening, Bob!
# 使用位置参数调用函数
print(greet("Alice"))
# 输出: Hello, Alice!
# 使用混合参数调用函数(先位置参数,后关键字参数)
print(greet("Alice", message="Good afternoon"))
# 输出: Good afternoon, Alice!
关键字参数的优点:
a.易读性:函数调用时明确指出了每个参数的意义;
b.灵活性:你可以只给出必须的参数,并且以任何顺序指定它们;
c.默认值:你可以为参数设置默认值,使得它们在不被显式提供时使用默认值;
收集参数(也称为可变参数)允许你定义一个函数,该函数能够接受任意数量的位置参数或关键字参数
通过在参数名前使用星号 *(对于位置参数)或双星号 **(对于关键字参数)来实现的。
当定义一个函数时,如果你在参数名前加上一个星号 *,这个参数会收集额外的位置参数,这些参数在函数体中被封装成一个元组。
def print_args(*args):
for arg in args:
print(arg)
# 调用函数
print_args(1, 'two', 3, 'four')
'''
print_args 函数可以接受任意数量的位置参数。
args 是一个元组,包含所有传递给函数的位置参数
'''
在参数名前加上两个星号 **,这个参数会收集所有额外的关键字参数,这些参数在函数体中被封装成一个字典
def print_kwargs(**kwargs):
for key, value in kwargs.items():
print(f"{key}: {value}")
# 调用函数
print_kwargs(key1='value1', key2='value2')
'''
print_kwargs 函数可以接受任意数量的关键字参数。
kwargs 是一个字典,包含所有传递给函数的关键字参数
'''
def print_all(*args, **kwargs):
for arg in args:
print(arg)
for key, value in kwargs.items():
print(f"{key}: {value}")
# 调用函数
print_all(1, 'two', key1='value1', key2='value2')
指将序列(例如列表或元组)或字典中的元素分配给函数的参数
通过在序列前面使用一个星号 * 或在字典前面使用两个星号 ** 来实现的。
如果你有一个列表或元组,你可以在调用函数时使用一个星号 * 来将其元素作为独立的参数传递给函数
def add(x, y):
return x + y
numbers = [1, 2]
# 使用分配参数调用函数
result = add(*numbers)
print(result) # 输出: 3
'''
在这个例子中,numbers 列表中的元素被分配给了 add 函数的参数 x 和 y。
'''
如果你有一个字典,你可以在调用函数时使用两个星号 ** 来将其键值对作为关键字参数传递给函数
def greet(name, message):
return f"{message}, {name}!"
person = {'name': 'Alice', 'message': 'Hello'}
# 使用分配参数调用函数
greeting = greet(**person)
print(greeting) # 输出: Hello, Alice!
'''
person 字典中的键值对被分配给了 greet 函数的参数 name 和 message。
'''
def print_details(age, name, job):
print(f"Name: {name}, Age: {age}, Job: {job}")
details = {'name': 'Bob', 'job': 'Developer'}
age = [25]
# 使用分配参数调用函数
print_details(*age, **details)
'''
age 列表和 details 字典的内容被分配给了 print_details 函数的相应参数
'''
函数式编程(Functional Programming, FP)是一种编程范式,它将计算视为数学函数的求值,并避免使用程序状态以及易变对象。
不可变性:
在函数式编程中,数据是不可变的。一旦数据被创建,就不能被修改。
函数是一等公民:
函数可以作为参数传递给其他函数,可以作为返回值,也可以赋值给变量。
纯函数:
函数的输出值仅依赖于其输入参数,且不产生副作用(如修改全局变量或输入参数)。
map函数接受一个函数和一个可迭代对象作为参数,然后将函数应用于可迭代对象的每个元素。
def square(x):
return x * x
numbers = [1, 2, 3, 4, 5]
squared = map(square, numbers)
print(list(squared)) # 输出: [1, 4, 9, 16, 25]
filter函数接受一个函数和一个可迭代对象作为参数。这个函数应用于可迭代对象的每个元素,返回一个由使函数返回值为True的元素组成的迭代器
def is_even(x):
return x % 2 == 0
numbers = [1, 2, 3, 4, 5]
even_numbers = filter(is_even, numbers)
print(list(even_numbers)) # 输出: [2, 4]
reduce函数位于functools模块中,它接受一个函数和一个可迭代对象作为参数。这个函数会累积地将函数应用于其所有元素,从而将可迭代对象减少为单一的值。
from functools import reduce
def add(x, y):
return x + y
numbers = [1, 2, 3, 4, 5]
result = reduce(add, numbers)
print(result) # 输出: 15
列表推导式和生成器表达式提供了一种更为简洁和可读性更强的方式来创建列表或生成器,这也是函数式编程的一种表现。
# 列表推导式
squared = [x*x for x in numbers]
# 生成器表达式
squared_gen = (x*x for x in numbers)
在一个函数体内,可以调用其他函数,如果在函数体内调用了该函数本身,这个函数就称为递归函数
极限条件:(针对最小的问题)满足这种条件时函数将直接返回一个值
递归条件:包含一个或多个调用,这些调用旨在解决问题的一部分
递归最核心的思想是:每一次递归,整体问题都要比原来减小,并且递归到一定层次时,要能直接给出结果!
最大递归深度:
Python有一个最大递归深度的限制(默认值通常是1000),超过这个限制将导致RecursionError异常。如果确实需要更深的递归,可以使用sys.setrecursionlimit()来设置更高的限制。
性能:
递归函数可能比迭代解决方案更慢,因为每次函数调用都需要时间来设置新的栈帧。
栈溢出:
如果递归太深,可能会导致栈溢出,因为每个函数调用都会在栈上占用一定的空间。
def factorial(num):
#0和1的阶乘都是本身,因此可以作为递归函数的循环结束条件
if num == 1:
return 1
#采用递归阶乘的算法来实现需求:n * (n -1)!
#定义阶乘的算法,传入的num形参值就是阶乘公式中的n,factorial(num - 1)就是(n -1)!
#factorial(num - 1)是递归函数,会循环执行factorial函数体,直到不满足条件位置,循环结束,也就相当于是(n -1)!阶乘的运算过程
return num * factorial(num - 1)
def binary_search_recursive(arr, target, low, high):
# 基例:如果范围无效,则目标不在数组中
if low > high:
return -1
# 计算中间位置
mid = (low + high) // 2
# 如果中间元素就是目标值,则返回其位置
if arr[mid] == target:
return mid
# 如果中间元素大于目标值,则在左侧子数组中继续查找
elif arr[mid] > target:
return binary_search_recursive(arr, target, low, mid - 1)
# 如果中间元素小于目标值,则在右侧子数组中继续查找
else:
return binary_search_recursive(arr, target, mid + 1, high)
# 测试函数
def test_binary_search():
arr = [1, 3, 5, 7, 9, 11, 13, 15, 17, 19]
target = 13
result = binary_search_recursive(arr, target, 0, len(arr) - 1)
if result != -1:
print(f"元素 {target} 在数组中的位置为:{result}")
else:
print(f"元素 {target} 不在数组中。")
# 运行测试
test_binary_search()
Python函数的意义在于提供了一种封装代码以便重用和组织的有效机制。使用函数可以带来以下好处:
1.代码重用:函数允许你编写一段代码并在多处调用它,这减少了重复代码的数量,使得程序更加简洁。
2.模块化:函数可以将程序分解成小块,每块完成一个单独的任务。这种模块化使得程序更易于理解、测试和维护。
3.命名空间分隔:函数内部的变量名不会与函数外部的变量名冲突,因为函数定义了一个局部命名空间。
4.抽象:函数提供了一个抽象层,允许调用者不必关注函数内部的具体实现,只需要了解其接口(参数和返回值)。
5.可测试性:函数使得你可以对程序的各个部分进行独立测试。
6.参数化操作:函数可以接受参数,这意味着你可以定制函数的行为。
7.维护性:如果需要更改程序的某个功能,通常只需修改对应的函数即可。
8.复用性和共享性:编写的函数可以被其他程序和代码重用,甚至可以分享给其他开发者。
9.递归处理:函数可以调用自身,这使得执行某些任务(如遍历数据结构)变得更简单。
10.闭包和高阶函数:函数可以定义在其他函数内部,可以作为返回值返回,或者作为参数传递给其他函数,这提供了极大的灵活性和强大的表达能力。
总之,函数在Python编程中是构建程序的基本和核心元素,它们使得编写大型和复杂程序变得可行和高效。