抵御代码复杂性使python函数更加Pythonic技巧示例详解
作者:简讯Alfred 爱生活爱扣钉
掌握设计优雅返回结果的函数的艺术
在开发解决方案时,我们倾向于将复杂的实际问题提炼为更小、更易于管理的子问题,然后使用函数来解决这些问题。函数是冗余代码的克星,也是我们抵御代码复杂性的最强防线。
大多数函数在编写过程中,关键是其返回值。函数产生结果的方式会极大地影响用户调用函数时的体验。掌握设计优雅返回结果的函数的艺术是制作高质量函数的基础。
不要返回多种类型
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
None 常被用来表示应该存在但缺少的东西,在 Python 中是独一无二的。由于 None 独特的虚无主义特质,它经常被用作函数返回值。
当我们使用 None 作为函数的返回值时,通常有以下三种情况。
• 作为操作类函数的默认返回值:当操作类函数不需要任何返回值时,通常会返回 None。此外,对于没有任何返回语句的函数,None 也是默认返回值。例如,list.append()
。
• 作为某个可能不存在的预期值:在 Python 标准库中,正则表达式模块下的函数 re 就属于这一类。例如,re.search
和 re.match
。
• 作为代表错误结果的值:有时,当函数调用失败时,我们经常使用 None 作为默认返回值。如果是这种情况,请确保您的函数名称更有意义,例如 create_user_or_none()
。
限制递归的使用
当函数返回调用自身时,这就是递归。递归在某些情况下是非常有用的编程技巧,但 Python 对递归的支持非常有限。
Python 语言不支持尾部递归优化。此外,Python 对递归级别的最大数量也有严格限制。所以要尽可能少写递归。如果您想用递归来解决问题,首先要考虑是否可以用循环来轻松替代递归。如果答案是肯定的,那就用循环重写。如果绝对必须使用递归,请考虑以下几点:
• 确保递归层小于 sys.getrecursionlimit()
。
• 尽可能使用缓存,例如 functools.lru_cache
。
以上就是抵御代码复杂性使python函数更加Pythonic技巧示例详解的详细内容,更多关于python函数Pythonic的资料请关注脚本之家其它相关文章!