Python中通过字符串访问与修改局部变量

简介: Python中通过字符串访问与修改局部变量

在Python中定义一个函数时,就会把变量空间划分为全局变量(global)与局部变量(local),如果是定义在一个类的成员函数中,那么就还有额外的成员变量(self)空间。

那么,如果在实际操作中,想把这几种不同的变量空间做一个分离的话,有没有办法呢?

读取和修改局部变量

首先来看一下局部变量的读取,一般有locals()、vars()sys._getframe(0).f_code.co_varnames这几种方法,另外有一种sys._getframe(0).f_locals的方法,其实等价于locals(),相关的实现代码如下:

x = 0

class Obj:
    def __init__(self,y):
        self.func(y)

    def func(y, z=1):
        m = 2
        print (locals())
        print (vars())
        print (__import__('sys')._getframe(0).f_code.co_varnames)

if __name__ == '__main__':

    Obj(2)

该代码的运行结果如下:

{
   'self': <__main__.Obj object at 0x7f5cf5e74e50>, 'y': 2, 'z': 1, 'm': 2}
{
   'self': <__main__.Obj object at 0x7f5cf5e74e50>, 'y': 2, 'z': 1, 'm': 2}

('self', 'y', 'z', 'm')

在vars方法不加具体变量名的时候,就是等价于locals方法,两者返回的结果都是字典格式。如果是一个类中的成员函数下执行locals或者vars,会附带一个__main__.Obj object的变量,相当于所有self的成员变量,其实也是局部变量的一部分。

而如果使用co_varnames的方法,那么得到的就是所有局部变量的名称,我们也可以在例子中额外定义一个self的成员变量:

x = 0

class Obj:
    def __init__(self, y):
        self.p = 5
        self.func(y)

    def func(self, y, z=1):
        m = 2
        print(locals())
        print(vars())
        print(__import__('sys')._getframe(0).f_code.co_varnames)

if __name__ == '__main__':
    Obj(2)
    # {'self': <__main__.Obj object at 0x7fe9aac0ce50>, 'y': 2, 'z': 1, 'm': 2}
    # {'self': <__main__.Obj object at 0x7fe9aac0ce50>, 'y': 2, 'z': 1, 'm': 2}

    # ('self', 'y', 'z', 'm')

可以发现,所有的成员变量都被放在了self中。并且需要注意的是,全局变量x自始至终都没有在局部变量中出现。那么既然我们可以通过这种方式分离出局部变量,或者是局部变量的名称,那我们如何去调整或者修改这些局部变量呢?

首先我们需要知道,locals()方法返回的变量是一个copy,也就是说即使修改了locals方法返回的结果,也不能真正的改变局部变量本身的值,这样描述可能有点抽象,我们直接看下这个案例:

x = 0

class Obj:
    def __init__(self,y):
        self.func(y)

    def func(self, y, z=1):
        m = 2
        vars()['z']=2
        locals()['n']=3
        print (locals())
        print (z)

if __name__ == '__main__':

    Obj(2)

在这个案例中分别通过vars方法和locals方法去修改局部变量的值,最终的输出结果如下:

{
   'self': <__main__.Obj object at 0x7f74d9470e50>, 'y': 2, 'z': 1, 'm': 2, 'n': 3}

1

首先要解释一下为什么这个案例中没有打印n这个变量,前面提到vars和locals的返回值都是真实变量的一个copy,因此我们不管是修改也好,新增也好,内容不会同步到变量空间中去,也就是说,此时的局部变量n还是处于一个没有定义的状态,只是在locals或者vars的字典中存在,此时打印只会报错NameError。而z的最终打印输出是1,这表明z的值确实没有受到对vars的变量修改的影响。

那到底有没有办法可以通过字符串去修改局部变量呢(不同步到全局变量)?答案是有的,但是这个方案非常的hacky,请看如下示例:

import ctypes

x = 0

class Obj:
    def __init__(self,y):
        self.func(y)

    def func(self, y, z=1):
        m = 2
        __import__('sys')._getframe(0).f_locals.update({
   
            'z': 2,'n': 3
        })
        ctypes.pythonapi.PyFrame_LocalsToFast(
            ctypes.py_object(__import__('sys')._getframe(0)), ctypes.c_int(0))
        print (locals())
        print (z)

if __name__ == '__main__':

    Obj(2)

这个案例是使用了Cython的方案直接去修改了数据帧的内容,而这里所使用的f_locals其实本质上就是locals。经过一番运行,输出结果如下:

{
   'self': <__main__.Obj object at 0x7fea2e2
a1e80>, 'y': 2, 'z': 2, 'm': 2, 'n': 3}

2

此时局部变量z是被成功修改了的,但是在前面提到的,即使我们通过这种方法修改了局部变量的值,但是依然不能通过这个方案去创建一个新的局部变量,此时去执行print (n)的话,依然会有报错提示。

读取和修改全局变量

相比于修改局部变量,其实查看修改全局变量要显的更加容易。首先我们用一个示例演示一下如何查看所有的全局变量:

x = 0

class Obj:
    def __init__(self,y):
        self.func(y)

    def func(self, y, z=1):
        m = 2
        print (globals())

if __name__ == '__main__':

    Obj(2)

获取局部变量的方式有很多,但是获取全局变量一般就是globals或者等价的f_globals。上述代码执行输出如下:

