Python--深入理解Python特性 第3章

第3章 高效的函数

3.1 函数是Python的头等对象

  Python中一切皆对象,函数也不例外。函数可以分配给变量或存储在数据结构中,还可以传递给其他函数或作为其他函数的返回值。

  函数可以嵌套,并且可以捕获并携带父函数的一些状态。具有这种行为的函数称作闭包

# 工厂函数
def make_adder(n):
    def add(x):
        # 这里引用了父函数的n,这种情况称为词法闭包(lexical closure)
        return x + n
    return add


plus_3 = make_adder(3)
plus_5 = make_adder(5)

print(plus_3(1))
print(plus_5(1))
View Code

  对象可以设置为可调用的,因此很多情况下可以将对象作为函数对待。

class Adder:
    def __init__(self, n):
        self.n = n

    def __call__(self, x):
        # 实现此方法,则类的实列对象可当中函数调用
        return self.n + x


plus_3 = Adder(3)
plus_5 = Adder(5)
# plus_3和plus_5实际是Adder的两个实例对象,
# 这里直接把它们当作函数调用,是因为Adder实现了__call__方法
print(plus_3(1))
print(plus_5(1))
View Code

3.2 lambda是单表达式函数

  lambda函数不必与名称绑定,因此常作为匿名函数。

# 工厂函数
def make_adder(n):
    # 使用lambda匿名函数形成了一个闭包
    return lambda x: x + n


plus_3 = make_adder(3)
plus_5 = make_adder(5)

print(plus_3(1))
print(plus_5(1))
View Code

  lambda函数不能使用普通的Python语句,因为lambda会计算其唯一的表达式,并隐式的把计算结果返回。

  不要过度使用lambda函数,使用前请先问问自己:使用普通具名函数或者列表解析式是否更加清晰。

# 错误的案例
class Car:
    # rev和crash应该使用常规的函数定义方法def来声明函数,
    # 那样会更清晰
    rev = lambda self: print('Wroom!')
    crash = lambda self: print('Boom!')


car = Car()
car.crash()

# 错误的案例
l_list = list(filter(lambda x: x % 2 == 0, range(16)))
# 正确的案例
l_list = [x for x in range(16) if x % 2 == 0]

# 注:尽可能在表达式清晰简单的时候才使用lambda函数
# 少敲一些代码并不重要,重要的是能够让代码清晰可读
View Code

3.3 装饰器的力量

  装饰器用于定义可重用的组件,可以将其应用于可调用对象以修改其行为,同时无须永久修改可调用对象本身。

  @语法只是在输入函数上调用装饰器的简写,不过@语法会在定义时就立即修饰该函数。在单个函数上应用多个装饰器的顺序是从底部到顶部(装饰器栈)

def decorator(func):
    print(f'func:{func.__name__}被装饰了')
    return func


# 方式1
# def greet():
#     print('in greet')
#     return 'Hello'


# 方式2 使用@进行装饰
@decorator
def greet():
    print('in greet')
    return 'Hello'


print('开始装饰')
# 方式1 直接调用装饰函数
# greet = decorator(greet)

greet()

''' 方式1 输出结果
开始装饰
func:greet被装饰了
in greet
'''
# 使用@进行装饰时,在定义的时候就会立即调用
# 方式1可以把装饰结果赋值给其他变量,这样就可以保留原函数的原滋原味
''' 方式2 输出结果
func:greet被装饰了
开始装饰
in greet
'''
View Code

  最好在自己的装饰器中使用functools.wraps将被装饰对象中的元数据转移到装饰后的对象中。

import functools


def upper_decorator(func):
    # 使用functools.wraps可把func的元数据移到wrapper中
    # @functools.wraps(func)   # 1
    def wrapper():
        return func().upper()
    return wrapper


@upper_decorator
def greet():
    """这是greet的注释"""
    return 'Hello'


print(greet.__name__)
print(greet.__doc__)

''' 注释1处代码 输出结果
wrapper
None
'''
''' 不注释1处代码 输出结果
greet
这是greet的注释
'''
View Code

  使用*和** 参数解包操作符完成带参函数的装饰

def trace(func):
    def wrapper(*args, **kwargs):
        print(f'Trace: calling {func.__name__}() '
              f'with {args}, {kwargs}')
        original_result = func(*args, **kwargs)
        print(f'Trace: {func.__name__}() '
              f'returned {original_result}')
        return original_result
    return wrapper


@trace
def say(name, line):
    return f'{name}: {line}'


result = say('Jane', line='Welcome')
print(result)


''' 输出结果
Trace: calling say() with ('Jane',), {'line': 'Welcome'}
Trace: say() returned Jane: Welcome
Jane: Welcome
'''
View Code

  装饰器不是万能的,容易产生可怕且不可维护的代码,不应过度使用。

3.4 有趣的*args和**kwargs

  *args和**kwargs 用于在Python中编写变长参数的函数

  *args收集额外的位置参数组成元组。**kwargs收集额外的关键字参数组成字典。

  实际起作用的语法是*和**,args和kwargs只是命名约定,也可以使用其他名称。建议遵循命名约定。

3.5 函数参数解包

  *可用于将元组、列表和生成器等序列解包为位置参数。

  如果使用*解包字典,则所有的键将以随机的顺序传递给函数。(我试了下,按key定义顺序输出,不是随机,用的python3.6,不知道是不是跟python版本有关)

dic_t = {'b': 1, 'a': 2, 'c': 3}
print(*dic_t)
''' 输出结果
b a c
'''
View Code

  **可用于将字典解包为关键字参数。

3.6 返回空值

  如果函数没有明确的return,则函数返回None。

  return、return None 或者不写return语句效果是一样的,返回的都是None。

  如果函数本身确实不用返回值,可以不显示的写return语句。

  如果函数有返回值,某些情况返回None,这种情况下,建议最后都显示的return None,可以使代码意图更明确。

原文地址:https://www.cnblogs.com/yarightok/p/15202150.html