Python 编程技巧

在开发解决方案时,我们倾向于将复杂的实际问题提炼为更小、更易于管理的子问题,然后使用函数来解决这些问题。函数是冗余代码的克星,也是我们抵御代码复杂性的最强防线。

大多数函数在编写过程中,关键是其返回值。函数产生结果的方式会极大地影响用户调用函数时的体验。掌握设计优雅返回结果的函数的艺术是制作高质量函数的基础。

不要返回多种类型

Python 是如此的灵活,以至于我们可以很容易地做到在其他语言中很难做到的事情。例如:让函数同时返回不同类型的结果。就像下面这样:

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)

使用类型提示定义返回类型

使用类型提示定义返回类型和显式参数声明。这样,集成开发环境就能帮助您进行自动补全和类型检查,从而在编辑的时候就发现错误。

例如:

python 复制代码
def say_hello(name: str) -> str:
    return "Hello, " + name

-> 语法表示 say_hello() 函数将返回一个字符串。

使用部分函数构造新函数

假设在这种情况下,您的代码中有一个带有很多参数的函数 A,它非常适用。另一个函数 B 调用 A 做一些工作,就像下面这样:

python 复制代码
def add(x, y):
    return x + y


def sum(value):
    # Calling add
    return add(100, value)

在上述示例中,我们可以使用 functools 模块中的 partial() 函数来简化它。

python 复制代码
import functools

sum = functools.partial(add, 100)
sum(200)  # Output is 300

partial(func,*args,**kwargs) 以传入的函数为基础,使用变量参数构造一个新函数。在合并当前调用参数和构造参数后,对新函数的所有调用都将委托给原始函数。

因此,在使用部分函数时,可以将上面的求和函数定义修改为单行表达式,这样会更加简洁和直接。

抛出异常而不是返回错误

有时,您可能需要编写同时返回结果和错误信息的函数:

python 复制代码
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 中,这并不是解决此类问题的最佳方法。因为这种做法会增加调用者处理错误的成本,尤其是当许多函数都遵循这种规范,并且存在多层调用时。

在这种情况下,使用异常来处理错误过程是更习以为常的做法。因此,上述代码可以重写为:

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.searchre.match
  • 作为代表错误结果的值:有时,当函数调用失败时,我们经常使用 None 作为默认返回值。如果是这种情况,请确保您的函数名称更有意义,例如 create_user_or_none()

限制递归的使用

当函数返回调用自身时,这就是递归。递归在某些情况下是非常有用的编程技巧,但 Python 对递归的支持非常有限。

Python 语言不支持尾部递归优化。此外,Python 对递归级别的最大数量也有严格限制。所以要尽可能少写递归。如果您想用递归来解决问题,首先要考虑是否可以用循环来轻松替代递归。如果答案是肯定的,那就用循环重写。如果绝对必须使用递归,请考虑以下几点:

  • 确保递归层小于 sys.getrecursionlimit()
  • 尽可能使用缓存,例如 functools.lru_cache
相关推荐
B站_计算机毕业设计之家29 分钟前
豆瓣电影数据采集分析推荐系统 | Python Vue Flask框架 LSTM Echarts多技术融合开发 毕业设计源码 计算机
vue.js·python·机器学习·flask·echarts·lstm·推荐算法
渣渣苏36 分钟前
Langchain实战快速入门
人工智能·python·langchain
lili-felicity1 小时前
CANN模型量化详解:从FP32到INT8的精度与性能平衡
人工智能·python
数据知道1 小时前
PostgreSQL实战:详解如何用Python优雅地从PG中存取处理JSON
python·postgresql·json
ZH15455891311 小时前
Flutter for OpenHarmony Python学习助手实战:面向对象编程实战的实现
python·学习·flutter
玄同7651 小时前
SQLite + LLM:大模型应用落地的轻量级数据存储方案
jvm·数据库·人工智能·python·语言模型·sqlite·知识图谱
User_芊芊君子1 小时前
CANN010:PyASC Python编程接口—简化AI算子开发的Python框架
开发语言·人工智能·python
白日做梦Q1 小时前
Anchor-free检测器全解析:CenterNet vs FCOS
python·深度学习·神经网络·目标检测·机器学习
喵手2 小时前
Python爬虫实战:公共自行车站点智能采集系统 - 从零构建生产级爬虫的完整实战(附CSV导出 + SQLite持久化存储)!
爬虫·python·爬虫实战·零基础python爬虫教学·采集公共自行车站点·公共自行车站点智能采集系统·采集公共自行车站点导出csv
喵手2 小时前
Python爬虫实战:地图 POI + 行政区反查实战 - 商圈热力数据准备完整方案(附CSV导出 + SQLite持久化存储)!
爬虫·python·爬虫实战·零基础python爬虫教学·地区poi·行政区反查·商圈热力数据采集