Python中vars()方法的深入解析与实用指南

python:vars()方法

1 前言

vars() 函数返回对象的__dict__ 属性的字典。这个字典包含了对象的所有属性和它们的值。vars() 函数可以用于模块、类、实例,或者拥有__dict__ 属性的任何其它对象。

参考Python官网如下:

https://docs.python.org/zh-cn/3/library/functions.html#vars

Python官网解释:

vars()

vars(object)

返回模块、类、实例或任何其他具有__dict__属性的对象的__dict__属性。

模块和实例这样的对象具有可更新的__dict__属性;但是,其他对象的__dict__属性可能会设置写入限制(例如,类会使用 types.MappingProxyType 来防止直接更新字典)。

不带参数时,vars() 的行为将类似于 locals()。

如果指定了一个对象但它没有__dict__属性(例如,当它所属的类定义了__slots__属性时)则会引发 TypeError 异常。

在 3.13 版本发生变更: 不带参数调用此函数的结果已被更新为与 locals() 内置函数的描述类似。

2 使用

本文基于Python 3.9进行Python vars() 方法使用详解。

2.1 函数概述

vars() 是 Python 内置函数,用于返回对象的 __dict__ 属性。若无参数传入,则返回当前作用域(局部命名空间)的变量字典。其核心功能是通过字典形式展示对象的属性和值,或当前作用域的变量集合。

2.2 函数语法与参数

