佛家有人生三重境界之说,即:“看山是山,看水是水;看山不是山,看水不是水;看山还是山,看水还是水。”编程亦有不同境界层次,本篇文章带领各位初步领略一下Python函数编写的不同方法~
1)不包含任何参数的基础函数
def hello():
print('hello')
print('world')
hello()
# hello
# world
你能编写的最基本的函数--一个接收 0 个参数,无法定制的函数。
2)接收一个参数的函数
def add10(n):
return n + 10
print(add10(4)) # 14
print(add10(5)) # 15
3)接收两个或更多参数的函数
def add(a, b):
return a + b
print(add(10, 4)) # 14
print(add(100, 5)) # 105
def average(a, b, c):
return (a+b+c)/3
print(average(1,2,3)) # 2.0
print(average(3,4,6)) # 4.333333333
我们可以让函数接收任意数量的参数。
4)具有默认/可选参数的函数
我们可以让函数接收默认参数,也称为可选参数。
def greet(name, greeting='hello'):
print(greeting, name)
greet('tim', 'hola') # hola tim
greet('tim') # hello tim
在这里,greeting 是默认参数/可选参数。
-
如果我们不输入任何信息,greeting 默认为 "hello"。
-
如果我们输入任何内容,greeting 将取该值
5)Lambda函数
def add10(x):
return x + 10
# 这与下面的写法等价
add10 = lambda x: x+10
def add(a, b):
return a + b
# 等价于
add = lambda a, b: a+b
在 Python 中,lambda 函数与普通函数类似,但使用另一种语法编写 - lambda 输入:输出。
优点
-
可以用一行来写
-
它是匿名的--如果我们不想给它命名,就不用命名
缺点
-
一个 lambda 函数中只能有一个表达式
下面是一些它的常见使用场景
1.作为参数传递给高阶函数:如 map(), filter(), 和 sorted() 等函数经常与 lambda 表达式一起使用。
-
使用 map() 转换列表项:map(lambda x: x * 2, [1, 2, 3]) 会得到 [2, 4, 6]。
-
使用 filter() 筛选列表项:filter(lambda x: x > 5, [3, 4, 5, 6, 7]) 会得到 [6, 7]。
-
使用 sorted() 对列表进行排序:sorted([5, 2, 9, 1], key=lambda x: x) 会得到 [1, 2, 5, 9]
2.在函数内部快速定义小函数:例如,如果你需要一个简单的函数来计算两个数的乘积,可以直接在调用处使用 lambda:result = (lambda x, y: x * y)(10, 5)
3.在列表推导式和字典推导式中使用:有时你可能想在推导式中进行更复杂的操作,可以内嵌 lambda 来实现。
-
例如:[lambda x: x * x for x in range(10)] 会创建一个函数列表,每个函数接受一个参数并返回其平方。
4.作为回调函数:在 GUI 编程或在事件驱动的编程中,经常需要传递简单的函数作为响应某个事件的操作,lambda 表达式因其简洁性而非常适合用作此类场景。
6)递归函数---函数调用自身
def factorial(n):
if n == 1:
return 1
return n * factorial(n-1)
print(factorial(5)) # 120
这里完成阶乘操作,记得定义退出时的条件。
def fibonacci(n):
if n == 0:
return 0
elif n == 1:
return 1
else:
return fibonacci(n-1) + fibonacci(n-2)
# 示例:计算斐波那契数列中的第 10 个数字
print(fibonacci(10))
虽然这个函数很优雅,但它不是计算斐波那契数的最高效方法,因为它进行了很多重复计算。在实际应用中,可能会使用更高效的递归方法(如带有缓存的递归),或其他更高效的算法(如动态规划)。
7)存在于类中的函数
class Dog:
def bark(self):
print('woof')
dog = Dog()
dog.bark() # woof
方法只是存在于类或对象中的一个函数。在我们的 Dog 类中,我们像定义普通函数一样定义了方法 bark。
需要注意的是,普通方法会多出一个 self,指的是对象本身。但在实际调用时,我们并不传递 self。
8)魔法函数
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self):
return f'Dog(name={{self.name}, age={self.age})'
dog = Dog('rocky', 4) # __init__ is called
print(dog) # __str__ is called
# Dog(name=rocky, age=4)
Python中的魔法方法(也称为特殊方法)是一种具有特殊名称的方法,它们以双下划线(__)开头和结尾。这些方法在Python中有特定的用途和意义,它们通常不需要直接调用,而是通过某些特定的Python语法自动触发。例如,当你创建类的实例时,__init__ 方法会自动调用,用于初始化新创建的对象。
这里有一些常见的魔法方法及其用途:
-
__init__(self, [...]):构造器方法,当一个新的对象被创建时自动调用。
-
__str__(self):定义当对象被当作字符串处理时的行为(比如使用 print() 函数)。
-
__repr__(self):定义对象的“官方”字符串表示,通常用于调试(比如print(类的实例名称))。
-
__add__(self, other):定义加法操作的行为。
-
__getitem__(self, key):允许对象使用索引操作,如 obj[key]。
-
__setattr__(self, name, value):拦截对属性的设置操作。
-
__len__(self):当内置 len() 函数被用于序列对象时调用。
-
__iter__(self) 和 __next__(self):分别用于迭代器的初始化和进行迭代操作。
这里再举一个使用__add__操作的例子
class ComplexNumber:
def __init__(self, real, imag):
self.real = real
self.imag = imag
def __add__(self, other):
return ComplexNumber(self.real + other.real, self.imag + other.imag)
def __str__(self):
return f"{self.real} + {self.imag}i"
# 使用示例
c1 = ComplexNumber(1, 2)
c2 = ComplexNumber(3, 4)
result = c1 + c2
print(result) # 输出: 4 + 6i
在这个例子中,__add__ 方法使得两个 ComplexNumber 对象可以使用加法运算符相加,而 __str__ 方法定义了当对象被当作字符串处理时的显示格式。
9)带 *args 的函数 - 包含任意数量参数的函数
def test(*args):
print(args)
test() # (,)
test(1) # (1, )
test(1, 2) # (1, 2)
test(1, 2, 3) # (1, 2, 3)
test(1, 2, 3, 4) # (1, 2, 3, 4)
如果我们让函数接受 *args,我们就可以在函数中传递任意数量的位置参数。我们传入的所有位置参数都将存储在元组 args 中。
def test(a, b, *args):
print(f'{a=} {b=} {args=}')
test() # ERROR
test(1) # ERROR
test(1, 2) # a=1 b=2 args=()
test(1, 2, 3) # a=1 b=2 args=(3,)
test(1, 2, 3, 4) # a=1 b=2 args=(3, 4)
test(1, 2, 3, 4, 5) # a=1 b=2 args=(3, 4, 5)
在这里,我们有一个函数,它首先接收 a 和 b,然后接收 *args。这意味着我们必须在 a 和 b 中输入至少 2 个参数,然后再输入任意数量的参数。
10)带 **kwargs 的函数 - 包含任意数量关键字参数的函数
def test(**kwargs):
print(kwargs)
test() # {}
test(a=1) # {'a': 1}
test(a=1, b=2) # {'a': 1, 'b': 2}
test(a=1, b=2, c=3) # {'a': 1, 'b': 2, 'c': 3}
test(a=1, b=2, c=3, d=4) # {'a': 1, 'b': 2, 'c': 3, 'd': 4}
如果我们让函数接受 **kwargs,我们就可以在函数中传递任意数量的关键字参数。我们传入的所有关键字参数都将存储在一个名为 kwargs 的字典中
def test(a, b, **kwargs):
print(f'{a=} {b=} {kwargs=}')
test() # ERROR
test(a=1) # ERROR
test(a=1, b=2) # a=1 b=2 kwargs={}
test(a=1, b=2, c=3) # a=1 b=2 kwargs={'c': 3}
test(a=1, b=2, c=3, d=4) # a=1 b=2 kwargs={'c': 3, 'd': 4}
11)带有 *args 和 **kwargs 的函数
def test(*args, **kwargs):
print(f'{args} {kwargs}')
test() # () {}
test(1,2,3) # (1, 2, 3) {}
test(a=1, b=2, c=3) # () {'a': 1, 'b': 2, 'c': 3}
test(1, 2, a=3, b=4) # (1, 2) {'a': 3, 'b': 4}
我们可以让我们的函数同时接受 *args 和 **kwargs。只需要注意**kwargs 必须位于 *args 之后。
def test(*apple, **orange):
print(f'{apple} {orange}')
test() # () {}
test(1,2,3) # (1, 2, 3) {}
test(a=1, b=2, c=3) # () {'a': 1, 'b': 2, 'c': 3}
test(1, 2, a=3, b=4) # (1, 2) {'a': 3, 'b': 4}
此外,请注意 args 和 kwargs 只是变量名(分别代表参数和关键字参数)。我们可以选择不使用 args 和 kwargs。重要的是 *args 中的 * 和 **kwargs 中的 **。
12)生成器函数——具有多个输出的函数
在普通函数中,当 return 语句运行时,之后不会发生任何事情 - 只有 1 个输出。
生成器函数使用yield关键字而不是return关键字。当yield关键字运行时,它不会停止整个函数(不同于return关键字)。
def generate_1_to_3():
yield 1
yield 2
yield 3
for i in generate_1_to_3():
print(i)
# 1
# 2
# 3
def gen_squares(start, stop):
for i in range(start, stop+1):
yield i**2
for i in gen_squares(1, 5):
print(i)
# 1
# 4
# 9
# 16
# 25
生成器函数是一种非常实用的工具,它们允许你按需生成值,而不是一次性地计算并存储所有数据。生成器提供了一种有效的方式来处理大数据集或复杂算法,因为它们仅在需要时才计算下一个值,从而减少了内存使用并提高了程序的效率。这里有几个生成器函数的实际用途:
-
处理大数据集: 当数据集非常大时,将它们全部加载到内存中可能不可行。生成器可以逐个生成数据项,只在处理时消耗内存。
-
无限序列: 生成器可以创建无限的数据序列,比如无限的自然数序列,而无需在内存中实际存储整个序列。
-
数据流处理: 生成器非常适合于流式数据处理,例如实时数据流或文件流,因为它们可以逐步处理数据而不需要等待全部数据的到来。
-
复杂算法的简化: 使用生成器可以简化某些算法的实现,比如在图算法中生成所有可能的路径,或者在文本处理中逐行读取和分析文件。
-
管道和数据链: 生成器可以被组织成管道或数据处理链,这样数据可以通过一系列的处理步骤逐步转换,每个步骤都使用上一个步骤的输出作为输入。
以下展示如何使用生成器处理大文件,逐行读取而不是一次性加载整个文件
def read_lines(filename):
with open(filename, 'r') as file:
for line in file:
yield line.strip()
# 使用生成器逐行处理文件
for line in read_lines('large_file.txt'):
print(line) # 只在需要时处理每一行
在这个例子中,read_lines 函数是一个生成器,它打开一个文件并逐行返回内容,这使得内存消耗得以控制,尤其是在处理非常大的文件时。这种方式避免了一次性读取整个文件带来的内存负担。
13)高阶函数
高阶函数是指以下函数:
-
接受另一个函数作为输入
-
返回另一个函数作为输出
-
同时包含前两者
# a function that takes in another function
def add10(n):
return n + 10
def apply_to_list(lis, function):
output = []
for n in lis:
output.append(function(n))
return output
lis = apply_to_list([1,2,3], add10)
# lis = [11, 12, 13]
# a function that returns another function
def create_add_n_function(n):
def add_n(number):
return number + n
return add_n
add100 = create_add_n_function(100)
print(add100(5)) # 105
高阶函数对于编写灵活且可重用的代码非常有用,因为它们允许行为的动态修改和抽象。以下是一些高阶函数的主要用途:
-
增强代码的可重用性: 通过将函数作为参数,你可以编写更通用的代码,然后通过传入不同的函数来改变其行为。这降低了代码重复,提高了代码的模块化。
-
简化复杂操作: 高阶函数可以简化一些常见的操作模式,例如映射、过滤和折叠数据结构。Python 的内建函数如 map()、filter() 和 reduce() 就是这样的例子。
-
函数组合与管道: 高阶函数可以用来组合多个函数,创建一个执行多个操作的管道。这种模式在数据处理和函数式编程中非常有用。
-
控制流和异步编程: 在需要控制函数的执行时(例如:延迟执行、重试、错误处理等),高阶函数可以封装这些控制流逻辑,使主逻辑更清晰。
-
回调和事件处理: 在处理异步操作或事件驱动的编程时,高阶函数允许动态地处理事件或异步结果,例如在GUI编程或网络请求中。
下面是一个例子,演示如何使用高阶函数 map() 来处理数据:
def square(x):
return x * x
numbers = [1, 2, 3, 4, 5]
squared_numbers = map(square, numbers)
print(list(squared_numbers)) # [1, 4, 9, 16, 25]
在这个例子中,map() 函数接收一个函数 square 和一个列表 numbers,然后将 square 函数应用到列表中的每个元素上。
阶函数的使用是为了代码的可维护性、可扩展性和重用性,尽管它们并非必需,但在适当的情境中使用它们可以大大增强程序的质量。下面是两个例子分别使用和不使用高阶函数
import requests
import threading
def fetch_data(url, callback):
"""
发起HTTP GET请求,获取数据后调用回调函数处理响应。
"""
def thread_function():
response = requests.get(url)
callback(response) # 调用回调函数
thread = threading.Thread(target=thread_function)
thread.start()
def handle_response(response):
"""
处理HTTP响应,打印状态码和响应头。
"""
print("Response Status Code:", response.status_code)
print("Response Headers:", response.headers)
# 使用示例
url = "https://api.github.com" # GitHub API的简单端点
fetch_data(url, handle_response)
在这个例子中:
-
fetch_data 函数接收一个URL和一个回调函数 callback。它在一个新的线程中发起HTTP请求,以模拟异步操作。
-
请求完成后,回调函数 handle_response 被调用并处理请求的响应,打印出响应的状态码和头信息。
在上述例子中,如果不使用高阶函数,你可以通过直接在函数内部定义和执行所需的逻辑来处理异步操作。例如,我们可以直接在线程函数中处理响应,而不是通过回调。这会使函数的通用性降低,但可以实现同样的功能:
import requests
import threading
def fetch_and_process_data(url):
"""
在线程中直接处理HTTP响应。
"""
def thread_function():
response = requests.get(url)
# 直接处理响应
print("Response Status Code:", response.status_code)
print("Response Headers:", response.headers)
thread = threading.Thread(target=thread_function)
thread.start()
# 使用示例
url = "https://api.github.com"
fetch_and_process_data(url)
在这个修改后的版本中,我们省略了回调函数,并将处理响应的逻辑直接嵌入到 thread_function 中。这种方法同样能够异步处理网络请求,并在请求完成后立即打印结果,但它牺牲了灵活性和函数的重用性。fetch_and_process_data 函数现在专门用于处理特定类型的响应,并且难以适应其他类型的处理逻辑或共享逻辑于其他部分的需求。
14)装饰器——一种特殊类型的高阶函数
首先,我们得知道 @ 语法在 Python 中的工作原理:
@decorator_function
def your_function(stuff):
return stuff
# 这等价于下面的写法
def your_function(stuff):
return stuff
your_function = decorator_function(your_function)
装饰器通过包装另一个函数或方法,可以在不修改原始函数代码的情况下,增加额外的功能。它们在多种场合非常有用,比如日志记录、性能测试、事务处理、权限校验等。
下面是一个简单的装饰器例子,该装饰器用于记录函数的调用信息,包括调用的函数名和所传递的参数:
def log_function_call(func):
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__} with arguments {args} and keyword arguments {kwargs}")
result = func(*args, **kwargs)
print(f"{func.__name__} returned {result}")
return result
return wrapper
@log_function_call
def add(a, b):
return a + b
@log_function_call
def greet(name, greeting="Hello"):
return f"{greeting}, {name}!"
# 使用装饰器增强的函数
print(add(3, 4))
print(greet("Alice", greeting="Hi"))
在这个例子中:
-
log_function_call 是一个装饰器函数,它接收一个函数 func 作为参数。
-
wrapper 函数包装了 func,增加了打印调用信息和结果的功能。它使用 *args 和 **kwargs 来接收任意数量的位置参数和关键字参数,确保装饰器可以用于任何函数。
-
add 和 greet 函数被 log_function_call 装饰器装饰,因此在调用这些函数时,它们的调用和返回信息将被打印出来。
使用装饰器的好处是它可以很容易地重复使用在多个函数上,提供了一种非常灵活和强大的方式来“装饰”或“标记”函数以增强其功能,而不会污染函数本身的核心逻辑。
在Python中,装饰器最常见的写法确实是使用函数嵌套函数的形式,这是因为装饰器需要返回一个可调用对象(通常是函数),它将包装并可能修改目标函数的行为。嵌套函数的形式很自然地实现了这一点,因为内部函数可以访问到外部函数的参数(即被装饰的函数)。
然而,装饰器的写法不限于嵌套函数。你也可以使用类来实现装饰器,这在需要存储状态或实现更复杂的行为时特别有用。在类装饰器中,你需要定义一个类,该类实现了 __call__ 方法,让类的实例可以被调用。
下面是使用类来实现装饰器的例子:
class LogFunctionCall:
def __init__(self, func):
self.func = func
self.call_count = 0 # 添加一个属性来跟踪调用次数
def __call__(self, *args, **kwargs):
self.call_count += 1 # 每次调用时增加调用次数
print(f"This function has been called {self.call_count} times.") # 打印调用次数
print(f"Calling {self.func.__name__} with arguments {args} and keyword arguments {kwargs}")
result = self.func(*args, **kwargs)
print(f"{self.func.__name__} returned {result}")
return result
@LogFunctionCall
def add(a, b):
return a + b
@LogFunctionCall
def greet(name, greeting="Hello"):
return f"{greeting}, {name}!"
# 使用装饰的函数
print(add(3, 4))
print(greet("Alice", greeting="Hi"))
在这个例子中,LogFunctionCall 是一个类,它接受一个函数作为参数并存储为一个实例变量。该类的 __call__ 方法被定义为执行实际的装饰逻辑,这使得你可以将类的实例像普通函数一样调用。当你在一个函数定义前使用装饰器语法(即使用 @ 符号 followed by the decorator name)时,Python 会自动创建装饰器类的实例。
类装饰器提供了更大的灵活性,特别是当装饰器需要维护状态时。例如,如果你想要计数函数调用次数,或者在多个函数调用之间维护一些信息,类装饰器就非常有用。不过,对于简单的无状态装饰,使用函数嵌套函数的形式就完事儿了~
15)高级装饰器函数
def add_symbol(symbol):
def decorator_function(your_function):
def wrapper(*args, **kwargs):
return your_function(*args, **kwargs) + symbol
return wrapper
return decorator_function
@add_symbol('!')
def greet(name):
return f'hello {name}'
print(greet('tom')) # hello tom!
@add_symbol('??')
def greet(name):
return f'hello {name}'
print(greet('tom')) # hello tom??
请注意,在 add_symbol 中,我们有 2 层嵌套函数。我们这样做是为了可以使用 @add_symbol('!') 和 @add_symbol('??') 作为装饰器函数,而不是创建 2 个装饰器函数。这里装饰器 add_symbol 使用了所谓的装饰器工厂模式。这种模式允许创建一个返回装饰器的函数。这是装饰器的一个更高级用法,允许在应用装饰器时传入额外的参数。
以上是全部的内容,希望对各位的理解有帮助~