Skip to main content

NoNone

拒绝隐式 None,拥抱 Rust 风格的显式错误处理

zread CI PyPI version Python versions License: MIT


核心特性

  • 🦀 Rust 体验: 将成熟的 Result[T, E] 模式带入 Python
  • 开发体验: 提供 @catchtry_catch 无缝迁移旧代码
  • 🛡️ 类型安全: 深度集成类型注解,支持 mypypyright
  • 🪶 轻量高效: 零依赖,使用 dataclass__slots__ 优化内存占用
  • 🧩 模式匹配: 专为 Python 3.10+ match-case 优化

为什么选择 NoNone?告别 Python 错误处理的三大痛点

在传统的 Python 开发中,我们经常面临以下痛点:

痛点 1:错误处理的缺失与运行时崩溃

💡 完整示例代码: examples/01_painpoint1_error_handling.py

❌ 传统写法 - 无显式错误处理

def divide(a: float, b: float) -> float:
    return a / b
    # ✅ 极简,零开销
    # ❌ 除零时程序崩溃
    # 🎯 适用:确信 b!=0、快速原型、脚本

result = divide(10, 0)  # 💥 程序直接崩溃, 抛出 ZeroDivisionError
print(f"结果: {result}")

❌ 传统写法 - 出错提前返回 None

def divide(a: float, b: float) -> float | None:
    if b == 0:
        return None
    return a / b

result = divide(10, 0)
print(result * 2)  # 💥 如果 result 是 None,这里会抛出 TypeError!

✅ NoNone 写法 - 类型安全

from nonone import Result, ok, err, Ok, Err

def divide_safe(a: float, b: float) -> Result[float, str]:
    if b == 0:
        return err("除数不能为零")
    return ok(a / b)

result = divide_safe(10, 0)

# Python 解释器和 IDE 会强制你处理两种情况,无法忽略错误
match result:
    case Ok(value):
        print(f"结果: {value * 2}")  # ✅ 安全,value 一定存在
    case Err(msg):
        print(f"错误: {msg}")

痛点 2:异常处理冗长且容易遗漏

💡 完整示例代码: examples/02_painpoint2_exception_handling.py

❌ 传统写法 - 异常处理复杂且容易遗漏

def parse_and_calculate(input_str: str) -> float:
    try:
        num = float(input_str)
        if num <= 0:
            raise ValueError("数字必须大于0")
        return 100 / num
    except ValueError as e:
        # 需要手动处理每种异常类型
        print(f"解析失败: {e}")
        raise  # 或者返回默认值,但会丢失异常信息

def another_operation(data: str) -> float:
    try:
        # 一些复杂操作
        return float(data) * 2
    except ValueError as e:
        print(f"操作失败: {e}")
        raise

# 调用方需要嵌套 try-catch,代码冗长
try:
    result1 = parse_and_calculate("abc")
    result2 = another_operation("xyz")
    print(f"结果1: {result1}, 结果2: {result2}")
except ValueError as e:
    print(f"值错误: {e}")
except ZeroDivisionError as e:
    print(f"除零错误: {e}")
except Exception as e:
    print(f"未知错误: {e}")

# 问题:
# 1. 异常处理代码冗长,需要多层嵌套
# 2. 容易遗漏某些异常类型
# 3. 错误处理逻辑分散,难以统一管理

✅ NoNone 写法 - 统一错误处理

from nonone import catch, try_catch

# 方式 A: 使用装饰器 - 一键转换现有函数
@catch
def dangerous_divide(a: float, b: float) -> float:
    return a / b  # 可能抛出 ZeroDivisionError

result = dangerous_divide(10, 0)
# 自动包装为: Err(ZeroDivisionError(...))

# 方式 B: 临时调用 - 无需修改原函数定义
def parse_and_calculate_unsafe(input_str: str) -> float:
    num = float(input_str)
    if num <= 0:
        raise ValueError("数字必须大于0")
    return 100 / num

# 直接包装调用,立即获得 Result
result = try_catch(parse_and_calculate_unsafe, "abc")

match result:
    case Ok(value):
        print(f"计算成功: {value}")
    case Err(e):
        print(f"处理失败: {type(e).__name__}: {e}")
# ✅ 所有异常都被统一捕获并转换为 Err,不会遗漏

痛点 3:繁琐的嵌套 None 判断

💡 完整示例代码: examples/03_painpoint3_nested_none.py

❌ 传统写法 - 嵌套地狱

