Python

对象引用、可变性、垃圾回收

从变量与对象的关系出发,整理共享传参、默认参数、垃圾回收和作用域。

· 4 min read · Revised 23 Oct 2026

变量与对象

  • 变量不是盒子,变量是便利贴:把变量s分配给对象xxx
  • == 比较两个对象的值(存储的数据),is比较对象的标识(identity)
    • is 判断是否是同一个对象,不能重载;== 可以调用类型定义的相等性比较。选择哪一个取决于语义,不应只看速度。
    • 判断 None 用 is None。== None 可能触发自定义 __eq__,因此并不保证同样的结果。
  • 浅拷贝创建最外层容器,但其中的项仍然引用原对象。如果有可变项,修改可能影响两边;需要独立的嵌套对象时,可以考虑深拷贝,并留意它是否复制了本来希望共享的数据。
  • Python唯一支持的参数传递模式是共享传参(call by sharing),由此函数可能会修改作为参数传入的可变对象,而不改变其标识(it cannot altogether replace an object with another)

默认参数与共享传参

"""
>>> bus1 = HauntedBus(['Alice', 'Bill'])
>>> bus1.passengers
['Alice', 'Bill']
>>> bus1.pick('Charlie')
>>> bus1.drop('Alice')
>>> bus1.passengers
['Bill', 'Charlie']
>>> bus2 = HauntedBus()
>>> bus2.pick('Carrie')
>>> bus2.passengers
['Carrie']
>>> bus3 = HauntedBus()
>>> bus3.passengers
['Carrie']
>>> bus3.pick('Dave')
>>> bus2.passengers
['Carrie', 'Dave']
>>> bus2.passengers is bus3.passengers
True
>>> bus1.passengers
['Bill', 'Charlie']

>>> dir(HauntedBus.__init__)  # doctest: +ELLIPSIS
['__annotations__', '__call__', ..., '__defaults__', ...]
>>> HauntedBus.__init__.__defaults__
(['Carrie', 'Dave'],)
>>> HauntedBus.__init__.__defaults__[0] is bus2.passengers
True
"""

class HauntedBus:
    """A bus model haunted by ghost passengers"""

    def __init__(self, passengers=[]): 
        self.passengers = passengers

    def pick(self, name):
        self.passengers.append(name)  

    def drop(self, name):
        self.passengers.remove(name)
  • 创建对象时没有传入passengers,导致对象去引用函数定义时求值一次的默认列表,变量成为参数默认值的别名

del 与垃圾回收

  • del 删除的是绑定或容器中的引用,不等同于直接销毁对象。CPython 使用引用计数,并配合循环垃圾回收;Python 语言不保证不可达对象立即被销毁,其他实现也可能使用不同策略。
    • 可以通过weakref.finalize注册回调函数,在销毁对象时调用
      • finalizer 对目标对象使用弱引用;回调及其参数不要直接或间接持有该对象,否则会阻止回收
>>> import weakref
>>> s1 = {1, 2, 3}
>>> s2 = s1
>>> def bye():
...     print('...like tears in the rain.') 
...
>>> ender = weakref.finalize(s1, bye)
>>> ender.alive 
True
>>> del s1
>>> ender.alive 
True
>>> s2 = 'spam'
...like tears in the rain. 
>>> ender.alive 
False

变量作用域

  • 变量作用域
    • Python does not require you to declare variables, but assumes that a variable assigned in the body of a function is local. This is much better than the behavior of JavaScript, which does not require variable declarations either, but if you do forget to declare that a variable is local (with var), you may clobber a global variable without knowing.
    • local, global, nonlocal
  • When a function is defined, the Python bytecode compiler determines how to fetch a variable x that appears in it, based on these rules:
    • If there is a global x declaration, x comes from and is assigned to the x global variable module
    • If there is a nonlocal x declaration, x comes from and is assigned to the x local variable of the nearest surrounding function where x is defined.
    • If x is a parameter or is assigned a value in the function body, then x is the local variable.
    • If x is referenced but is not assigned and is not a parameter:
      • x will be looked up in the local scopes of the surrounding function bodies (nonlocal scopes).
      • If not found in surrounding scopes, it will be read from the module global scope.
      • If not found in the global scope, it will be read from __builtins__.__dict__.

以下反汇编保留旧版 CPython 的输出,指令名称与偏移会随版本变化。

>>> def f1(a):
...     print(a)
...     print(b)
...
>>> f1(3)
3
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in f1
NameError: name 'b' is not defined
>>> b = 6
>>> f1(3)
3
6
>>> def f2(a):
...     print(a)
...     print(b)
...     b = 9
...
>>> f2(3)
3
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in f2
UnboundLocalError: local variable 'b' referenced before assignment
>>> from dis import dis
>>> dis(f1)
  2           0 LOAD_GLOBAL              0 (print)  <1>
              3 LOAD_FAST                0 (a)  <2>
              6 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
              9 POP_TOP

  3          10 LOAD_GLOBAL              0 (print)
             13 LOAD_GLOBAL              1 (b)  <3>
             16 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             19 POP_TOP
             20 LOAD_CONST               0 (None)
             23 RETURN_VALUE
>>> dis(f2)
  2           0 LOAD_GLOBAL              0 (print)
              3 LOAD_FAST                0 (a)
              6 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
              9 POP_TOP

  3          10 LOAD_GLOBAL              0 (print)
             13 LOAD_FAST                1 (b)  <1>
             16 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             19 POP_TOP

  4          20 LOAD_CONST               1 (9)
             23 STORE_FAST               1 (b)
             26 LOAD_CONST               0 (None)
             29 RETURN_VALUE

校订依据

Find a note

Search titles and the full text of published notes.