编写Python函数的15个层次

佛家有人生三重境界之说,即:“看山是山,看水是水;看山不是山,看水不是水;看山还是山,看水还是水。”编程亦有不同境界层次,本篇文章带领各位初步领略一下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__ 方法会自动调用,用于初始化新创建的对象。

这里有一些常见的魔法方法及其用途:

  1. __init__(self, [...]):构造器方法,当一个新的对象被创建时自动调用。

  2. __str__(self):定义当对象被当作字符串处理时的行为(比如使用 print() 函数)。

  3. __repr__(self):定义对象的“官方”字符串表示,通常用于调试(比如print(类的实例名称))。

  4. __add__(self, other):定义加法操作的行为。

  5. __getitem__(self, key):允许对象使用索引操作,如 obj[key]。

  6. __setattr__(self, name, value):拦截对属性的设置操作。

  7. __len__(self):当内置 len() 函数被用于序列对象时调用。

  8. __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

生成器函数是一种非常实用的工具,它们允许你按需生成值,而不是一次性地计算并存储所有数据。生成器提供了一种有效的方式来处理大数据集或复杂算法,因为它们仅在需要时才计算下一个值,从而减少了内存使用并提高了程序的效率。这里有几个生成器函数的实际用途:

  1. 处理大数据集: 当数据集非常大时,将它们全部加载到内存中可能不可行。生成器可以逐个生成数据项,只在处理时消耗内存。

  2. 无限序列: 生成器可以创建无限的数据序列,比如无限的自然数序列,而无需在内存中实际存储整个序列。

  3. 数据流处理: 生成器非常适合于流式数据处理,例如实时数据流或文件流,因为它们可以逐步处理数据而不需要等待全部数据的到来。

  4. 复杂算法的简化: 使用生成器可以简化某些算法的实现,比如在图算法中生成所有可能的路径,或者在文本处理中逐行读取和分析文件。

  5. 管道和数据链: 生成器可以被组织成管道或数据处理链,这样数据可以通过一系列的处理步骤逐步转换,每个步骤都使用上一个步骤的输出作为输入。

以下展示如何使用生成器处理大文件,逐行读取而不是一次性加载整个文件

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

高阶函数对于编写灵活且可重用的代码非常有用,因为它们允许行为的动态修改和抽象。以下是一些高阶函数的主要用途:

  1. 增强代码的可重用性: 通过将函数作为参数,你可以编写更通用的代码,然后通过传入不同的函数来改变其行为。这降低了代码重复,提高了代码的模块化。

  2. 简化复杂操作: 高阶函数可以简化一些常见的操作模式,例如映射、过滤和折叠数据结构。Python 的内建函数如 map()、filter() 和 reduce() 就是这样的例子。

  3. 函数组合与管道: 高阶函数可以用来组合多个函数,创建一个执行多个操作的管道。这种模式在数据处理和函数式编程中非常有用。

  4. 控制流和异步编程: 在需要控制函数的执行时(例如:延迟执行、重试、错误处理等),高阶函数可以封装这些控制流逻辑,使主逻辑更清晰。

  5. 回调和事件处理: 在处理异步操作或事件驱动的编程时,高阶函数允许动态地处理事件或异步结果,例如在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)

在这个例子中:

  1. fetch_data 函数接收一个URL和一个回调函数 callback。它在一个新的线程中发起HTTP请求,以模拟异步操作。

  2. 请求完成后,回调函数 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 使用了所谓的装饰器工厂模式。这种模式允许创建一个返回装饰器的函数。这是装饰器的一个更高级用法,允许在应用装饰器时传入额外的参数。

以上是全部的内容,希望对各位的理解有帮助~

  • 27
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值