在开发解决方案时,我们倾向于将复杂的实际问题提炼为更小、更易于管理的子问题,然后使用函数来解决这些问题。函数是冗余代码的克星,也是我们抵御代码复杂性的最强防线。
大多数函数在编写过程中,关键是其返回值。函数产生结果的方式会极大地影响用户调用函数时的体验。掌握设计优雅返回结果的函数的艺术是制作高质量函数的基础。
Python 是如此的灵活,以至于我们可以很容易地做到在其他语言中很难做到的事情。例如:让函数同时返回不同类型的结果。就像下面这样:
def get_users(user_id=None):
if user_id is not None:
return User.get(user_id)
else:
return User.filter(is_active=True)
# Return single user
get_users(user_id=1)
# Return all users
get_users()
在上面的代码片段中,当我们需要获取单个用户时,我们传递一个 "user_id
" 参数,否则,如果我们传递一个 None 值,它将返回所有活跃用户的列表。乍一看,这种设计似乎很合理。
但是,编写类似功能强大的函数并不是一件好事。这是因为优秀的函数必须具有 单一职责。所谓 “单一职责”,是指一个函数只做好一件事,而且目的明确。这样的函数将来也不太可能随着需求的变化而修改,同时也非常方便编写单元测试。
返回多种类型的函数违反了 "单一职责 "原则。一个好的函数应始终提供一个稳定的返回值,以尽量减少调用者的处理成本。就像上面的例子,我们应该编写两个独立的函数 get_active_users()
和 get_user_by_id(user_id)
。
使用类型提示定义返回类型和显式参数声明。这样,集成开发环境就能帮助您进行自动补全和类型检查,从而在编辑的时候就发现错误。
例如:
def say_hello(name: str) -> str:
return "Hello, " + name
->
语法表示 say_hello()
函数将返回一个字符串。
假设在这种情况下,您的代码中有一个带有很多参数的函数 A,它非常适用。另一个函数 B 调用 A 做一些工作,就像下面这样:
def add(x, y):
return x + y
def sum(value):
# Calling add
return add(100, value)
在上述示例中,我们可以使用 functools 模块中的 partial()
函数来简化它。
import functools
sum = functools.partial(add, 100)
sum(200) # Output is 300
partial(func,*args,**kwargs)
以传入的函数为基础,使用变量参数构造一个新函数。在合并当前调用参数和构造参数后,对新函数的所有调用都将委托给原始函数。
因此,在使用部分函数时,可以将上面的求和函数定义修改为单行表达式,这样会更加简洁和直接。
有时,您可能需要编写同时返回结果和错误信息的函数:
def create_user(name):
if len(name) > MAX_LENGTH_OF_NAME:
return None, 'name of user is too long'
if len(CURRENT_USERS) > MAX_USERS_QUOTA:
return None, 'too many users'
return User(name=name), ''def create_from_input():
name = input()
user, err_msg = create_user(name)
if err_msg:
print(f'create user failed: {err_msg}')
else:
print(f'user<{name}> created')
在上例中,create_user
函数的作用是创建一个新的用户对象。同时,为了在发生错误时向调用者提供错误详细信息,它利用了多返回值特性,将错误信息作为第二个结果返回。
但在 Python 中,这并不是解决此类问题的最佳方法。因为这种做法会增加调用者处理错误的成本,尤其是当许多函数都遵循这种规范,并且存在多层调用时。
在这种情况下,使用异常来处理错误过程是更习以为常的做法。因此,上述代码可以重写为:
class CreateUserError(Exception):
"""Exception for user creation failure"""
pass
def create_user(name):
"""Create new user
:raises: CreateUserError
"""
if len(name) > MAX_LENGTH_OF_NAME:
raise CreateUserError('name of user is too long')
if len(CURRENT_USERS) > MAX_USERS_QUOTA:
raise CreateUserError('Too many users')
return User(name=name)
def create_for_input():
name = input()
try:
user = create_user(name)
except CreateUserError as e:
print(f'create user failed: {e}')
else:
print(f'user<{name}> created')
在使用抛出异常而不是返回结果、错误信息后,整个错误处理过程乍一看变化不大,但实际上在一些细节上有很大不同:
create_user
的一级调用者可以完全省略异常处理,将其留给上层处理。None 常被用来表示应该存在但缺少的东西,在 Python 中是独一无二的。由于 None 独特的虚无主义特质,它经常被用作函数返回值。
当我们使用 None 作为函数的返回值时,通常有以下三种情况。
list.append()
。re.search
和 re.match
。create_user_or_none()
。当函数返回调用自身时,这就是递归。递归在某些情况下是非常有用的编程技巧,但 Python 对递归的支持非常有限。
Python 语言不支持尾部递归优化。此外,Python 对递归级别的最大数量也有严格限制。所以要尽可能少写递归。如果您想用递归来解决问题,首先要考虑是否可以用循环来轻松替代递归。如果答案是肯定的,那就用循环重写。如果绝对必须使用递归,请考虑以下几点:
sys.getrecursionlimit()
。functools.lru_cache
。