【Effective Python】读书笔记-03函数

1. 不要把函数返回的多个数值拆分到三个以上的变量中

把返回的值拆分到四个或四个以上的变量是很容易出错的,所以最好不要那么写,而是应该通过小类或 namedtuple 实例完成。

2. 遇到意外状况时应该抛出异常,不要返回 None

不用 None 的原因:

没办法与 0 和空白字符串之类的值区分,这些值都相当于 False。

用异常表示特殊情况:

  • 让调用这个函数的程序根据文档里写的异常情况做出处理。
  • 通过类型注解可以明确禁止函数返回 None,即便在特殊情况下,它也不能返回这个值。

案例:

def divide(a: float, b: float) -> float:
    """Divide by a and b

    Raises:
        ValueError: when the inputs can not be divided

    :param a:
    :param b:
    :return:
    """
    try:
        res = a / b
        return res
    except ZeroDivisionError as e:
        raise ValueError('Invalid inputs')

3. 了解如何在闭包里面使用外围作用域中的变量-尽量不要使用nolocal

在表达式中引用某个变量时,Python 解释器会按照下面的顺序,在各个作用域(scope)里面查找这个变量,以解析(resolve)这次引用:

  1. 当前函数的作用域。
  2. 外围作用域(例如包含当前函数的其他函数所对应的作用域)
  3. 包含当前代码的那个模块所对应的作用域(也叫全局作用域,global scope)。
  4. 内置作用域(built-in scope,也就是包含 len 与 str 等函数的那个作用域)。

为了防止函数中的局部变量污染外围变量,当 python 的当前作用于和外围作用域有重名变量时,Python 的当前作用域会单独维护自己的变量,如果需要将该变量指向外围作用域,则可以使用nolocal,但是不能侵入模块级别的作用域(以防污染全局作用域)。

尽量不要使用nolocal,因为它造成的副作用有时很难发现。尤其是在那种比较长的函数里,nonlocal 语句与其关联变量的赋值操作之间可能隔得很远。

替代方案:如果 nonlocal 的用法比较复杂,那最好是改用辅助类来封装状态

4. 用数量可变的位置参数给函数设计清晰的参数列表

让函数接受数量可变的位置参数(positional argument),可以把函数设计得更加清晰(这些位置参数通常简称 varargs,或者叫作 star args,因为我们习惯用*args 指代)

作用:

可以给最后一个位置参数加前缀*,这样调用者就只需要提供不带星号的那些参数,然后可以不再指其他参数,也可以继续指定任意数量的位置参数。函数的主体代码不用改,只修改调用代码即可。

令函数接受数量可变的位置参数可能会导致的问题:

  • 如果调用函数时,把带*操作符的生成器传了过去,那么程序必须先把这个生成器里的所有元素迭代完(以便形成元组),然后才能继续往下执行(相关知识,参见第 30 条)。这个元组包含生成器所给出的每个值,这可能耗费大量内存,甚至会让程序崩溃。
def gen():
    """迭代器"""
    for i in range(10):
        yield i


def func(*arg):
    """如果得到的是带*的迭代器,必须要执行完迭代器才行,如果迭代器返回的数据过多,可能导致内存溢出"""
    print(arg)


g = gen()
func(*g)
  • 如果用了*args 之后,又要给函数添加新的位置参数,那么原有的调用操作就需要全都更新

在给这种*arg 函数添加参数时,应该使用只能通过关键字来指定的参数(keyword-only argument)

5. 用关键字参数来表示可选的行为

定义函数时,如果想让这个函数接受任意数量的关键字参数,那么可以在参数列表里写上万能形参**kwargs,它会把调用者传进来的参数收集合到一个字典里面稍后处理

关键字参数的好处:

  • 用关键字参数调用函数可以让初次阅读代码的人更容易看懂
  • 它可以带有默认值,该值是在定义函数时指定的。
  • 可以很灵活地扩充函数的参数,而不用担心会影响原有的函数调用代码。
  • 可选的关键字参数有助于维护向后兼容(backward compatibility)

可选的关键字参数总是应该通过参数名来传递,而不应按位置传递。

def func(a, b, c=1):
    """c就是可选参数"""
    print(a, b, c)


func(a=1, b=2)

6. 用 None 和 docstring 来描述默认值会变的参数

参数的默认值只会在系统加载这个模块的时候,计算一遍,而不会在每次执行时都重新计算。

可能出现的问题:

import random


def test1(b, a={}):
    a[b] = random.randrange(10)
    return a


def test2(b, a=None):
    a[b] = random.randrange(10)
    return a


# {'h': 6}
print(test1('h'))
# {'h': 6, 'e': 0}
print(test1('e'))

# {'h': 8}
print(test2('h', {}))
# {'e': 5}
print(test2('e', {}))

把参数的默认值写成 None 的意义:

  • 表示那种以后可能由调用者修改内容的默认值
  • 用于每次调用都要执行的函数

与类型注解搭配使用:

  • Optional

7. 用只能以关键字指定和只能按位置传入的参数来设计清晰的参数列表

对于参数比较复杂的函数,我们可以声明只能通过关键字指定的参数(keyword-only argument),这样的话,写出来的代码就能清楚地反映调用者的想法了。这种参数只能用关键字来指定,不能按位置传递:

def func(a, b, *, c=1, d=2):
    """c,d只能通过关键字指定"""
    print(a, b, c, d)


# TypeError: func() takes 2 positional arguments but 4 were given
# func(1, 2, 3, 4)
# 1 2 3 4
func(1, 2, c=3, d=4)

用法:参数列表里的*符号把参数分成两组,左边是位置参数,右边是只能用关键字指定的参数。

Python3.8 新特性:只能按照位置传递参数,参数列表中的/符号,表示它左边的那些参数只能按位置指定。

*/之间的参数:这两个符号之间的参数,既可以按位置提供,又可以用关键字形式指定。


def func2(a, b, /, *, c=1, d=2):
    """a,b只能通过位置指定,c,d只能通过关键字指定"""
    print(a, b, c, d)


# 1 2 3 4
func2(1, 2, c=3, d=4)

# TypeError: func2() got some positional-only arguments passed as keyword arguments: 'a, b'
func2(a=1, b=2, c=3, d=4)

8. 用 functools.wraps 定义函数修饰器

场景:函数装饰器返回的是装饰后的新函数,而不是原始函数,即修改了函数的元数据,可能导致 help()、对象序列化器无法正常运转。

def func(func):
    def wrapper(*args, **kwargs):
        print('前置处理')
        return func(*args, *kwargs)

    return wrapper


@func
def func1():
    print('hello')


# 前置处理
# hello
func1()
# .wrapper at 0x102fca290>
print(func1)

解决:改用 functools 内置模块之中的 wraps 辅助函数来实现。wraps 本身也是个修饰器,它可以帮助你编写自己的修饰器。把它运用到 wrapper 函数上面,它就会将重要的元数据(metadata)全都从内部函数复制到外部函数。

from functools import wraps


def func(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print('前置处理')
        return func(*args, *kwargs)

    return wrapper


@func
def func1():
    print('hello')


# 前置处理
# hello
func1()
# 
print(func1)

你可能感兴趣的:(python,数据库,服务器)