vars([object]) 
  • ‌参数‌:
  • object:可选,支持模块、类实例或其他具有__dict__ 属性的对象。

  • ‌返回值‌:
  • 若传入对象,返回该对象的属性字典;若未传参数,返回当前局部作用域的变量字典。

    2.3 核心功能

  • ‌获取对象属性字典‌
  • vars() 可查看类实例的属性及值,适用于调试或动态分析:

    class Person:
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
        def run(self):
            pass
    p = Person("Xiaoxu&li", 25)
    print(vars(p))  # 输出:{'name': 'Xiaoxu&li', 'age': 25}
    

    此处注意:不是所有的实例对象都可以使用vars方法打印其__dict__属性,比如含有的__slots__的类所定义的实例对象:

    class Apple:
        __slots__ = ()
        def __init__(self):
            pass
    
    a = Apple()
    
    
    # 这个是可以打印的
    print(vars(Apple))
    print(vars(a))  # 报错:TypeError: vars() argument must have __dict__ attribute
    

    但是我们知道,类的__slots__虽然定义后会导致类实例对象缺失__dict__属性,但在__slots__声明中是可以再次显式定义__dict__的,下述将不会抛出异常:

    class Apple:
        __slots__ = {"__dict__": {"name": "xiaoli"}}
    
        def __init__(self):
            pass
    
    a = Apple()
    
    # 下述两个语句都将不再报错
    print(vars(Apple))
    print(vars(a)
    

    结果正常执行,不再抛出异常:

    {'__module__': '__main__', '__slots__': {'__dict__': {'name': 'xiaoli'}}, '__init__': <function Apple.__init__ at 0x01AE8B68>, '__dict__': <attribute '__dict__' of 'Apple' objects>, '__doc__': None}
    {}
    

    针对上述的修改,即便将__slots__修改为如下示例,也不会使得vars方法抛出异常,也即是说,只要__slots__中具有__dict__属性即可,即便为空也不会产生影响:

    class Apple:
    	__slots__ = {"__dict__": None}
    	...
    
  • ‌查看模块或类的变量‌
  • ‌模块属性‌:

    import math
    
    print(vars(math))
    print(math.__dict__)
    print(id(vars(math)))
    print(id(math.__dict__))  # 返回模块内定义的变量、函数等字典  
    

    结果如下:

    {'__name__': 'math', '__doc__': 'This module provides access to the mathematical functions\ndefined by the C standard.', '__package__': '', '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': ModuleSpec(name='math', loader=<class '_frozen_importlib.BuiltinImporter'>, origin='built-in'), 'acos': <built-in function acos>, 'acosh': <built-in function acosh>, 'asin': <built-in function asin>, 'asinh': <built-in function asinh>, 'atan': <built-in function atan>, 'atan2': <built-in function atan2>, 'atanh': <built-in function atanh>, 'ceil': <built-in function ceil>, 'copysign': <built-in function copysign>, 'cos': <built-in function cos>, 'cosh': <built-in function cosh>, 'degrees': <built-in function degrees>, 'dist': <built-in function dist>, 'erf': <built-in function erf>, 'erfc': <built-in function erfc>, 'exp': <built-in function exp>, 'expm1': <built-in function expm1>, 'fabs': <built-in function fabs>, 'factorial': <built-in function factorial>, 'floor': <built-in function floor>, 'fmod': <built-in function fmod>, 'frexp': <built-in function frexp>, 'fsum': <built-in function fsum>, 'gamma': <built-in function gamma>, 'gcd': <built-in function gcd>, 'hypot': <built-in function hypot>, 'isclose': <built-in function isclose>, 'isfinite': <built-in function isfinite>, 'isinf': <built-in function isinf>, 'isnan': <built-in function isnan>, 'isqrt': <built-in function isqrt>, 'lcm': <built-in function lcm>, 'ldexp': <built-in function ldexp>, 'lgamma': <built-in function lgamma>, 'log': <built-in function log>, 'log1p': <built-in function log1p>, 'log10': <built-in function log10>, 'log2': <built-in function log2>, 'modf': <built-in function modf>, 'pow': <built-in function pow>, 'radians': <built-in function radians>, 'remainder': <built-in function remainder>, 'sin': <built-in function sin>, 'sinh': <built-in function sinh>, 'sqrt': <built-in function sqrt>, 'tan': <built-in function tan>, 'tanh': <built-in function tanh>, 'trunc': <built-in function trunc>, 'prod': <built-in function prod>, 'perm': <built-in function perm>, 'comb': <built-in function comb>, 'nextafter': <built-in function nextafter>, 'ulp': <built-in function ulp>, 'pi': 3.141592653589793, 'e': 2.718281828459045, 'tau': 6.283185307179586, 'inf': inf, 'nan': nan}
    {'__name__': 'math', '__doc__': 'This module provides access to the mathematical functions\ndefined by the C standard.', '__package__': '', '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': ModuleSpec(name='math', loader=<class '_frozen_importlib.BuiltinImporter'>, origin='built-in'), 'acos': <built-in function acos>, 'acosh': <built-in function acosh>, 'asin': <built-in function asin>, 'asinh': <built-in function asinh>, 'atan': <built-in function atan>, 'atan2': <built-in function atan2>, 'atanh': <built-in function atanh>, 'ceil': <built-in function ceil>, 'copysign': <built-in function copysign>, 'cos': <built-in function cos>, 'cosh': <built-in function cosh>, 'degrees': <built-in function degrees>, 'dist': <built-in function dist>, 'erf': <built-in function erf>, 'erfc': <built-in function erfc>, 'exp': <built-in function exp>, 'expm1': <built-in function expm1>, 'fabs': <built-in function fabs>, 'factorial': <built-in function factorial>, 'floor': <built-in function floor>, 'fmod': <built-in function fmod>, 'frexp': <built-in function frexp>, 'fsum': <built-in function fsum>, 'gamma': <built-in function gamma>, 'gcd': <built-in function gcd>, 'hypot': <built-in function hypot>, 'isclose': <built-in function isclose>, 'isfinite': <built-in function isfinite>, 'isinf': <built-in function isinf>, 'isnan': <built-in function isnan>, 'isqrt': <built-in function isqrt>, 'lcm': <built-in function lcm>, 'ldexp': <built-in function ldexp>, 'lgamma': <built-in function lgamma>, 'log': <built-in function log>, 'log1p': <built-in function log1p>, 'log10': <built-in function log10>, 'log2': <built-in function log2>, 'modf': <built-in function modf>, 'pow': <built-in function pow>, 'radians': <built-in function radians>, 'remainder': <built-in function remainder>, 'sin': <built-in function sin>, 'sinh': <built-in function sinh>, 'sqrt': <built-in function sqrt>, 'tan': <built-in function tan>, 'tanh': <built-in function tanh>, 'trunc': <built-in function trunc>, 'prod': <built-in function prod>, 'perm': <built-in function perm>, 'comb': <built-in function comb>, 'nextafter': <built-in function nextafter>, 'ulp': <built-in function ulp>, 'pi': 3.141592653589793, 'e': 2.718281828459045, 'tau': 6.283185307179586, 'inf': inf, 'nan': nan}
    31089920
    31089920
    

    类属性‌(有区别于上述的类self实例属性):

    class MyClass:  
        class_var = "Class Variable"  
    print(vars(MyClass))  # 包含类变量 `class_var` 及其他内置属性
    

    结果如下:

    {'__module__': '__main__', 'class_var': 'Class Variable', '__dict__': <attribute '__dict__' of 'MyClass' objects>, '__weakref__': <attribute '__weakref__' of 'MyClass' objects>, '__doc__': None}
    
  • ‌动态修改对象属性‌
  • 上述表明,vars方法获取的__dict__不是拷贝出来的副本,而是同一个引用对象,故可通过修改返回的字典,动态调整对象属性:

    obj = MyClass()  
    obj_dict = vars(obj)  
    obj_dict["new_attr"] = "Dynamic Value"  
    print(obj.new_attr)  # 输出:Dynamic Value
    

    再来看一个演示例子:

    a = 12
    b = False
    
    
    class Apple:
        def __init__(self, name, price):
            self.name = name
            self.price = price
    
    print(vars())
    print(vars(Apple))
    print(Apple.__dict__)
    print(id(vars(Apple)))
    print(id(Apple.__dict__))
    
    # AttributeError: type object 'Apple' has no attribute 'a'
    # print(Apple.a)
    # dict_data = vars(Apple)
    # dict_data["a"] = 88
    # print(dict_data.a)
    # 上述的方式依然会报错:
    # TypeError: 'mappingproxy' object does not support item assignment
    
    apple = Apple("apple", 2.7)
    dict_data2 = vars(apple)
    dict_data2["a"] = 77
    print(dict_data2)
    print(apple.a)
    

    执行结果:

    {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x00B1F478>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'D:\\ptest\\test\\TestVars.py', '__cached__': None, 'a': 12, 'b': False, 'Apple': <class '__main__.Apple'>}
    {'__module__': '__main__', '__init__': <function Apple.__init__ at 0x00B68B68>, '__dict__': <attribute '__dict__' of 'Apple' objects>, '__weakref__': <attribute '__weakref__' of 'Apple' objects>, '__doc__': None}
    {'__module__': '__main__', '__init__': <function Apple.__init__ at 0x00B68B68>, '__dict__': <attribute '__dict__' of 'Apple' objects>, '__weakref__': <attribute '__weakref__' of 'Apple' objects>, '__doc__': None}
    11983432
    11983432
    {'name': 'apple', 'price': 2.7, 'a': 77}
    77
    

    由python官网可知:模块和实例这样的对象具有可更新的__dict__属性;但是,其他对象的__dict__属性可能会设置写入限制(例如,类会使用 types.MappingProxyType 来防止直接更新字典)。故而上述通过对vars(Apple)来直接修改类的__dict__属性,会抛出TypeError错误。

    TypeError: ‘mappingproxy’ object does not support item assignment错误是由于尝试修改Python的types.MappingProxyType对象导致的。该类型是字典的只读视图,底层字典的修改需通过原始可变字典实现。

    (一)、原因分析

  • ‌只读特性‌
  • MappingProxyType 通过封装普通字典实现只读访问,其底层数据修改只能通过原字典完成。

  • ‌设计用途‌
  • 常用于保护配置数据、类属性等场景,防止意外修改。

    (二)、解决方案

    方法 1:修改原始字典后重新生成只读视图

    import types
    
    original_dict = {'key1': 'value1'}
    read_only_dict = types.MappingProxyType(original_dict)
    
    # 错误操作:read_only_dict['key2'] = 'value2'  # 触发TypeError
    
    # 正确操作:修改原字典后重新生成只读视图
    original_dict['key2'] = 'value2'
    new_read_only_dict = types.MappingProxyType(original_dict)
    

    方法 2:创建新可变字典进行修改

    若无法访问原字典,可通过复制数据到新字典实现修改:

    modified_dict = dict(read_only_dict)  # 转换为普通字典
    modified_dict['new_key'] = 'new_value'
    read_only_dict = types.MappingProxyType(modified_dict)
    

    方法 3:使用不可变数据类型替代

    若需完全不可变结构,可改用 frozendict 等第三方库:

    from frozendict import frozendict
    
    immutable_dict = frozendict({'key': 'value'})
    

    (三)、设计思路

  • ‌分层封装‌
  • 在模块或类内部维护可变字典,对外暴露 MappingProxyType 对象。

  • ‌明确权限控制‌
  • 通过工厂函数控制只读字典的生成过程:

    def create_protected_config(config_data):
        _internal_config = dict(config_data)
        return types.MappingProxyType(_internal_config)
    

    (四)、扩展类比

    此问题与以下错误类型原理相似(均因操作不可变对象引发):

    TypeError: ‘str’ object does not support item assignment → 字符串转列表修改
    TypeError: ‘tuple’ object does not support item assignment → 元组转列表修改

    (五)、自定义Mapping使用MappingProxyType

    print("开始使用只读视图:")
    from types import MappingProxyType
    from typing import Mapping
    
    
    class CustomMapping(Mapping):
        def __init__(self, data):
            self._data = data
    
        def __getitem__(self, key):
            return self._data[key]
    
        def __iter__(self):
            return iter(self._data)
    
        def __len__(self):
            return len(self._data)
    
    
    # TypeError: Can't instantiate abstract class Mapping with abstract methods __getitem__, __iter__, __len__
    # 直接实例化mappings = Mapping()会报错:
    # mappings = Mapping()
    
    # 使用 issubclass() 检查继承关系:
    print(issubclass(CustomMapping, Mapping))  # 应输出 True
    
    mappings = CustomMapping({"a": 1, "b": 2})
    mappingProxy = MappingProxyType(mappings)
    print(mappingProxy)
    

    结果:

    开始使用只读视图:
    True
    <__main__.CustomMapping object at 0x01FFDAC0>
    

    也可以改为使用collections.abc模块下的Mapping抽象类,可以通过Mapping.__abstractmethods__获取其抽象方法:

    print("开始使用只读视图:")
    from types import MappingProxyType
    from collections.abc import Mapping
    
    
    class CustomMapping(Mapping):
        def __init__(self, data):
            self._data = data
    
        def __getitem__(self, key):
            return self._data[key]
    
        def __iter__(self):
            return iter(self._data)
    
        def __len__(self):
            return len(self._data)
    
    
    # TypeError: Can't instantiate abstract class Mapping with abstract methods __getitem__, __iter__, __len__
    # 直接实例化mappings = Mapping()会报错:
    # mappings = Mapping()
    
    # 使用 issubclass() 检查继承关系:
    print(issubclass(CustomMapping, Mapping))  # 应输出 True
    
    mappings = CustomMapping({"a": 1, "b": 2})
    mappingProxy = MappingProxyType(mappings)
    print(mappingProxy)
    print(Mapping.__abstractmethods__)
    print(dir(Mapping))
    

    结果:

    开始使用只读视图:
    True
    <__main__.CustomMapping object at 0x10318cfa0>
    frozenset({'__iter__', '__getitem__', '__len__'})
    ['__abstractmethods__', '__class__', '__class_getitem__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__setattr__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', '_abc_impl', 'get', 'items', 'keys', 'values']
    
  • ‌ ‌查看具有描述器对象的类属性‌
  • 参考Python官网对于描述器的说明:

    https://docs.python.org/zh-cn/3/howto/descriptor.html#descriptorhowto
    

    在Python中,具有__get__、__set__或者__set_name__方法的类,都可以称为描述器。要使用描述器,它必须作为一个类变量存储在另一个类中(注意,这里描述器必须是作为类变量而非实例变量)。而vars方法可以查看类变量和实例变量,下面将对含有描述器的类下使用vars进行分析:

    在类A中,y为描述器类属性,则对类A实例对象a.y 查找中,点运算符会根据描述器实例的 __get__ 方法将其识别出来,调用该方法并返回。

    描述器的一种流行用法是管理对实例数据的访问。 描述器被分配给类字典中的公有属性,而实际数据则作为私有属性存储在实例字典中。 描述器的__get__() 和__set__() 方法会在公有属性被访问时被触发。

    在下面的例子中,age 是公开属性,_age 是私有属性。当访问公开属性时,描述器会记录下查找或更新的日志:

    import logging
    
    # logging.basicConfig(level=logging.INFO)
    logging.basicConfig(
        level=logging.DEBUG,
        format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
        handlers=[logging.FileHandler('app.log'), logging.StreamHandler()]
    )
    
    
    # logger = logging.getLogger(__name__)
    
    class LoggedAgeAccess:
    
        def __set_name__(self, owner, name):
            logging.info('Creating %r from %r', name, owner)
    
        def __get__(self, obj, objtype=None):
            value = obj._age
            logging.info('Accessing %r giving %r', 'age', value)
            return value
    
        def __set__(self, obj, value):
            logging.info('Updating %r to %r', 'age', value)
            obj._age = value
    
    
    class Person:
        age = LoggedAgeAccess()  # 描述器实例
    
        def __init__(self, name, age):
            self.name = name  # 常规实例属性
            self.age = age  # 调用 __set__()
    
        def birthday(self):
            self.age += 1  # 调用 __get__() 和 __set__()
    
    
    mary = Person('Marry M', 30)
    dave = Person('Xiao Li', 40)
    print(vars(mary))
    print(vars(Person))
    

    结果:

    2025-05-05 17:30:01,451 - root - INFO - Creating 'age' from <class '__main__.Person'>
    2025-05-05 17:30:01,451 - root - INFO - Updating 'age' to 30
    2025-05-05 17:30:01,451 - root - INFO - Updating 'age' to 40
    {'name': 'Marry M', '_age': 30}
    {'__module__': '__main__', 'age': <__main__.LoggedAgeAccess object at 0x01A2C070>, '__init__': <function Person.__init__ at 0x01A924F0>, 'birthday': <function Person.birthday at 0x01B46F58>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None}
    

    上述结果可知,因官网提到:描述器被分配给类字典中的公有属性,而实际数据则作为私有属性存储在实例字典中,所以我们通过vars(Person)可以看到类字典中公有属性age,它是一个描述器对象实例,但是并没有获取到描述器为我们生成的私有属性_age;而vars(mary)则没有类字典的公有属性age,实例对象可以获取到私有属性_age。

    核心代码不改动,新增代码如下:

    mary = Person('Marry M', 30)
    dave = Person('Xiao Li', 40)
    print(vars(mary))
    print(vars(Person))
    
    print(mary.age)
    mary.birthday()
    print(mary.name)
    

    执行结果:

    2025-05-05 18:01:32,846 - root - INFO - Creating 'age' from <class '__main__.Person'>
    2025-05-05 18:01:32,847 - root - INFO - Updating 'age' to 30
    2025-05-05 18:01:32,847 - root - INFO - Updating 'age' to 40
    2025-05-05 18:01:32,847 - root - INFO - Accessing 'age' giving 30
    2025-05-05 18:01:32,847 - root - INFO - Accessing 'age' giving 30
    2025-05-05 18:01:32,847 - root - INFO - Updating 'age' to 31
    {'name': 'Marry M', '_age': 30}
    {'__module__': '__main__', 'age': <__main__.LoggedAgeAccess object at 0x00F3C9B8>, '__init__': <function Person.__init__ at 0x00FA24F0>, 'birthday': <function Person.birthday at 0x01056F58>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None}
    30
    Marry M
    

    上述可知,常规属性查找不会被记录,比如name;而被描述器管理的属性比如age,是被会记录的。上述的日志打印,因为__set_name__方法在类中作为描述器定义的方法,多个实例也仅会调用一次,且是最先调用的;而描述器的__get__() 和__set__() 方法会在公有属性被访问时被触发(比如a.x的形式(x是描述器对象,同时x为类属性,a为类实例对象)会触发__get__() ,而a.x = XXX的形式会触发__set__()),所以日志打印是符合预期的。

    最后的思考如果上述将描述器中的obj._age都修改为obj.age,这样是否可行呢?如果可行的话vars打印属性是什么样的呢?

    答案是不行,也就无谓讨论vars的结果了。因为比如调用mary.age时,本质是调用描述器的__get__()方法,而__get__()方法又是调用的obj.age,这里我们知道,obj其实就是定义描述器对象age的所属类Person的实例对象,也就是mary,那么这里本质上就还是调用的mary.age,故而此处会导致进行递归调用,从而在达到python中的最大递归次数后抛出异常:RecursionError: maximum recursion depth exceeded while calling a Python object

    也就是下述错误的修改方式

    class LoggedAgeAccess:
    
        def __set_name__(self, owner, name):
            logging.info('Creating %r from %r', name, owner)
    
        def __get__(self, obj, objtype=None):
            # value = obj._age
            value = obj.age
            logging.info('Accessing %r giving %r', 'age', value)
            return value
    
        def __set__(self, obj, value):
            logging.info('Updating %r to %r', 'age', value)
            # obj._age = value
            obj.age = value
    

    结果与上述分析一致,执行报错:

    下来再来看下官网中提到的另外一个栗子:

    import logging
    
    logging.basicConfig(level=logging.INFO)
    
    
    class LoggedAccess:
    
        def __set_name__(self, owner, name):
            self.public_name = name
            self.private_name = '_' + name
    
        def __get__(self, obj, objtype=None):
            value = getattr(obj, self.private_name)
            logging.info('Accessing %r giving %r', self.public_name, value)
            return value
    
        def __set__(self, obj, value):
            logging.info('Updating %r to %r', self.public_name, value)
            setattr(obj, self.private_name, value)
    
    
    class Person:
        name = LoggedAccess()  # 第一个描述器实例
        age = LoggedAccess()  # 第二个描述器实例
    
        def __init__(self, name, age):
            self.name = name  # 调用第一个描述器
            self.age = age  # 调用第二个描述器
    
        def birthday(self):
            self.age += 1
    

    此处调用 vars() 来查找描述器而不触发它:

    print(vars(vars(Person)['name']))
    print(vars(vars(Person)['age']))
    

    结果:

    {'public_name': 'name', 'private_name': '_name'}
    {'public_name': 'age', 'private_name': '_age'}
    

    新类会记录对name和age二者的访问,同时这两个Person实例只会包含私有名称:

    a = Person('Xiaoxu', 30)
    b = Person('Xiaoli', 20)
    print(vars(a))
    print(vars(b))
    

    结果:

    INFO:root:Updating 'name' to 'Xiaoxu'
    INFO:root:Updating 'age' to 30
    INFO:root:Updating 'name' to 'Xiaoli'
    INFO:root:Updating 'age' to 20
    {'_name': 'Xiaoxu', '_age': 30}
    {'_name': 'Xiaoli', '_age': 20}
    

    但是我们通过.的方式是可以分别获取到实例对象的公有和私有属性的:

    print(b.name)
    print(b.age)
    print(b._name)
    print(b._age)
    

    结果如下:

    INFO:root:Accessing 'name' giving 'Xiaoli'
    INFO:root:Accessing 'age' giving 20
    Xiaoli
    20
    Xiaoli
    20
    

    2.4 注意事项

  • ‌作用域差异‌
  • 在全局作用域无参调用 vars() 时,等同于 globals();
    在函数内部调用 vars(),则返回函数的局部变量字典。

  • ‌对象限制‌
  • 仅当对象具有__dict__ 属性时,vars() 才生效。例如,基本数据类型(如 int、str)调用会报错(当然前面提到的实现了 __slots__的类且未声明__dict__ 属性的类实例使用vars方法同样也会报错)。

    举例说明:

    # 这样不会报错
    print(vars(int))
    # 这样会报错:TypeError: vars() argument must have __dict__ attribute
    # print(vars(10))
    

    结果:

    {'__repr__': <slot wrapper '__repr__' of 'int' objects>, '__hash__': <slot wrapper '__hash__' of 'int' objects>, '__getattribute__': <slot wrapper '__getattribute__' of 'int' objects>, '__lt__': <slot wrapper '__lt__' of 'int' objects>, '__le__': <slot wrapper '__le__' of 'int' objects>, '__eq__': <slot wrapper '__eq__' of 'int' objects>, '__ne__': <slot wrapper '__ne__' of 'int' objects>, '__gt__': <slot wrapper '__gt__' of 'int' objects>, '__ge__': <slot wrapper '__ge__' of 'int' objects>, '__add__': <slot wrapper '__add__' of 'int' objects>, '__radd__': <slot wrapper '__radd__' of 'int' objects>, '__sub__': <slot wrapper '__sub__' of 'int' objects>, '__rsub__': <slot wrapper '__rsub__' of 'int' objects>, '__mul__': <slot wrapper '__mul__' of 'int' objects>, '__rmul__': <slot wrapper '__rmul__' of 'int' objects>, '__mod__': <slot wrapper '__mod__' of 'int' objects>, '__rmod__': <slot wrapper '__rmod__' of 'int' objects>, '__divmod__': <slot wrapper '__divmod__' of 'int' objects>, '__rdivmod__': <slot wrapper '__rdivmod__' of 'int' objects>, '__pow__': <slot wrapper '__pow__' of 'int' objects>, '__rpow__': <slot wrapper '__rpow__' of 'int' objects>, '__neg__': <slot wrapper '__neg__' of 'int' objects>, '__pos__': <slot wrapper '__pos__' of 'int' objects>, '__abs__': <slot wrapper '__abs__' of 'int' objects>, '__bool__': <slot wrapper '__bool__' of 'int' objects>, '__invert__': <slot wrapper '__invert__' of 'int' objects>, '__lshift__': <slot wrapper '__lshift__' of 'int' objects>, '__rlshift__': <slot wrapper '__rlshift__' of 'int' objects>, '__rshift__': <slot wrapper '__rshift__' of 'int' objects>, '__rrshift__': <slot wrapper '__rrshift__' of 'int' objects>, '__and__': <slot wrapper '__and__' of 'int' objects>, '__rand__': <slot wrapper '__rand__' of 'int' objects>, '__xor__': <slot wrapper '__xor__' of 'int' objects>, '__rxor__': <slot wrapper '__rxor__' of 'int' objects>, '__or__': <slot wrapper '__or__' of 'int' objects>, '__ror__': <slot wrapper '__ror__' of 'int' objects>, '__int__': <slot wrapper '__int__' of 'int' objects>, '__float__': <slot wrapper '__float__' of 'int' objects>, '__floordiv__': <slot wrapper '__floordiv__' of 'int' objects>, '__rfloordiv__': <slot wrapper '__rfloordiv__' of 'int' objects>, '__truediv__': <slot wrapper '__truediv__' of 'int' objects>, '__rtruediv__': <slot wrapper '__rtruediv__' of 'int' objects>, '__index__': <slot wrapper '__index__' of 'int' objects>, '__new__': <built-in method __new__ of type object at 0x7A2B48F0>, 'conjugate': <method 'conjugate' of 'int' objects>, 'bit_length': <method 'bit_length' of 'int' objects>, 'to_bytes': <method 'to_bytes' of 'int' objects>, 'from_bytes': <method 'from_bytes' of 'int' objects>, 'as_integer_ratio': <method 'as_integer_ratio' of 'int' objects>, '__trunc__': <method '__trunc__' of 'int' objects>, '__floor__': <method '__floor__' of 'int' objects>, '__ceil__': <method '__ceil__' of 'int' objects>, '__round__': <method '__round__' of 'int' objects>, '__getnewargs__': <method '__getnewargs__' of 'int' objects>, '__format__': <method '__format__' of 'int' objects>, '__sizeof__': <method '__sizeof__' of 'int' objects>, 'real': <attribute 'real' of 'int' objects>, 'imag': <attribute 'imag' of 'int' objects>, 'numerator': <attribute 'numerator' of 'int' objects>, 'denominator': <attribute 'denominator' of 'int' objects>, '__doc__': "int([x]) -> integer\nint(x, base=10) -> integer\n\nConvert a number or string to an integer, or return 0 if no arguments\nare given.  If x is a number, return x.__int__().  For floating point\nnumbers, this truncates towards zero.\n\nIf x is not a number or if base is given, then x must be a string,\nbytes, or bytearray instance representing an integer literal in the\ngiven base.  The literal can be preceded by '+' or '-' and be surrounded\nby whitespace.  The base defaults to 10.  Valid bases are 0 and 2-36.\nBase 0 means to interpret the base from the string as an integer literal.\n>>> int('0b100', base=0)\n4"}
    
  • ‌动态修改风险‌
  • 直接操作 vars() 返回的字典可能破坏对象封装性,需谨慎使用。

    2.5 举些实际场景中使用的栗子

  • 局部作用域用法,字符串格式化
  • name1 = "xiaoxu"
    name2 = "xiaoli"
    s = '{name1} and {name2} have happy life.'
    print(s.format_map(vars()))
    

    结果:

    xiaoxu and xiaoli have happy life.
    
  • 根据实例对象__dict__属性字典执行序列化&反序列化
  • import json
    
    
    class Apple:
        def __init__(self, name, price):
            self.name = name
            self.price = price
    
        def __str__(self):
            return f"SApple(name={self.name}, price={self.price})"
    
        def __repr__(self):
            return "Apple{name=" +self.name + ",price=" + self.price + "}"
    
    
    
    apple = Apple("apple", 2.7)
    
    
    # vars用法
    def serialize_instance(obj):
        d = {'__classname__': type(obj).__name__}
        d.update(vars(obj))
        return d
    
    
    classes = {
        'Apple': Apple
    }
    
    
    def unserialize_object(d):
        clsname = d.pop('__classname__', None)
        if clsname:
            cls = classes[clsname]
            obj = cls.__new__(cls)  # Make instance without calling __init__
            for key, value in d.items():
                setattr(obj, key, value)
            return obj
        else:
            return d
    
    
    s = json.dumps(apple, default=serialize_instance)
    print(s)
    print(type(s))
    a = json.loads(s, object_hook=unserialize_object)
    print(a)
    print(vars(a))
    

    结果:

    {"__classname__": "Apple", "name": "apple", "price": 2.7}
    <class 'str'>
    SApple(name=apple, price=2.7)
    {'name': 'apple', 'price': 2.7}
    
  • 动态对象配置与调试
  • 通过vars() 直接操作对象的__dict__,动态添加、修改对象属性,并实现调试日志功能:

    class DynamicConfig:
        def __init__(self, **kwargs):
            self.__dict__.update(kwargs)
    
        def add_property(self, key, value):
            vars(self)[key] = value  # 直接操作 __dict__
    
        def log_properties(self):
            print("Current properties:", vars(self))
    
    
    # 使用示例
    config = DynamicConfig(name="Xiaoxu")
    config.add_property("age", 30.0)
    config.log_properties()  # 结果:Current properties: {'name': 'Xiaoxu', 'age': 30.0}
    
    # 动态删除属性
    del vars(config)['age']
    config.log_properties()  # 结果:Current properties: {'name': 'Xiaoxu'}
    
  • 模块级变量动态管理
  • 动态操作模块变量并验证效果:

    # 创建模块文件 my_module.py
    var_a = 100
    def func(): return "original"
    
    from test import my_module
    
    # 修改模块变量
    vars(my_module)['var_a'] = 200
    vars(my_module)['new_var'] = "dynamic"
    my_module.func = lambda: "modified"
    
    print(my_module.var_a)          # Output: 200
    print(my_module.new_var)        # Output: dynamic
    print(my_module.func())         # Output: modified
    
  • 上下文管理器监控局部变量
  • 实现局部变量变化的自动记录,先看下面的栗子,分析为何达不到我们预期的效果:

    class VariableMonitor:
        def __enter__(self):
            vcop = vars().copy()
            print("进入上下文:", vcop)
            print(id(vcop), id(vars()))
            vcop1 = vars().copy()
            print(id(vcop1))
            self.initial_vars = dict(vcop1)  # 记录进入时的局部变量
            return self
    
        def __exit__(self, exc_type, exc_val, exc_tb):
            final_vars = vars()
            print("结束上下文:", final_vars)
            changed = {k: final_vars[k] for k in final_vars if k not in self.initial_vars}
            print("Variables changed:", changed)
    
    
    # 使用示例
    def process_data():
        name = "xiaoli"
        with VariableMonitor():
            x = 42
            y = [i for i in range(10)]
    
    
    process_data() 
    

    我们预期是通过上下文管理器来实现with上下文的外部和内部局部变量的获取,但是执行结果却与我们的预期相反:

    进入上下文: {'self': <__main__.VariableMonitor object at 0x01B6DB98>}
    28755040 27812720
    28757160
    结束上下文: {'exc_type': None, 'exc_val': None, 'exc_tb': None, 'self': <__main__.VariableMonitor object at 0x01B6DB98>}
    Variables changed: {'exc_type': None, 'exc_val': None, 'exc_tb': None}
    

    上述的结果,是因为vars()方法获取的是局部命名空间,因此获取的是__enter__以及__exit__方法的局部命名空间参数。若想要同时获取with上下文的外部和内部局部变量,因with上下文的管理是在调用者栈帧process_data方法中触发的,所以可以使用inspect模块获取调用者的栈帧,从而访问外层上下文的局部变量,修改如下:

    import inspect
    
    
    class VariableMonitor:
        def __enter__(self):
            vcop = vars().copy()
            print("进入上下文:", vcop)
            print(id(vcop), id(vars()))
            vcop1 = vars().copy()
            print(id(vcop1))
            # 获取调用者(外层上下文)的栈帧
            self.frame = inspect.currentframe().f_back
            # 记录进入时的局部变量状态
            copy_f = self.frame.f_locals.copy()
            print("进入上下文时的栈帧局部变量:", copy_f)
            copy_f.update(vcop1)
            print("进入上下文时合并后的局部变量:", copy_f)
            self.initial_vars = copy_f
            return self
    
        def __exit__(self, exc_type, exc_val, exc_tb):
            local_vas = vars().copy()
            # 获取退出时的局部变量状态
            final_vars = self.frame.f_locals
            print("退出上下文时的栈帧局部变量:", final_vars)
            final_vars.update(local_vas)
            print("退出上下文时的合并局部变量:", final_vars)
            # 找出新增或修改的变量
            changed = {k: final_vars[k] for k in final_vars if k not in self.initial_vars}
            print("Variables changed:", changed)
    
    
    # 使用示例
    def process_data():
        name = "xiaoli"
        with VariableMonitor():
            x = 42
            y = [i for i in range(10)]
    
    
    process_data()
    

    执行结果符合预期:

    进入上下文: {'self': <__main__.VariableMonitor object at 0x01ABDD90>}
    27092184 27091824
    32887624
    进入上下文时的栈帧局部变量: {'name': 'xiaoli'}
    进入上下文时合并后的局部变量: {'name': 'xiaoli', 'self': <__main__.VariableMonitor object at 0x01ABDD90>, 'vcop': {'self': <__main__.VariableMonitor object at 0x01ABDD90>}}
    退出上下文时的栈帧局部变量: {'name': 'xiaoli', 'x': 42, 'y': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]}
    退出上下文时的合并局部变量: {'name': 'xiaoli', 'x': 42, 'y': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 'exc_type': None, 'exc_val': None, 'exc_tb': None, 'self': <__main__.VariableMonitor object at 0x01ABDD90>}
    Variables changed: {'x': 42, 'y': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 'exc_type': None, 'exc_val': None, 'exc_tb': None}
    

    上述的inspect.currentframe().f_back,inspect.currentframe()就是当前栈帧,也就是__enter__或者__exit__方法所在的栈帧,f_back意即它们的调用者栈帧,这里是process_data方法栈帧,因此可以获取到with上下文的外部、内部定义的局部变量了。

  • 复杂继承结构中的属性合并
  • 在多层继承中动态合并父类属性:

    class Base:
        base_var = "base"
    
    
    class Parent(Base):
        parent_var = "parent"
    
    
    class Child(Parent):
        def __init__(self):
            self.child_var = "child"
    
        def collect_vars(self):
            combined = {}
            # 遍历 MRO 继承链收集所有类的属性
            for cls in self.__class__.mro():
                if hasattr(cls, '__dict__'):
                    combined.update(vars(cls))
            combined.update(vars(self))  # 合并实例属性
            combined = {k: v for k, v in combined.items() if not k.startswith("__")}
            return combined
    
    
    obj = Child()
    print(obj.collect_vars())
    

    结果输出包含base_var、parent_var、child_var的合并字典:

    {'collect_vars': <function Child.collect_vars at 0x011B8C40>, 'parent_var': 'parent', 'base_var': 'base', 'child_var': 'child'}
    

    由此总结可知,vars() 的核心价值在于其动态性,适用于但不局限于如下各个方面:

  • 动态属性管理(配置注入、调试)
  • 元编程与类结构分析
  • 模块级变量操作
  • 上下文敏感的变量监控
  • 复杂对象结构的属性聚合
  • 2.6 总结

    vars() 是调试和动态编程的实用工具,通过字典形式直观展示对象属性或作用域变量。其核心应用包括属性分析、模块检查及动态编程,但需注意作用域差异和对象限制。

    作者:小徐也要努力鸭

    物联沃分享整理
    物联沃-IOTWORD物联网 » Python中vars()方法的深入解析与实用指南

    发表回复