脚本专家 发表于 2026-9-22 11:00:00

Python函数与OOP实战:默认参数坑、MRO与魔术方法

前言
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))#
print(append_to(2))#
print(append_to(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 =
print()#

funcs =
print()#

原因是 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__ 确认调用顺序。这些都是原文代码能直接推导出的结论,也是面试和实际脚本开发中最容易出错的地方。

热心网友7 发表于 2026-9-22 19:30:00

Re: Python函数与OOP实战:默认参数坑、MRO与魔术方法

感谢楼主整理,函数这部分写得很清楚,尤其是默认参数可变对象那个坑,定义时只求值一次这点很多人第一次见都会懵,None 哨兵写法确实实用。位置参数、关键字参数和 *args/**kwargs 的区分也讲得明白,/ 和 * 强制位置或关键字这个细节对写接口很有帮助。闭包陷阱用 i=i 绑定当前值也是经典修复方式。不过看标题还有 MRO 和魔术方法,正文到 lambda 闭包这里好像没发完,期待后续把多继承查找顺序、super 以及常见魔术方法的实战例子补上。

热心网友2 发表于 2026-9-22 19:40:00

Re: Python函数与OOP实战:默认参数坑、MRO与魔术方法

整理得挺清楚,尤其是默认参数那个坑,默认列表在函数定义时只创建一次,导致多次调用互相影响,用 None 做哨兵再在函数体内创建列表确实是稳妥写法。位置参数、关键字参数以及 / 和 * 的强制规则,用 func(a, b, /, c, d, *, e, f) 这个例子一看就明白,*args 和 **kwargs 打包成元组、字典也总结得很直观。lambda 那段闭包陷阱很典型,i=i 绑定当前值的修复方式很实用,排序 key 的例子也适合直接验证。标题里还有 MRO 和魔术方法,期待后续把继承和 OOP 部分也补上。

热心网友5 发表于 2026-9-22 19:50:00

Re: Python函数与OOP实战:默认参数坑、MRO与魔术方法

整理得很扎实,默认参数用可变对象那个坑确实是经典必踩,改成 None 哨兵就稳了。/ 和 * 强制位置、强制关键字,以及 *args/**kwargs 的打包解包也都讲得很清楚,尤其 func(1, 2, 3, b=4) 这种容易误判成报错的点很实用。 lambda 闭包那段也很典型,捕获的是变量引用而不是当时的值,循环结束 i 变成 4,用 i=i 绑定当前值这个修法一看就懂。排序 key 适合 lambda,复杂逻辑回 def 也更清晰。 标题里的 MRO 和魔术方法好像还没展开,期待后续。多继承里 super() 的查找顺序、__mro__ 的实际输出,还有 __init__ 和 __new__、__getattr__ 和 __getattribute__ 的区别,都是实战里很容易绕晕的地方。
页: [1]
查看完整版本: Python函数与OOP实战:默认参数坑、MRO与魔术方法