技术背景

在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

参考链接

  1. https://www.cnblogs.com/dechinphy/p/dict-class.html

Python3通过字符串访问与修改局部变量的更多相关文章

  1. 匿名内部类为什么访问外部类局部变量必须是final的?

    1.内部类里面使用外部类的局部变量时,其实就是内部类的对象在使用它,内部类对象生命周期中都可能调用它,而内部类试图访问外部方法中的局部变量时,外部方法的局部变量很可能已经不存在了,那么就得延续其生命, ...

  2. 为什么Java匿名内部类访问的外部局部变量或参数需要被final修饰

    大部分时候,类被定义成一个独立的程序单元.在某些情况下,也会把一个类放在另一个类的内部定义,这个定义在其他类内部的类就被称为内部类,包含内部类的类也被称为外部类. class Outer { priv ...

  3. 为什么局部内部类和匿名内部类只能访问 final 的局部变量?

    首先,我们看一个局部内部类的例子: class OutClass { private int age = 12; public void outPrint(final int x) { class I ...

  4. 使用vscode访问和修改远程计算机文件

    使用vscode访问和修改远程文件,分三步实现:在远程linux机器上安装rmate:在本地windows上安装openssh:在vscode中安装扩展remote vscode. 1. 在远程lin ...

  5. touch — 设定文件的访问和修改时间

    PHP touch 设定文件的访问和修改时间 touch (PHP 4, PHP 5) touch — 设定文件的访问和修改时间 说明 bool touch ( string $filename [, ...

  6. Python3 格式化字符串

    Python3 格式化字符串 在Python 3.6之前,有两种将Python表达式嵌入到字符串文本中进行格式化的主要方法:%-formatting和str.format() 一.%-formatti ...

  7. 【WPF】当 ItemsSource 正在使用时操作无效。改用 ItemsControl.ItemsSource 访问和修改元素

    问题: 中文版报错:Additional information: 当 ItemsSource 正在使用时操作无效.改用 ItemsControl.ItemsSource 访问和修改元素. 英文版报错 ...

  8. Python3.x:访问带参数链接并且获取返回json串

    Python3.x:访问带参数链接并且获取返回json串 示例一: import json import xml.dom.minidom from urllib import request, par ...

  9. Java学习笔记23---内部类之局部内部类只能访问final的局部变量

    局部内部类是定义在方法体或代码块中的类,在笔记19中已有过简单介绍. 今天要讨论的是局部内部类为什么只能访问为常量的局部变量. 作者: 博客园--蝉蝉 请尊重作者劳动成果,转载请在标题注明“转载”字样 ...

随机推荐

  1. 转发有关tomcat和nginx

    nginx 与 tomcat 组合搭建web服务   部分内容转自 http://www.cnblogs.com/naaoveGIS/ 1. Web服务 nginx是常用的web服务器,用于获取静态资 ...

  2. Maven安装与配置——详细教程

    一.安装Maven 进入Maven官网,下载安装包(https://maven.apache.org/download.cgi) . 2.下载完成后,解压到某一路径下.本文以C:\Soft\Java\ ...

  3. zabbix服务端的部署及zabbix简单介绍

    Zabbix企业级监控方案--服务端部署 Zabbix 是一个基于 WEB 界面的提供分布式系统监视以及网络监视功能的企业级的开源解决方案. zabbix 能监视各种网络参数,保证服务器系统的安全运营 ...

  4. 微信小程序获取当前的时间戳

    js文件中进行计算var timestamp = Date.parse(new Date()); timestamp = timestamp / 1000; console.log(timestamp ...

  5. [翻译] TensorFlow 分布式之论文篇 "Implementation of Control Flow in TensorFlow"

    [翻译] TensorFlow 分布式之论文篇 "Implementation of Control Flow in TensorFlow" 目录 [翻译] TensorFlow ...

  6. php 魔术方法,未声明属性,数组的注释 - 帮助ide跳转,提高可读性

    本人使用vscode编辑器.其他编辑器未测试. 经过1: 用laravel开发了一段时间,最麻烦的一点就是许多时候编辑器无法智能提示和辅助跳转. 有一款ide-helper的插件,感觉不是很好用,经常 ...

  7. Linux服务器 I/O 原理和流程

    计算机I/OI/O在计算机中指Input/Output,IOPS (Input/Output Per Second)即每秒的输入输出量(或读写次数),是衡量磁盘性能的主要指标之一.IOPS是指单位时间 ...

  8. Mysql 8.0 配置主从备份

    my.ini文件的位置 mysql 8.0安装完过后没有my.ini疑惑了我好久,最后发现,配置文件在,C盘的一个隐藏文件夹里面 具体路径如下图 主库配置 修改主库INI文件 在[mysqld]节点添 ...

  9. 如何用 Redis 解决海量重复提交问题

    前言 在实际的开发项目中,一个对外暴露的接口往往会面临很多次请求,我们来解释一下幂等的概念:任意多次执行所产生的影响均与一次执行的影响相同.按照这个含义,最终的含义就是 对数据库的影响只能是一次性的, ...

  10. 张高兴的 Entity Framework Core 即学即用:(一)创建第一个 EF Core 应用

    写在前面 Entity Framework Core (EF Core) 是 .NET 平台流行的对象关系映射(ORM)框架.虽然 .NET 平台中 ORM 框架有很多,比如 Dapper.NHibe ...