在 C# 里写函数,就像写一份正式的合同——访问修饰符、返回类型、参数类型,一个都不能少。

在 Python 里写函数,就像发微信消息——def 加个名字,参数随意,类型什么的...看心情。

当我第一次写 Python 函数发现不需要声明返回类型的时候,我的内心是:"这代码...能跑?"

结果不仅能跑,还跑得飞快。Python 的函数定义就是这么"任性"。

函数定义基础对比

C# 版本:

// 标准写法:必须声明返回类型和参数类型
public string Greet(string name)
{
    return $"Hello, {name}!";
}

// 无返回值用 void
public void SayHello()
{
    Console.WriteLine("Hello!");
}

// 静态方法(最常见)
public static int Add(int a, int b)
{
    return a + b;
}

Python 版本:

# 标准写法:def 加个名字就行
def greet(name):
    return f"Hello, {name}!"

# 无返回值(不用写 void)
def say_hello():
    print("Hello!")

# Python 没有 static,直接定义就行
def add(a, b):
    return a + b

对比一下:

对比项 C# Python
定义关键字 返回类型 方法名() def 函数名():
返回类型 必须声明 不用声明(可选注解)
参数类型 必须声明 不用声明(可选注解)
无返回值 void 不用写
访问修饰符 public/private/protected 没有(约定 _ 开头为私有)
静态方法 static 关键字 不需要(模块级别就是)
分号和大括号 需要 不需要

C# 的函数定义像写合同,Python 的像发微信——"在吗?帮我算个数"。

参数类型大对比

默认参数

C# 版本:

// C# 4.0 开始支持默认参数
public string Greet(string name, string greeting = "Hello")
{
    return $"{greeting}, {name}!";
}

// 调用
Greet("张三");           // "Hello, 张三!"
Greet("张三", "你好");   // "你好, 张三!"

Python 版本:

# Python 一直支持默认参数
def greet(name, greeting="Hello"):
    return f"{greeting}, {name}!"

# 调用
greet("张三")           # "Hello, 张三!"
greet("张三", "你好")   # "你好, 张三!"

Python 的默认参数从诞生那天就有,C# 等了 8 个版本才加上。

可变参数

C# 版本:

// params 关键字
public int Sum(params int[] numbers)
{
    int total = 0;
    foreach (var n in numbers)
        total += n;
    return total;
}

// 调用
Sum(1, 2, 3);       // 6
Sum(1, 2, 3, 4, 5); // 15

// C# 没有 **kwargs,只能用命名参数或对象

Python 版本:

# *args 接收任意数量的位置参数
def sum_numbers(*args):
    return sum(args)

# 调用
sum_numbers(1, 2, 3)       # 6
sum_numbers(1, 2, 3, 4, 5) # 15

# **kwargs 接收任意数量的关键字参数
def print_info(**kwargs):
    for key, value in kwargs.items():
        print(f"{key}: {value}")

# 调用
print_info(name="张三", age=25, city="北京")

C# 有 params,Python 有 *args**kwargs——Python 的更灵活,C# 的更明确。

参数顺序规则

C# 版本:

// C# 的参数顺序很严格
public void Method(int a, string b = "default", int c = 0)
{
    // 可选参数必须在必需参数后面
}

// 调用时必须按顺序,或者用命名参数
Method(1);
Method(1, "hello");
Method(1, "hello", 10);
Method(1, c: 10);  // 跳过 b,用命名参数

Python 版本:

# Python 的参数顺序更灵活
def method(a, b="default", c=0):
    pass

# 调用时可以用关键字参数跳过前面的
method(1)
method(1, "hello")
method(1, "hello", 10)
method(1, c=10)  # 跳过 b,用关键字参数

C# 用"命名参数"跳过默认值,Python 直接用关键字参数,更直观。

仅限关键字/位置参数

C# 版本:

// C# 用参数名前加命名关键字(语法糖)
// 没有真正的"仅限关键字"或"仅限位置"参数

Python 版本:

# Python 3.8+ 支持仅限位置参数
def func(name, /, age=25):
    # name 只能按位置传,age 可以用关键字
    pass

# Python 3 支持仅限关键字参数
def func(name, *, age, city):
    # age 和 city 只能用关键字传
    pass

# 调用
func("张三", age=25, city="北京")
func("张三", 25, "北京")  # 报错:city 只能用关键字

Python 有 C# 没有的参数控制能力——/* 分隔位置和关键字参数。

函数参数类型完整对比

# Python 的参数类型完整示例
def func(a, b, /, c, d, *, e, f, **kwargs):
    """
    a, b: 仅位置参数(只能按位置传递)
    c, d: 位置或关键字参数(可以按位置或关键字传递)
    e, f: 仅限关键字参数(只能按关键字传递)
    **kwargs: 额外的关键字参数
    """
    print(f"a={a}, b={b}, c={c}, d={d}, e={e}, f={f}")
    print(f"kwargs={kwargs}")

# 调用方式
func(1, 2, 3, 4, e=5, f=6)           # 正确
func(1, 2, c=3, d=4, e=5, f=6)       # 正确
func(1, 2, 3, 4, 5, 6)               # 正确(c, d, e, f 都按位置)
func(1, 2, 3, 4, e=5, f=6, g=7)      # 正确(g 进入 kwargs)

# 以下调用会报错
# func(1, 2, 3, 4, 5, 6, 7)  # 错误:e, f 只能用关键字
# func(a=1, b=2, c=3, d=4)    # 错误:a, b 只能用位置

C# 的参数类型:

// C# 的参数类型
void Func(int a, int b, int c = 0, int d = 0)
{
    // a, b: 必需参数
    // c, d: 可选参数
}

// C# 没有真正的"仅限关键字"或"仅限位置"参数
// 但可以用命名参数模拟
void CallExamples()
{
    Func(1, 2);           // 正确
    Func(1, 2, 3);        // 正确
    Func(1, 2, c: 3);     // 正确(命名参数)
    Func(1, 2, d: 4);     // 正确(跳过 c)
    // Func(a: 1, b: 2);  // 错误:a, b 是必需参数,必须按位置
}
参数类型 C# Python
必需参数 void Func(int a) def func(a):
可选参数 void Func(int a = 0) def func(a=0):
仅位置参数 不支持 def func(a, /):
仅关键字参数 不支持 def func(*, a):
可变位置参数 params int[] args def func(*args):
可变关键字参数 不支持 def func(**kwargs):
命名参数 支持 支持

Python 的参数控制能力比 C# 强大得多——可以精确控制每个参数是位置传递还是关键字传递。

返回值对比

C# 版本:

// 单个返回值
public int Add(int a, int b)
{
    return a + b;
}

// 多个返回值(用 out 参数)
public void Divide(int a, int b, out int quotient, out int remainder)
{
    quotient = a / b;
    remainder = a % b;
}

// 多个返回值(用元组,C# 7+)
public (int Sum, int Product) Calculate(int a, int b)
{
    return (a + b, a * b);
}

// 调用
var result = Calculate(3, 4);
Console.WriteLine(result.Sum);     // 7
Console.WriteLine(result.Product); // 12

Python 版本:

# 单个返回值
def add(a, b):
    return a + b

# 多个返回值(天然支持元组)
def divide(a, b):
    quotient = a // b
    remainder = a % b
    return quotient, remainder  # 返回元组

# 调用
q, r = divide(10, 3)  # 直接解包
print(q, r)  # 3 1

# 多个返回值(更灵活)
def calculate(a, b):
    return a + b, a * b  # 不用括号也行

sum_val, product = calculate(3, 4)

C# 的多返回值要专门的语法(out 参数或元组),Python 天生就是多返回值——返回的其实是个元组。

函数作为参数

C# 版本:

// 用委托
public int Apply(Func<int, int> func, int value)
{
    return func(value);
}

// 用 lambda
int result = Apply(x => x * 2, 5);  // 10

// 定义委托类型
public delegate int Transform(int x);

public int ApplyTransform(Transform func, int value)
{
    return func(value);
}

Python 版本:

# 函数直接作为参数(不需要委托)
def apply(func, value):
    return func(value)

# 用 lambda
result = apply(lambda x: x * 2, 5)  # 10

# 用普通函数
def double(x):
    return x * 2

result = apply(double, 5)  # 10

C# 需要委托或 Func/Action 类型,Python 直接传函数名就行——Python 的函数就是一等公民。

文档字符串 vs XML 注释

C# 版本:

/// <summary>
/// 计算两个数的和
/// </summary>
/// <param name="a">第一个数</param>
/// <param name="b">第二个数</param>
/// <returns>两数之和</returns>
public int Add(int a, int b)
{
    return a + b;
}

Python 版本:

def add(a, b):
    """
    计算两个数的和

    Args:
        a: 第一个数
        b: 第二个数

    Returns:
        两数之和
    """
    return a + b

# 查看文档
help(add)
print(add.__doc__)

C# 用 XML 注释,Python 用文档字符串——Python 的文档字符串可以直接通过 __doc__ 属性访问。

设计哲学

C# 的函数定义是"显式契约"——类型声明就是合同条款,编译器帮你检查,适合大型项目和团队协作。

Python 的函数定义是"鸭子契约"——"只要能用就行",类型注解是可选的,适合快速开发。

C# 的函数定义像是写法律文书,每一条都清清楚楚; Python 的函数定义像是发微信消息,"帮我算个数"就够了。

坑点提醒

默认参数是对象引用——Python 的经典坑:

# 危险!默认参数是可变对象
def append_to(item, lst=[]):
    lst.append(item)
    return lst

print(append_to(1))  # [1]
print(append_to(2))  # [1, 2]  不是 [2]!

# 正确写法
def append_to(item, lst=None):
    if lst is None:
        lst = []
    lst.append(item)
    return lst

C# 的默认参数是编译时常量——不会有这个问题:

// C# 的默认参数在编译时确定
public void AppendTo(string item, List<string> lst = null)
{
    lst ??= new List<string>();
    lst.Add(item);
}

可变参数顺序——*args 和 **kwargs 的位置:

# 正确顺序:普通参数 → *args → keyword-only → **kwargs
def func(a, b, *args, key=None, **kwargs):
    pass

函数名约定——Python 用下划线命名:

# C# 风格(Python 也能用,但不符合约定)
def GetUserName():  # 不推荐

# Python 风格
def get_user_name():  # 推荐
    pass

迁移指南:C# 开发者最容易犯的错

这一章节的内容比较特殊,迁移指南会在后续文章中详细讨论。

一句话总结

C# 的函数定义是"先签合同再干活",Python 的函数定义是"先干活再说"。

下一篇咱们来聊聊 Lambda 表达式——C# 的 x => x * 2 和 Python 的 lambda x: x * 2,看似相似但用法大不同。


📦 示例代码:C# 转 Python 全系列配套练习代码(含 48 章示例)

💬 欢迎点赞、收藏、转发,你的支持是我持续创作的动力!

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