def get_user(user_id: int) -> User | None:...

def get_score_record(user: User) -> ScoreRecord | None:...

def extract_score(record: ScoreRecord) -> float | None:...

def get_user_score_traditional(user_id: int) -> float | None:
    """获取用户分数,需要三层检查"""
    # 第1层: 检查用户是否存在
    user = get_user(user_id)
    if user is None:
        return None
    
    # 第2层: 检查用户是否有分数记录
    score_record = get_score_record(user)
    if score_record is None:
        return None
    
    # 第3层: 检查分数是否有效
    score = extract_score(score_record)
    if score is None:
        return None
    
    return score

# 使用时的嵌套判断
score = get_user_score_traditional(123)
if score is not None:
    print(f"用户分数: {score}")
else:
    print("无法获取用户分数")

✅ NoNone 写法 - 扁平化链式调用

from nonone import ok, err, Result, Ok, Err

def get_user_safe(user_id: int) -> Result[User, str]:...

def get_score_record_safe(user: User) -> Result[ScoreRecord, str]:...

def extract_score_safe(record: ScoreRecord) -> Result[float, str]:...

def get_user_score_nonone(user_id: int) -> Result[float, str]:
    """使用链式调用获取用户分数"""
    return (
        get_user_safe(user_id)            # 获取用户
        .and_then(get_score_record_safe)  # 获取分数记录
        .and_then(extract_score_safe)     # 提取分数
    )

# 使用时只需要一次 match
match get_user_score_nonone(123):
    case Ok(score):
        print(f"用户分数: {score}")          # ✅ 输出: 用户分数: 95.5
    case Err(msg):
        print(f"获取失败: {msg}")            # 任何一步出错都会到这里

安装

使用 uv(推荐)或 pip 安装 NoNone

uv pip install nonone

或者

pip install nonone

快速上手

💡 重要提示:大写 vs 小写

NoNone 提供了两套 API,用途不同但很简单:

场景 使用 示例
构造实例 小写 ok() / err() return ok(42)
模式匹配 大写 Ok / Err case Ok(value):
类型注解 大写 Result -> Result[int, str]

简单记忆:🏗️ 建造时用小写的(函数),🔍 检查时用大的(类)


快速上手:5分钟体验 NoNone

💡 完整示例代码: examples/04_quick_start.py

# quick_start.py
from nonone import ok, err, Result, Ok, Err

# 1. 基本构造
result = ok(42)           # Ok(42)
error = err("出错了")     # Err("出错了")

# 2. 链式调用
def parse_number(s: str) -> Result[float, str]:
    try:
        return ok(float(s))
    except ValueError:
        return err(f"'{s}' 不是有效数字")

def validate_positive(num: float) -> Result[float, str]:
    if num <= 0:
        return err("数字必须大于0")
    return ok(num)

def calculate(num: float) -> Result[float, str]:
    return ok(num * 2)

# 组合多个操作
def process_input(input_str: str) -> Result[float, str]:
    return (
        parse_number(input_str)      # 解析字符串
        .and_then(validate_positive) # 验证正数
        .and_then(calculate)         # 计算
    )

# 3. 模式匹配处理结果
result = process_input("10")
match result:
    case Ok(value):
        print(f"✅ 结果: {value}")
    case Err(error):
        print(f"❌ 错误: {error}")

这就是 NoNone 的核心用法:构造 → 链式调用 → 模式匹配


进阶用法:强大的 match-case

💡 完整示例代码: examples/05_advanced_pattern_matching.py

NoNone 基于 dataclass 构建,可以充分利用 Python 模式匹配的强大功能。

🎯 使用 Guard(条件过滤)

case 中添加 if 条件,实现更精细的控制:

from nonone import ok, err, Ok, Err

def divide(a: float, b: float):
    if b == 0:
        return err("除数不能为零")
    return ok(a / b)

result = divide(100, 2)

match result:
    case Ok(val) if val > 10:
        print(f"得到一个较大的数: {val}")     # ✅ 输出: 得到一个较大的数: 50.0
    case Ok(val):
        print(f"得到一个较小的数: {val}")
    case Err(msg):
        print(f"计算失败: {msg}")

📦 嵌套对象解构

如果 Ok 里面包裹的是 dataclass,可以直接在 case 中解构其属性:

from dataclasses import dataclass
from nonone import ok, err, Ok, Err, Result

@dataclass
class CalculationResult:
    value: float
    operation: str
    success: bool

