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 = nameself.age = agedef 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):passa = 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):passa = 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 mathprint(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 = Falseclass Apple:def __init__(self, name, price):self.name = nameself.price = priceprint(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 assignmentapple = 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 typesoriginal_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 frozendictimmutable_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 Mappingclass CustomMapping(Mapping):def __init__(self, data):self._data = datadef __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)) # 应输出 Truemappings = 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 Mappingclass CustomMapping(Mapping):def __init__(self, data):self._data = datadef __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)) # 应输出 Truemappings = 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._agelogging.info('Accessing %r giving %r', 'age', value)return valuedef __set__(self, obj, value):logging.info('Updating %r to %r', 'age', value)obj._age = valueclass 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._agevalue = obj.agelogging.info('Accessing %r giving %r', 'age', value)return valuedef __set__(self, obj, value):logging.info('Updating %r to %r', 'age', value)# obj._age = valueobj.age = value
结果与上述分析一致,执行报错:
下来再来看下官网中提到的另外一个栗子:
import logginglogging.basicConfig(level=logging.INFO)class LoggedAccess:def __set_name__(self, owner, name):self.public_name = nameself.private_name = '_' + namedef __get__(self, obj, objtype=None):value = getattr(obj, self.private_name)logging.info('Accessing %r giving %r', self.public_name, value)return valuedef __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 jsonclass Apple:def __init__(self, name, price):self.name = nameself.price = pricedef __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 dclasses = {'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 objelse:return ds = 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 selfdef __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 = 42y = [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 inspectclass 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_freturn selfdef __exit__(self, exc_type, exc_val, exc_tb):local_vas = vars().copy()# 获取退出时的局部变量状态final_vars = self.frame.f_localsprint("退出上下文时的栈帧局部变量:", 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 = 42y = [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 combinedobj = 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() 是调试和动态编程的实用工具,通过字典形式直观展示对象属性或作用域变量。其核心应用包括属性分析、模块检查及动态编程,但需注意作用域差异和对象限制。