上周帮一个 C# 团队做 Python 培训,有个学员写了个函数:
defcalculate_price(quantity, unit_price):return quantity * unit_price
然后他问我:"返回类型写在哪?"
我说不用写。他不信,说"这编译器怎么知道返回的是 int 还是 float?"
我说 Python 没有编译器。他沉默了五秒,然后说:"那类型安全怎么办?"
这个问题,每个从 C# 转 Python 的人都会问。今天聊聊函数定义——Python 的"极简主义"到底能走到哪一步。
函数定义:告别那些仪式
C# 定义一个函数,仪式感拉满:
publicstaticintAdd(int a, int b){return a + b;}
Python 呢?
defadd(a, b):return a + b
不需要 public,不需要 static,不需要 int,不需要 {},甚至不需要分号。
这不是偷懒,是设计哲学——Python 认为"代码是写给人看的",类型信息应该在需要时才写。
| | |
|---|
| 返回类型 方法名() | def 函数名(): |
| | |
| | |
| | |
| static | |
参数:Python 的灵活度碾压 C#
默认参数
C# 从 4.0 才支持默认参数,Python 从出生就有:
defgreet(name, greeting="你好"):returnf"{greeting}, {name}!"greet("张三") # "你好, 张三!"greet("张三", "早上好") # "早上好, 张三!"
关键字参数
C# 的命名参数需要特殊语法,Python 直接用关键字:
defcreate_user(name, age, email=None, phone=None):return {"name": name, "age": age, "email": email, "phone": phone}# 只传必要参数user1 = create_user("张三", 25)# 跳过中间参数user2 = create_user("李四", 30, phone="13800138000")
C# 要实现"跳过中间参数",得用命名参数:
var user2 = Create(name: "李四", age: 30, phone: "13800138000");
Python 的关键字参数更自然——你不需要记住参数顺序,按名字传就行。
万能参数
C# 有 params,但只能接收固定类型的可变参数。Python 有 *args 和 **kwargs:
deflog_message(level, *args, **kwargs): print(f"[{level}]")for msg in args: print(f" {msg}")for key, value in kwargs.items(): print(f" {key}={value}")log_message("INFO", "用户登录", "IP=192.168.1.1", user="张三", action="login")
C# 没有 **kwargs 的等价物——如果你想传递动态的命名参数,得用 IDictionary<string, object> 然后手动处理。
仅位置/关键字参数
Python 3.8+ 引入了 / 和 * 分隔符,精确控制参数传递方式:
deffunc(a, b, /, c, d, *, e, f):# a, b: 只能按位置传# c, d: 可以按位置或关键字传# e, f: 只能按关键字传pass
C# 没有这个能力。这是 Python 参数控制的"终极形态"。
多返回值:告别 out 参数
C# 的 out 参数是出了名的啰嗦:
publicstaticvoidDivide(int a, int b, outint quotient, outint remainder){ quotient = a / b; remainder = a % b;}int q, r;Divide(10, 3, out q, out r);
Python 直接 return 两个值:
defdivide(a, b):return a // b, a % bquotient, remainder = divide(10, 3)
原理是:Python 把返回值自动打包成元组,调用方自动解包。
函数是一等公民
在 Python 里,函数就是普通对象——可以赋值、可以传参、可以当返回值:
defapply(func, value):return func(value)# 传函数名result = apply(lambda x: x * 2, 5) # 10# 函数作为字典值operations = {"add": lambda a, b: a + b,"sub": lambda a, b: a - b,"mul": lambda a, b: a * b,}result = operations["add"](3, 4) # 7
C# 需要委托或 Func<T> 类型才能实现同样功能:
publicintApply(Func<int, int> func, intvalue){return func(value);}int result = Apply(x => x * 2, 5);
Python 不需要声明委托类型——直接传函数名就行。
文档字符串:比 XML 注释更实用
C# 用 XML 注释:
///<summary>/// 计算两数之和///</summary>///<param name="a">第一个数</param>///<param name="b">第二个数</param>publicintAdd(int a, int b);
Python 用文档字符串:
defadd(a, b):""" 计算两数之和 Args: a: 第一个数 b: 第二个数 Returns: 两数之和 """return a + b# 直接访问文档help(add)print(add.__doc__)
Python 的文档字符串是运行时对象,可以通过 __doc__ 属性直接访问——比 C# 的 XML 注释更灵活。
默认参数的坑:可变对象
这是 Python 最经典的坑——默认参数是可变对象:
# 危险!默认参数是列表(可变对象)defappend_to(item, lst=[]): lst.append(item)return lstprint(append_to(1)) # [1]print(append_to(2)) # [1, 2] 不是 [2]!
原因:默认参数在函数定义时创建,之后每次调用都复用同一个对象。
正确写法:
defappend_to(item, lst=None):if lst isNone: lst = [] lst.append(item)return lst
C# 没有这个问题——C# 的默认参数是编译时常量。
我的真实感受
从 C# 转到 Python 后,函数定义是最让我"放松"的地方。
不再需要写返回类型、参数类型、访问修饰符——def add(a, b): return a + b 就够了。
但"放松"不等于"随意"。Python 3.5+ 引入了类型注解,让你在需要时可以添加类型信息:
defadd(a: int, b: int) -> int:return a + b
类型注解是可选的——你可以选择在关键函数上添加类型信息,让 IDE 和 mypy 帮你检查。
Python 的函数定义是"先干活再说"——不需要签合同就能开工。但如果你想要类型安全,类型注解随时可以加上。
下一篇聊聊 Lambda 表达式——Python 的 lambda 只能写一行,但这一行能干的事比你想的多。