{
   '__name__': '__main__', '__doc__': None, '__package__': None,
 '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x7f202632ac40>,
 '__spec__': None, '__annotations__': {
   }, '__builtins__': <module 'builtins' (built-in)>,

 '__file__': 'xxx.py', '__cached__': None, 'x': 0, 'Obj': <class '__main__.Obj'>}

用这种方法我们发现了全局变量x,而在同一个函数内的几个局部变量,就没有显示在globals的key中。而不同于locals变量的是,globals函数返回的是一个真实的数据,是可以直接修改,并且在全局生效的。比如我们在函数内定义或者修改全局变量:

x = 0

class Obj:
    def __init__(self,y):
        self.func(y)

    def func(self, y, z=1):
        global m
        m = 2
        globals()['x']=3

if __name__ == '__main__':
    Obj(2)
    print(globals()['x'])
    print(globals()['m'])
    # 3

    # 2

在这个例子中我们就可以发现,不仅仅是修改的x值生效了,新建的m也同步到了全局变量中,这样就可以比较容易的划分全局变量和局部变量再进行统一赋值或者修改。

读取和修改成员变量

在python中每一个定义的object都有一个隐藏属性__dict__,这是一个字典,其中包含了所有的成员变量名和成员变量值。在前一篇博客中,我们就介绍了通过__dict__去给类中的成员变量进行赋值,非常的方便。我们可以通过一个示例来看看__dict__中所包含的内容:

x = 0

class Obj:
    def __init__(self,y):
        self.m = 2
        self.func(y)

    def func(self, y, z=1):
        print (self.__dict__)

if __name__ == '__main__':
    Obj(2)

    # {'m': 2}

从输出结果中我们就可以看到,__dict__输出的内容非常的纯净,就是所有的成员变量名和变量值。而成员变量虽然是一个对象的属性,但是其操作方式跟全局变量globals是非常接近的,不像locals一样只读,具体示例如下:

x = 0

class Obj:
    def __init__(self,y):
        self.m = 2
        self.func(y)

    def func(self, y, z=1):
        self.m = 5
        self.__dict__['n'] = 6
        print (self.__dict__)
        print (self.m, self.n)

if __name__ == '__main__':
    Obj(2)
    # {'m': 5, 'n': 6}
    # 5

    # 6

在这个案例中,我们修改了成员变量的值,也使用__dict__新建了一个成员变量的值,可以看到最终都有同步到变量空间中,这样就完成了成员变量的修改。

总结

Python本身是一门比较灵活便捷的编程语言,但是便捷往往有可能伴随着一些风险,比如exec和eval等内置函数的实现,有可能导致sandbox escaping的问题。而有时候我们又需要一些批量化的操作,比如批量化的创建或者修改局部、全局或者是成员变量,这样就需要我们首先要把所有的变量名存成字符串,在需要的时候再作为变量名去调用。在这篇文章中,我们介绍了一系列非exec和eval的操作(并不是说没有风险,也引用了ctype和sys定义的数据帧),来查看和定义、修改所需的各种变量。

相关文章
|
2天前
|
SQL JSON C语言
Python中字符串的三种定义方法
Python中字符串的三种定义方法
|
3天前
|
程序员 数据库连接 API
分享一个解决 EF 性能低的思路,通过 Python 访问心跳侦测 API 保持 EF 在线
分享一个解决 EF 性能低的思路,通过 Python 访问心跳侦测 API 保持 EF 在线
|
5天前
|
索引 Python
Python学习笔记----操作字符串
这篇文章是一份Python字符串操作的学习笔记,涵盖了字符串相加、序列相加、字符串长度和字符的查找、统计、分割、连接、替换、去除空白、大小写转换以及判断字符串是否由字母和数字组成等常用方法。
Python学习笔记----操作字符串
|
8天前
|
Python
2:Python字符串与数字
这段代码示例展示了Python中的字符串定义、字符串操作(如连接和重复)、基本算术运算以及条件判断。字符串可通过单双引号定义。字符串支持加法(连接)与乘法(重复)。数字变量支持加减乘除等运算。示例还对比了两个条件语句代码块:第一个因使用全角冒号及未闭合字符串引发语法错误;第二个则正确无误,当条件为真时将输出&quot;我是神仙&quot;和&quot;我是高手&quot;。这强调了遵循Python语法规范的重要性。
|
18天前
|
IDE API 开发工具
|
26天前
|
Unix API Python
python提供了两个级别访问的网络服务
【7月更文挑战第23天】python提供了两个级别访问的网络服务
31 7
|
1月前
|
存储 Python 容器
Python基础语法:变量和数据类型详解(整数、浮点数、字符串、布尔值)
变量和数据类型是Python编程的基础,理解这些概念对于编写高效和正确的代码至关重要。通过本文的介绍,希望你能对Python中的变量和常用数据类型有一个清晰的认识,并能够在实际编程中灵活运用这些知识。
|
24天前
|
Python
Python中的全局变量和局部变量是什么?
在 Python 中, 全局变量与局部变量在作用域及访问权限上有着明显区别。全局变量在整个程序范围内均可访问, 如定义 `global_var` 并在函数 `func_using_global()` 内使用。局部变量仅在其定义的函数内有效, 如 `func_creating_local()` 中的 `local_var`, 在函数外访问会引发 `NameError`。
|
3天前
|
存储 数据安全/隐私保护 索引
Python基础语法day02字符串详解和列表
Python基础语法day02字符串详解和列表
|
27天前
|
Python
Python小技巧:一种字符串的排序方式
Python小技巧:一种字符串的排序方式
15 0