Python 怎么获取函数参数的字面量?

2017-11-07 09:52:51 +08:00
 vtoexsir

def get_var_literal(var):
var_literal=? # 怎么获取到 var 的字面量?
print(var_literal)
s = 'abc'
get_var_literal(s)
#=>s 希望能打印出 s,而不是打印出来 abc

5500 次点击
所在节点    Python
33 条回复
qsnow6
2017-11-07 11:27:30 +08:00
1 以 kwargs 传进函数里
2 在 locals()里找,找到了打印出来
wcsjtu
2017-11-07 11:46:14 +08:00
难道是逆向 pyc?
lz 你可能需要这个 https://docs.python.org/2/library/dis.html python 字节码分析

比如说, 你在 m.py 中定义

def func(x, y):
a = 2
return x+y+a

然后在 main.py 中导入这个模块

import dis
import m
print dis.dis(m.func)

打印出来的结果是
2 0 LOAD_CONST 1 (2)
3 STORE_FAST 2 (a)

3 6 LOAD_FAST 2 (a)
9 LOAD_FAST 0 (x)
12 BINARY_ADD
13 LOAD_FAST 1 (y)
16 BINARY_ADD
17 PRINT_ITEM
18 PRINT_NEWLINE
19 LOAD_CONST 0 (None)
22 RETURN_VALUE

中间那一列就是字节码, 后面的就是变量名和对应的值了。
这个函数的所有信息都在这了。 如果有闭包的话,情况稍微复杂些
lz 有兴趣可以研究研究
lrxiao
2017-11-07 11:49:51 +08:00
好的 我又丧病的 hack 了

import.sys
import.dis
import.types
from.opcode.import.*

#.ref:.https://nedbatchelder.com/blog/200804/wicked_hack_python_bytecode_tracing.html
def.hack_line_numbers(f):
....""".Replace.a.code.object's.line.number.information.to.claim.that.every
........byte.of.the.bytecode.is.a.new.line...Returns.a.new.code.object.
........Also.recurses.to.hack.the.line.numbers.in.nested.code.objects.
...."""
....code.=.f.__code__
....n_bytes.=.len(code.co_code)
....new_lnotab.=."\x01\x01".*.(n_bytes-1)
....new_consts.=.[]
....for.const.in.code.co_consts:
........if.type(const).==.types.CodeType:
............new_consts.append(hack_line_numbers(const))
........else:
............new_consts.append(const)
....new_code.=.types.CodeType(
........code.co_argcount,.code.co_kwonlyargcount,.code.co_nlocals,.code.co_stacksize,.code.co_flags,
........code.co_code,.tuple(new_consts),.code.co_names,.code.co_varnames,
........code.co_filename,.code.co_name,.0,.str.encode(new_lnotab),.code.co_freevars,.code.co_cellvars
........)..
....f.__code__.=.new_code
....return.f

def.get_variable_name_simple(var):
....loc.=.sys._getframe(1).f_locals
....names.=.[]
....for.k,.v.in.loc.items():
........if.v.==.var:
............names.append(k)
....return.name

#.don't.work.with.REPL
#.the.caller.function.should.hack.line.number.to.get.accurate.lineno
def.get_variable_name(var):
....last_frame.=.sys._getframe(1)
....frame.=.sys._getframe(0)
....last_code.=.sys._getframe(1).f_code
....last_code_arr.=.bytearray(last_code.co_code)
....call_lineno.=.last_frame.f_lineno
....#.last_code_arr[last_frame.f_lineno].=.opmap['CALL_FUNCTION']
....load_var_op.=.last_code_arr[call_lineno.-.2]
....load_var_pos.=.last_code_arr[call_lineno.-.1]
....if.load_var_op.==.opmap['LOAD_NAME'].or.load_var_op.==.opmap['LOAD_FAST']:
........return.last_code.co_varnames[load_var_pos]
....if.load_var_op.==.opmap['LOAD_GLOBAL']:
........return.last_code.co_names[load_var_pos]
....print("I.don't.know,.maybe.just.consts")
....return.None

def.do_nothing():
....pass

@hack_line_numbers
def.g():
....ar.=.1
....arrrrrgggghhhhhh.=.1
....do_nothing()
....do_nothing()
....do_nothing()
....do_nothing()
....n.=.get_variable_name(arrrrrgggghhhhhh)
....print(n)

> arrrrrgggghhhhhh

空格全部替换的. 为了缩进。。。。。
lrxiao
2017-11-07 11:50:45 +08:00
emmm 原来可以 kwargs 23333
lrxiao
2017-11-07 13:00:41 +08:00
emmm 解决了传入 attr 访问

import sys
import dis
import types
from opcode import *
import inspect

# ref: https://nedbatchelder.com/blog/200804/wicked_hack_python_bytecode_tracing.html
def hack_line_numbers(f):
....""" Replace a code object's line number information to claim that every
........byte of the bytecode is a new line. Returns a new code object.
........Also recurses to hack the line numbers in nested code objects.
...."""
....code = f.__code__
....n_bytes = len(code.co_code)
....new_lnotab = "\x01\x01" * (n_bytes-1)
....new_consts = []
....for const in code.co_consts:
........if type(const) == types.CodeType:
............new_consts.append(hack_line_numbers(const))
........else:
............new_consts.append(const)
....new_code = types.CodeType(
........code.co_argcount, code.co_kwonlyargcount, code.co_nlocals, code.co_stacksize, code.co_flags,
........code.co_code, tuple(new_consts), code.co_names, code.co_varnames,
........code.co_filename, code.co_name, 0, str.encode(new_lnotab), code.co_freevars, code.co_cellvars
........)
....f.__code__ = new_code
....f.__is_lineno_hacked__ = True
....return f

