Python中变量动态赋值引发的一些思考

Python中变量动态赋值引发的一些思考

之前我在一篇微信文章中看到的一个问题。这里引用文章中的一段内容。

已知 list = [‘A’, ‘B’, ‘C’, ‘D’] , 如何才能得到以 list 中元素命名的新列表 A = [], B = [], C = [], D = []呢?

这个问题的意思是,将字符串内容作为其它对象的变量名list 中的元素是字符串,此处的 ‘A’-‘D’ 是常量 ,而在要求的结果中,A-D 是变量 。但如果强行直接将常量当做变量使用,它会报错。

报错中的literal 指的是字面量 ,这是计算机科学中常见的一个概念,用于表达源代码中的固定值。 例如,整数、浮点数、字符串等基本类型,就是字面量。

我看到这个问题时,我的第一反应就是用eval这个函数。我相信凡是搞web安全的同学对这个函数都再熟悉不过了。最原始的一句话木马就是用的eval。不过我觉得最简单的办法还是用globals()。

方法一:


>>> list1 = [‘A’‘B’‘C’‘D’]
>>> for i in list1:
>>>     globals()[i] = []
>>> A
[]


这个方法通过修改全局命名空间,巧妙地“定义”出了新的变量。globals() 方法取出来的是一个字典,字符串 ‘A’ 是其中一个键值(key),而这个键值恰恰是全局命名空间中的一个变量,这就实现了从常量到变量的转化。在数据结构层面上,空列表 [] 作为一个值(value)跟它的字符串键值绑定在一起,而在运用层面上,它作为变量内容而跟变量名绑定在一起。

我看到这个方法时也非常感慨为何没有想到如此巧妙的方法。多半还是因为对它的理解不够透彻。所以重新查看了globals()的使用。

Python globals() 函数

globals() 函数会以字典类型返回当前位置的全部全局变量。

直接分别每次打印globals()。


print(globals())
list1 = ['A', 'B', 'C', 'D']
print(globals())
for i in list1:
    globals()[i] = []
    print(globals())
-----------------------------------
第一次打印globals()的内容:
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x000001E07DAD5C50>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'E:/pythonlab/test101.py', '__cached__': None}
后面每次打印的内容,前面都是相同的,我就用截图的方式展示:

一目了然,globals()返回的是一个字典,通过向这个字典里追加键值对实现了动态赋值。

方法二:


>>> list1 = [‘A’‘B’‘C’‘D’]
>>> for i in list1:
>>>     exec(f“{i} = []”)
>>> A
[]


用到了 Python 3.6 才引入的 f-strings 特性,事实上,在较低版本中,也是可以实现的,只需要保证 exec() 方法接收的参数是包含了变量 i 的字符串即可,例如这样写:

# 以下代码可替换上例的第 4 行
exec(i + ” = []”)
# 或者:
exec(“{} = []”.format(i))
# 或者:
exec(‘ ‘.join([i, ‘= []’]))

这个方法的核心在于使用 exec() 方法,它是内置的,用途是执行储存在字符串或文件中的代码段其实和eval也是一个道理。

上面两种方法都是间接的动态方法:一个是动态地进行变量赋值,通过修改命名空间而植入变量;一个是动态地执行代码,可以说是通过“走后门”的方式,安插了变量。算是殊途同归了。

这让我想起了之前我在写接口的时候遇到的一个问题,也算是自己遇到的一个实际案例吧。我想实现向web页面传参(模块名&&函数名)来查看python各个模块的源码。

因为当时还不会Django,所以就用的php来接收参数。大家可能没有用过python中的内置模块 inspect。inspect模块可以用来获取对象的信息,对象可以是类,方法。调用其中的inspect.getsource()方法,简单的示例如下:


import inspect
import requests

print(inspect.getsource(requests.get))
------------------------------------------
def get(url, params=None, **kwargs):
    r"""Sends a GET request.

    :param url: URL for the new :class:`Request` object.
    :param params: (optional) Dictionary, list of tuples or bytes to send
        in the body of the :class:`Request`.
    :param \*\*kwargs: Optional arguments that ``request`` takes.
    :return: :class:`Response <Response>` object
    :rtype: requests.Response
    """

    kwargs.setdefault('allow_redirects', True)
    return request('get', url, params=params, **kwargs)

这样直接打印出了requests.get方法的源码,通过这样来写一个接口来调用便更加方便。

但是传参的时候,就遇到问题了。因为getsource方法接收的是一个对象!!!我在这里纠结了好久,参数肯定是字符串,这样必然报错。无奈之下,请求了老师。他建议我用反射机制,虽然我觉得他说的好像很有道理的,但是我又想了想,我又不是为了去调用这个函数,而是把它作为对象传入,这样还是不行。

思索了好久之后,突然灵光一闪,一句话木马!!我为什么不用eval呢,把字符串当作命令执行了,通过拼接字符串的方式将传入进去。最后终于成功了。这里代码很简单,就不展示了。

参考文章:

Python进阶:如何将字符串常量转为变量?

赞赏

微信赞赏支付宝赞赏

Zgao

愿有一日,安全圈的师傅们都能用上Zgao写的工具。