前言
Python 的函数比 Java 灵活,默认参数、关键字参数、lambda 能替代不少重载和接口写法;面向对象上,多继承、MRO、动态属性又与 Java 差异明显。下面按函数、类、继承与 MRO 的顺序,把原文涉及的语法、代码和易错点整理成可直接在解释器里验证的实践笔记。
一、函数定义、返回值和默认参数陷阱
Python 用 def 定义函数,不强制声明返回类型和参数类型,但支持类型注解。函数是一等公民,可赋值、传参、返回;Java 的方法必须属于类。返回多个值本质是返回元组,调用处可解包。- def add(a: int, b: int) -> int:
- '''两数相加'''
- return a + b
- result = add(1, 2)
- print(result) # 3
- def divide(a, b):
- quotient = a // b
- remainder = a % b
- return quotient, remainder
- q, r = divide(10, 3)
- print(f'商: {q}, 余: {r}') # 商: 3, 余: 1
复制代码 默认参数最容易踩的坑是使用可变对象。默认参数在函数定义时只创建/求值一次,不是每次调用重新创建:- def append_to(item, lst=[]):
- lst.append(item)
- return lst
- print(append_to(1)) # [1]
- print(append_to(2)) # [1, 2]
- print(append_to(3)) # [1, 2, 3]
- def append_to_fixed(item, lst=None):
- if lst is None:
- lst = []
- lst.append(item)
- return lst
复制代码 正确写法是用 None 作为哨兵,在函数体内再创建列表。
二、位置参数、关键字参数与 *args/**kwargs
默认参数可以替代 Java 中多个重载方法。调用时,位置参数按顺序传,关键字参数按名字传,顺序随意;混合使用时位置参数在前,关键字参数在后。Python 3.8+ 还支持 / 和 * 来强制位置参数、强制关键字参数:- def greet(name: str, greeting: str = 'Hello', punctuation: str = '!') -> str:
- return f'{greeting}, {name}{punctuation}'
- print(greet('Alice')) # Hello, Alice!
- print(greet('Bob', 'Hi')) # Hi, Bob!
- print(greet('Charlie', 'Hey', '.')) # Hey, Charlie.
- def create_user(name, age, city='Unknown'):
- print(f'{name}, {age}岁, 来自{city}')
- create_user('Alice', 25, 'Beijing')
- create_user('Bob', city='Shanghai', age=30)
- create_user('Charlie', 28, city='Shenzhen')
- def func(a, b, /, c, d, *, e, f):
- pass
复制代码 / 前面的 a、b 只能按位置传;中间的 c、d 位置或关键字都行;* 后面的 e、f 必须用关键字传。
*args 接收任意数量位置参数并打包成元组,**kwargs 接收任意数量关键字参数并打包成字典:- def print_info(name, *args, **kwargs):
- print(f'Name: {name}')
- print(f'Args: {args}')
- print(f'Kwargs: {kwargs}')
- print_info('Alice', 25, 'Beijing', hobby='coding', level='senior')
- # Args: (25, 'Beijing')
- # Kwargs: {'hobby': 'coding', 'level': 'senior'}
复制代码 常见错误:func(1, a=2) 会报 TypeError: func() got multiple values for argument 'a',因为 1 已经赋给 a。另一个容易忽略的点是,func(1, 2, 3, b=4) 中 b=4 不会报未知参数,而是进入 **kwargs。
三、Lambda 的适用场景与闭包陷阱
lambda 是匿名函数简写,只能写一个表达式,不能写多行逻辑。它最常见的场景是排序 key,例如按字典字段、字符串长度或多条件排序:- double = lambda x: x * 2
- print(double(5)) # 10
- students = [
- {'name': 'Alice', 'age': 25},
- {'name': 'Bob', 'age': 20},
- {'name': 'Charlie', 'age': 23}
- ]
- students.sort(key=lambda s: s['age'])
- print(students)
- words = ['banana', 'pie', 'Washington', 'book']
- words.sort(key=lambda w: len(w))
- print(words) # ['pie', 'book', 'banana', 'Washington']
- students.sort(key=lambda s: (s['age'], s['name']))
复制代码 选择 lambda 还是 def:排序 key、map/filter 回调适合 lambda;逻辑超过一行、需要复用、需要文档字符串或异常处理时用 def。
闭包陷阱要特别注意。下面代码想得到 f(0)=0、f(1)=1……实际全是 4:- funcs = [lambda: i for i in range(5)]
- print([f() for f in funcs]) # [4, 4, 4, 4, 4]
- funcs = [lambda i=i: i for i in range(5)]
- print([f() for f in funcs]) # [0, 1, 2, 3, 4]
复制代码 原因是 lambda 捕获的是变量 i 的引用,不是当时的值;循环结束后 i=4。用默认参数 i=i 绑定当前值即可修复。Java 的 lambda 要求捕获 effectively final 变量,因此不会出现同样问题。
四、类、self、实例属性与类属性
Python 用 class 定义类,__init__ 是构造方法,self 类似 Java 的 this,但必须显式写,实例方法第一个参数必须是 self。创建对象不需要 new。- class Student:
- school = '清华大学'
- def __init__(self, name: str, age: int):
- self.name = name
- self.age = age
- self._score = 0
- def introduce(self) -> str:
- return f'{self.name}, {self.age}岁, 来自{Student.school}'
- def study(self, course: str):
- print(f'{self.name}正在学习{course}')
- stu = Student('Alice', 20)
- print(stu.introduce())
- stu.study('Python')
复制代码 类属性由所有实例共享,实例属性每个实例独有。读属性时先找实例再找类;但通过实例赋值不会修改类属性,而是创建同名实例属性,产生遮蔽:- class Counter:
- count = 0
- def __init__(self, name):
- self.name = name
- Counter.count += 1
- c1 = Counter('A')
- c2 = Counter('B')
- print(Counter.count) # 2
- print(c1.count) # 2
- print(c2.count) # 2
- c1.count = 100
- print(c1.count) # 100,实例属性
- print(c2.count) # 2,类属性未变
- print(Counter.count) # 2,类属性未变
复制代码 忘记写 self 的典型报错是 TypeError: wrong_method() takes 0 positional arguments but 1 was given。因为 Python 自动把实例作为第一个参数传入,但函数没有接收。
五、@staticmethod、@classmethod 与 @property
@staticmethod 不需要 self 或 cls,适合纯工具方法;@classmethod 第一个参数是 cls,可访问类属性,常用于工厂方法,并且在继承时 cls 会变成子类:- class MathUtil:
- @staticmethod
- def add(a: int, b: int) -> int:
- return a + b
- @staticmethod
- def is_even(n: int) -> bool:
- return n % 2 == 0
- print(MathUtil.add(1, 2)) # 3
- print(MathUtil.is_even(4)) # True
- class Date:
- def __init__(self, year: int, month: int, day: int):
- self.year = year
- self.month = month
- self.day = day
- @classmethod
- def from_string(cls, date_str: str) -> 'Date':
- year, month, day = map(int, date_str.split('-'))
- return cls(year, month, day)
- def __str__(self):
- return f'{self.year}-{self.month:02d}-{self.day:02d}'
- d1 = Date.from_string('2026-09-18')
- print(d1) # 2026-09-18
复制代码 实例方法能访问实例属性和类属性;@classmethod 能访问类属性;@staticmethod 都不能直接访问。Java 的 static 方法大致对应 @staticmethod,@classmethod 在 Java 中没有直接对应。
@property 把方法变成属性访问,适合在赋值时加校验、暴露只读计算属性:- class Student:
- def __init__(self, name: str, score: int):
- self.name = name
- self._score = score
- @property
- def score(self) -> int:
- print(' [调用getter]')
- return self._score
- @score.setter
- def score(self, value: int):
- if not 0 <= value <= 100:
- raise ValueError('分数必须在0-100之间')
- self._score = value
- @property
- def grade(self) -> str:
- if self._score >= 90:
- return 'A'
- elif self._score >= 80:
- return 'B'
- elif self._score >= 60:
- return 'C'
- else:
- return 'D'
- stu = Student('Alice', 85)
- print(stu.score) # 85
- stu.score = 92
- print(stu.grade) # A
复制代码 只提供 getter 不提供 setter 的属性是只读的,对 stu.grade 赋值会触发 AttributeError。
六、继承、多继承与 MRO
Python 继承用 class Dog(Animal),子类通过 super().__init__(name) 调用父类构造,方法覆盖与 Java 类似:- class Animal:
- def __init__(self, name: str):
- self.name = name
- def speak(self) -> str:
- return f'{self.name} makes a sound'
- def __str__(self):
- return f'Animal({self.name})'
- class Dog(Animal):
- def __init__(self, name: str, breed: str):
- super().__init__(name)
- self.breed = breed
- def speak(self) -> str:
- return f'{self.name} barks: Woof!'
- def fetch(self, item: str):
- return f'{self.name} fetches {item}'
- dog = Dog('Buddy', 'Golden Retriever')
- print(dog.speak()) # Buddy barks: Woof!
- print(dog.fetch('ball'))# Buddy fetches ball
- print(dog) # Animal(Buddy)
复制代码 Python 支持多继承,Java 只能单继承和多个接口:- class Flyable:
- def fly(self):
- return f'{self.__class__.__name__} is flying'
- class Swimmable:
- def swim(self):
- return f'{self.__class__.__name__} is swimming'
- class Duck(Flyable, Swimmable):
- def quack(self):
- return 'Quack! Quack!'
- duck = Duck()
- print(duck.fly())
- print(duck.swim())
- print(duck.quack())
复制代码 多继承下同名方法的查找顺序由 MRO 决定,Python 使用 C3 线性化算法:- class A:
- def who(self): return 'A'
- class B(A):
- def who(self): return 'B'
- class C(A):
- def who(self): return 'C'
- class D(B, C):
- pass
- d = D()
- print(d.who()) # B
- print(D.__mro__) # D, B, C, A, object
复制代码 原文提醒不需要死记 C3 算法细节,关键是理解查找顺序会按 MRO 从左到右、从子类到父类依次解析。私有属性、名称改写与魔术方法在原文目录中列出,但正文提供的代码事实到此为止,本文不额外编造未给出的实现。
结尾实践建议
默认参数不要用可变对象;需要多返回值用元组解包;lambda 仅适合一行表达式;类属性修改建议用类名;通过实例赋值会遮蔽类属性;工厂方法优先用 @classmethod;继承层级复杂时用 __mro__ 确认调用顺序。这些都是原文代码能直接推导出的结论,也是面试和实际脚本开发中最容易出错的地方。 |