def divide_with_info(a: float, b: float) -> Result[CalculationResult, str]:
    if b == 0:
        return err("除数不能为零")
    return ok(CalculationResult(
        value=a / b,
        operation="division",
        success=True
    ))

match divide_with_info(10, 2):
    # 直接解构内部对象的特定属性
    case Ok(CalculationResult(operation="division", value=result_value)):
        print(f"结果: {result_value}")           # ✅ 输出: 除法结果: 5.0
    case Ok(CalculationResult(value=result_value)):
        print(f"其他运算结果: {result_value}")
    case Err(error):
        print(f"计算失败: {error}")

这种深度解构让你可以在一行代码中完成类型检查 + 属性提取 + 条件判断,非常优雅!


API 参考

类型定义

Result: TypeAlias = Ok[T, E] | Err[T, E]

构造器

ok(value: T) -> Ok[T, Any]

创建成功的 Result 实例。

参数

  • value: 成功时返回的值

返回值

  • Ok[T, Any] - 成功的 Result,错误类型为 Any

示例

result = ok(42)        # Ok(42)
result = ok("success") # Ok("success")

err(error: E) -> Err[Any, E]

创建失败的 Result 实例。

参数

  • error: 错误信息或异常对象

返回值

  • Err[Any, E] - 失败的 Result,成功类型为 Any

示例

result = err("出错了")     # Err("出错了")
result = err(ValueError("无效值")) # Err(ValueError("无效值"))

装饰器和工具函数

@catch

装饰器,自动捕获函数中的异常并返回 Result。

适用场景

  • 将现有函数一键转换为返回 Result 的函数
  • 统一异常处理,避免遗漏异常类型

示例

@catch
def divide(a: float, b: float) -> float:
    return a / b

result = divide(10, 0)  # Err(ZeroDivisionError(...))

try_catch(func: Callable[P, T], *args, **kwargs) -> Result[T, Exception]

立即执行函数并返回 Result,无需修改原函数定义。

参数

  • func: 要执行的函数
  • *args, **kwargs: 传递给函数的参数

返回值

  • Result[T, Exception] - 执行结果

示例

def parse_number(s: str) -> float:
    return float(s)

result = try_catch(parse_number, "abc")  # Err(ValueError(...))

状态检查方法

is_ok() -> bool

检查是否为成功状态。

返回值

  • True - 如果是 Ok
  • False - 如果是 Err

示例

ok(42).is_ok()      # True
err("error").is_ok() # False

is_err() -> bool

检查是否为错误状态。

返回值

  • True - 如果是 Err
  • False - 如果是 Ok

示例

ok(42).is_err()      # False
err("error").is_err() # True

值提取方法(谨慎使用)

unwrap() -> T

提取成功值,如果是 Err 则抛出 UnwrapError 异常。

警告

  • 仅在确定结果是 Ok 时使用
  • 生产代码建议使用模式匹配或链式调用

示例

ok(42).unwrap()        # 42
err("error").unwrap()  # 抛出 UnwrapError

unwrap_err() -> E

提取错误值,如果是 Ok 则抛出 UnwrapError 异常。

示例

err("error").unwrap_err() # "error"
ok(42).unwrap_err()      # 抛出 UnwrapError

unwrap_or(default: T) -> T

返回成功值或默认值。

参数

  • default: 当结果是 Err 时返回的默认值

示例

ok(42).unwrap_or(0)        # 42
err("error").unwrap_or(0)   # 0

unwrap_or_else(op: Callable[[E], T]) -> T

返回成功值或用错误值调用函数的结果。

参数

  • op: 接收错误值并返回默认值的函数

示例

ok(42).unwrap_or_else(str)           # 42
err("error").unwrap_or_else(len)      # 5 (字符串长度)

expect(msg: str) -> T

返回成功值,如果是 Err 则抛出带自定义消息的 UnwrapError

参数

  • msg: 自定义错误消息

示例

ok(42).expect("应该成功")        # 42
err("error").expect("应该成功")  # 抛出 UnwrapError("应该成功: error")

链式操作方法

map(f: Callable[[T], U]) -> Result[U, E]

对成功值应用函数,保持错误类型不变。

参数

  • f: 接收成功值并返回新值的函数

返回值

  • Ok(f(value)) - 如果是 Ok
  • Err(error) - 如果是 Err(无操作)

示例

ok(42).map(str)         # Ok("42")
err("error").map(str)   # Err("error")

map_err(f: Callable[[E], F]) -> Result[T, F]