def get_variable_name_easy(**kwargs):
....for arg_name in kwargs:
........return kwargs[arg_name], arg_name

def get_variable_name_simple(var):
....loc = sys._getframe(1).f_locals
....names = []
....for k, v in loc.items():
........if v == var:
............names.append(k)
....return names

# Don't work with REPL, nothing named after f_globals['<module>']
# Need to redirect a frame
# If not handled with hacked lineno,
# We must use 1-level nested no-argument function
# which directly ref to ordered variable
def get_variable_name(var):
....last_frame = sys._getframe(1)
....last_code = last_frame.f_code
....last_func_name = last_code.co_name
....last_func = None
....if last_func_name in last_frame.f_globals.keys():
........last_func = last_frame.f_globals[last_func_name]
....elif last_func_name in last_frame.f_locals.keys():
........last_func = last_frame.f_globals[last_func_name]
....else:
........# nested support
........if last_func_name in last_frame.f_back.f_globals.keys():
............last_func = last_frame.f_back.f_globals[last_func_name]
........elif last_func_name in last_frame.f_back.f_locals.keys():
............last_func = last_frame.f_back.f_locals[last_func_name]
....is_lineno_hacked = False;
....if not last_func:
........print("Holy crap. Assume we have hacked our lineno")
........is_lineno_hacked = True
....elif '__is_lineno_hacked__' in last_func.__dict__.keys():
........is_lineno_hacked = True
....if is_lineno_hacked:
........last_code_arr = bytearray(last_code.co_code)
........call_lineno = last_frame.f_lineno
........# last_code_arr[last_frame.f_lineno] = opmap['CALL_FUNCTION']
........attr_name = []
........pos_code = 2
........pos_off = 1
........load_var_op = last_code_arr[call_lineno - pos_code]
........load_var_pos = last_code_arr[call_lineno - pos_off]
........while load_var_op == opmap['LOAD_ATTR']:
............attr_name.append(last_code.co_names[load_var_pos])
............load_var_op = last_code_arr[call_lineno - pos_code]
............load_var_pos = last_code_arr[call_lineno - pos_off]
............pos_code += 2
............pos_off += 2
........if load_var_op == opmap['LOAD_FAST']:
............attr_name.append(last_code.co_varnames[load_var_pos])
............return '.'.join(attr_name)
........elif load_var_op == opmap['LOAD_GLOBAL'] or load_var_op == opmap['LOAD_NAME']:
............attr_name.append(last_code.co_names[load_var_pos])
............return '.'.join(attr_name)
........elif load_var_op == opmap['LOAD_DEREF']:
............attr_name.append(last_code.co_freevars[load_var_pos])
............return '.'.join(attr_name)

........print("I don't know, maybe just consts")
........return None
....else:
........last_func = hack_line_numbers(last_func)
........sys._getframe(0).f_locals[last_func_name] = last_func
........return last_func()
........# sys._getframe(0).f_back = last_frame.f_back
........# last_frame.clear()

def do_nothing():
....pass

@hack_line_numbers
def f():
....a_var = 'str'
....print(get_variable_name(a_var)) # a_var


def g():
....ar = 1
....arrrrrgggghhhhhh = 1
....do_nothing()
....do_nothing()
....do_nothing()
....do_nothing()
....name = None
....def nested_get_varname():
........return get_variable_name(arrrrrgggghhhhhh)
....name = nested_get_varname()
....print(name) # arrrrrgggghhhhh

class A:
....pass

def h():
....test = A()
....test.t = A()
....test.t.t = 1
....return get_variable_name(test.t.t) # test.t.t

def u():
....return get_variable_name(1)

def test_all():
....f()
....g()
....print(h())
....print(u())
Xiaobaixiao
2017-11-07 13:23:33 +08:00
用 global 声明?

>>> def get_var_literal(var):
... global var_literal
... var_literal=666666
... print(var_literal)
...
>>> s='abc'
>>> get_var_literal(s)
666666
wizardoz
2017-11-07 14:00:12 +08:00
我觉得这个需求在 Python 中属于伪需求。楼主是不是用其它语言中的经验来考虑 Python 了?

在 Python 中
def fun(**kwargs):
print(kwargs)

fun(a=1, b=2, c=3)

可以用来完成一样的事情,并且要求在调用的时候指定参数名,语义更明确。
takeoffyoung
2017-11-07 14:16:19 +08:00
这个不是叫变量名么?字面量是什么意思?
https://stackoverflow.com/questions/932818/retrieving-a-variables-name-in-python-at-runtime

这个值只在查看堆栈信息有意义,也只能从中获取。
shiina
2017-11-07 14:36:57 +08:00
虽然不知道怎么解, 不过好像大部分回复都误解了题主的意思
s, var, abc 这三个里题主想要的是 s

@lrxiao 解法刁钻, 菜 b 一个表示看不懂
lolizeppelin
2017-11-08 10:40:40 +08:00
openstack 的 taskflow 项目里有相关代码可以直接抄

位置就在 atom 这个基类里
lolizeppelin
2017-11-08 10:44:02 +08:00
这需求主要用来处理获取函数参数列表
处理可选参数和必选参数

taskflow 里的处理非常不错 还能 rebind 参数名避免冲突
billgreen1
2017-11-08 11:41:56 +08:00
这在 R 语言里叫做 non standard evaluation, python 也可以实现。参考
http://www.ibis-project.org/design-composability/
FaiChou
2017-11-09 10:15:53 +08:00
flask 里的 render_templete 函数就有此功能

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/404184

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX