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

<think>好的,我现在需要解释Pythonvars()方法。首先,我得回忆一下vars()的基本用法和功能。根据之前的学习,vars()返回对象的__dict__属性,用于获取对象的属性和它们的值。如果没有参数,它返回当前作用域的局部变量,类似于locals()。接下来,我需要确定用户的需求是什么。他们可能想知道vars()的具体用法,适用场景以及注意事项。可能还希望看到代码示例,说明在不同情况下的行为。 首先,我应该明确vars()的两种使用方式:带参数和不带参数。不带参数时,返回当前局部作用域的变量字典;带参数时,返回对象的__dict__属性。如果对象没有__dict__,比如内置类型,会抛出TypeError。 然后,我需要举几个例子来说明。比如,使用vars()查看模块、类实例、类的__dict__。还要注意当对象是实例时,vars(instance)返回实例的__dict__,而vars(class)返回类的__dict__。此外,自定义类可以控制__dict__的内容,而内置类型如int或list没有__dict__,所以使用vars()会报错。还需要提到与dir()的区别,dir()返回属性名列表,而vars()返回的是属性和值的字典。另外,当修改返回的字典时,可能会影响原对象,因为对于模块和实例,__dict__是可写的。 接下来,我需要考虑用户可能遇到的问题。例如,为什么在某些情况下vars()会抛出TypeError,或者如何利用vars()进行动态属性操作。此外,可能涉及元类和__slots__的情况,这些会影响__dict__的存在与否。最后,总结一下vars()的主要用途:调试时查看对象属性,动态操作属性,以及结合inspect模块进行更复杂的反射操作。同时,提醒用户注意使用时的限制,比如不能用于没有__dict__的对象,修改字典可能带来的副作用等。 现在,我需要将这些内容组织成清晰的解释,并附上代码示例。确保代码覆盖不同情况,如模块、实例、类、以及内置类型的情况。同时,指出常见错误和最佳实践,帮助用户正确使用vars()方法。</think>Python的`vars()`函数用于获取对象的`__dict__`属性,以下是详细解析及使用示例: --- ### 一、基础用法 #### 1. 无参数调用 ```python def test(): a = 10 b = "hello" print(vars()) # 返回当前作用域的局部变量字典 test() # 输出:{'a': 10, 'b': 'hello'} ``` #### 2. 带对象参数 ```python class Person: def __init__(self, name, age): self.name = name self.age = age p = Person("Alice", 30) print(vars(p)) # {'name': 'Alice', 'age': 30} ``` --- ### 二、不同对象类型表现 #### 1. 模块对象 ```python import math print(vars(math)['pi']) # 3.141592653589793 ``` #### 2. 类对象 ```python class MyClass: class_var = 100 def __init__(self): self.instance_var = 200 print(vars(MyClass)) # 包含class_var等类属性 ``` #### 3. 内置类型(不可用) ```python try: print(vars(10)) # 抛出TypeError except TypeError as e: print(e) # vars() argument must have __dict__ attribute ``` --- ### 三、高级应用场景 #### 1. 动态属性操作 ```python class Config: pass config = Config() vars(config)['timeout'] = 30 # 等价于 config.timeout = 30 print(config.timeout) # 30 ``` #### 2. 对象序列化 ```python def serialize(obj): return vars(obj) data = serialize(p) print(data) # {'name': 'Alice', 'age': 30} ``` #### 3. 调试辅助 ```python def debug_view(obj): return f"{type(obj).__name__} {vars(obj)}" print(debug_view(p)) # Person {'name': 'Alice', 'age': 30} ``` --- ### 四、特殊注意事项 #### 1. __slots__限制 ```python class SlotClass: __slots__ = ['x'] def __init__(self): self.x = 10 sc = SlotClass() print(vars(sc)) # 抛出AttributeError(无__dict__属性) ``` #### 2. 元类影响 ```python class Meta(type): def __new__(cls, name, bases, namespace): namespace['meta_var'] = True return super().__new__(cls, name, bases, namespace) class MyClass(metaclass=Meta): pass print(vars(MyClass)['meta_var']) # True ``` #### 3. 字典同步性 ```python p = Person("Bob", 25) d = vars(p) d['age'] = 26 print(p.age) # 26(修改字典会影响原对象) ``` --- ### 五、与相关函数对比 | 函数 | 返回值类型 | 作用对象 | 可写性 | |------|------------|----------|--------| | `vars(obj)` | dict | 有`__dict__`的对象 | 可修改 | | `dir(obj)` | list | 任意对象 | 只读 | | `locals()` | dict | 当前作用域 | 可修改 |
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值