对错误值应用函数,保持成功类型不变。

参数

  • f: 接收错误值并返回新错误值的函数

返回值

  • Ok(value) - 如果是 Ok(无操作)
  • Err(f(error)) - 如果是 Err

示例

ok(42).map_err(str.upper)      # Ok(42)
err("error").map_err(str.upper) # Err("ERROR")

and_then(f: Callable[[T], Result[U, E]]) -> Result[U, E]

链式调用,用成功值调用返回 Result 的函数。

参数

  • f: 接收成功值并返回新 Result 的函数

返回值

  • f(value) - 如果是 Ok
  • Err(error) - 如果是 Err(短路,不调用 f

示例

def double(x: int) -> Result[int, str]:
    return ok(x * 2)

ok(42).and_then(double)        # Ok(84)
err("error").and_then(double)  # Err("error")

try_and_then(f: Callable[[T], U]) -> Result[U, Exception | E]

安全链式调用,将不返回 Result 的函数包装为 Result。

参数

  • f: 接收成功值并可能抛出异常的函数

返回值

  • Ok(f(value)) - 如果是 Ok 且函数成功
  • Err(exception) - 如果是 Ok 且函数抛出异常
  • Err(error) - 如果是 Err(无操作)

示例

def risky_divide(x: int) -> float:
    return 100 / x

ok(2).try_and_then(risky_divide)      # Ok(50.0)
ok(0).try_and_then(risky_divide)      # Err(ZeroDivisionError(...))
err("error").try_and_then(risky_divide) # Err("error")

or_else(f: Callable[[E], Result[T, F]]) -> Result[T, F]

错误恢复,用错误值调用函数尝试恢复。

参数

  • f: 接收错误值并返回新 Result 的函数

返回值

  • Ok(value) - 如果是 Ok(无操作)
  • f(error) - 如果是 Err

示例

def recover(error: str) -> Result[int, str]:
    return ok(0)

ok(42).or_else(recover)        # Ok(42)
err("error").or_else(recover)  # Ok(0)

inspect(func: Callable[[T | E], Any]) -> Result[T, E]

执行副作用函数后返回自身,用于调试和日志。

参数

  • func: 对值执行副作用的函数

返回值

  • 原始的 Result 实例

示例

# 对成功值执行副作用
ok(42).inspect(print)  # 打印 42,返回 Ok(42)

# 对错误值执行副作用  
err("error").inspect(print)  # 打印 "error",返回 Err("error")

异常类

UnwrapError

当在错误状态下调用 unwrap()unwrap_err() 时抛出的异常。

继承关系

  • RuntimeError

许可协议

本项目基于 MIT License 开源.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

nonone-0.1.10.tar.gz (9.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

nonone-0.1.10-py3-none-any.whl (10.4 kB view details)

Uploaded Python 3

File details

Details for the file nonone-0.1.10.tar.gz.

File metadata

  • Download URL: nonone-0.1.10.tar.gz
  • Upload date:
  • Size: 9.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for nonone-0.1.10.tar.gz
Algorithm Hash digest
SHA256 68d22cab604c5b0724b8834a8f4742081707799450c31b19aac02df5b1f27543
MD5 62178a65cd80fafba5b0afb3f8f83300
BLAKE2b-256 b9c976e48d198ee8dd598dac2812a66200f39c59d191e59c84b881b55c3a3661

See more details on using hashes here.

Provenance

The following attestation bundles were made for nonone-0.1.10.tar.gz:

Publisher: test_and_publish.yml on DBinK/NoNone

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file nonone-0.1.10-py3-none-any.whl.

File metadata

  • Download URL: nonone-0.1.10-py3-none-any.whl
  • Upload date:
  • Size: 10.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for nonone-0.1.10-py3-none-any.whl
Algorithm Hash digest
SHA256 e02d9917f85889fff724f0c00ca78df0b73a30be2ceaa45701093010abd597a1
MD5 82750935b8e6301151d07a7e7f73d0c4
BLAKE2b-256 d12873b6d62fa516b6d80271eb97f31c3955c09b9a07f7a98d54be503351b875

See more details on using hashes here.

Provenance

The following attestation bundles were made for nonone-0.1.10-py3-none-any.whl:

Publisher: test_and_publish.yml on DBinK/NoNone

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.1.10 This release

2 files

0.1.9

2 files

0.1.8

2 files

0.1.6

2 files

0.1.5

2 files

0.1.3

2 files

0.1.2

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page