推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
vtoexsir
V2EX  ›  Python

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

  •  
  •   vtoexsir · Nov 7, 2017 · 6203 views
    This topic created in 3134 days ago, the information mentioned may be changed or developed.

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

    33 replies    2017-11-09 10:15:53 +08:00
    crazycabbage
        1
    crazycabbage  
       Nov 7, 2017 via Android
    def get_variable_name(s):
    loc = locals()
    print loc
    for key in loc:
    if loc[key] == s:
    return key
    sagaxu
        2
    sagaxu  
       Nov 7, 2017
    这个 name 不一定存在啊,get_var_literal(foo())
    ipwx
        3
    ipwx  
       Nov 7, 2017
    你的这个需求是很别扭的,建议用别的设计。
    est
        4
    est  
       Nov 7, 2017
    最喜欢这种扯蛋的问题了

    import inspect
    inspect.getargspec(get_var_literal).args
    est
        5
    est  
       Nov 7, 2017
    貌似我审题错误。LZ 自己去翻 inspect 吧
    vtoexsir
        6
    vtoexsir  
    OP
       Nov 7, 2017
    @crazycabbage
    "if loc[key] == s:" #这里的 s 不是固定不变的
    我的本意是:获取函数的实参的字面值。比如,传递 a 这个参数,就返回 a 这个字符串;传递 b 这个参数,就返回 b 这个字符串,而不管 a 和 b 自身是什么类型,也不管是什么具体的值,只获取实参的字面量
    ipwx
        7
    ipwx  
       Nov 7, 2017   ❤️ 1
    @vtoexsir 你这事情有方法做,但是你得写很多代码,而且必须源代码 .py 存在,而不是 .pyc 。首先,拿到 stacktrace,找到调用你这个函数的代码的行号。然后,用 ast 去分析那一行代码。最后,你拿到了字面量。

    然而何必呢?我看不出来 Python 里面搞这一套的意义。换个设计不是更好?
    alber1986
        8
    alber1986  
       Nov 7, 2017
    看看 Python 视频教程就懂了
    http://www.sucaihuo.com/video/172-2-0
    vtoexsir
        9
    vtoexsir  
    OP
       Nov 7, 2017
    @est inspect.getargspec(get_var_literal).args # 这是获取形参的字面值,不是实参的字面值
    不过非常感谢,inspection 可能会有戏解决这个问题!
    crazycabbage
        10
    crazycabbage  
       Nov 7, 2017 via Android
    a=123
    loc = locals()

    def get_variable_name(s):
    ...print loc
    ...for key in loc:
    ......if loc[key] == s:
    .........return key

    print get_variable_name(a)
    lrxiao
        11
    lrxiao  
       Nov 7, 2017
    @crazycabbage
    用 locals 不太好
    不如 loc = sys._getframe(1)
    lrxiao
        12
    lrxiao  
       Nov 7, 2017
    loc = sys._getframe(1).f_locals
    qsnow6
        13
    qsnow6  
       Nov 7, 2017
    >>> def foo(**kwargs):
    for arg_name in kwargs:
    return kwargs[arg_name], arg_name


    >>> foo(fib=1)
    (1, 'fib')
    qsnow6
        14
    qsnow6  
       Nov 7, 2017
    jmc891205
        15
    jmc891205  
       Nov 7, 2017
    请问为什么会有这样的需求啊
    Kilerd
        16
    Kilerd  
       Nov 7, 2017
    用装饰器,先把抓到的变量读取 __name__ 再传进去
    Kilerd
        18
    Kilerd  
       Nov 7, 2017
    emmm, 试了下除非使用 kwargs 的方式穿进去,不然都无解
    SuperMild
        19
    SuperMild  
       Nov 7, 2017
    楼主是想在没有源码的情况下分析源码?

    如果有源码,想不到在程序里分析参数名称的用处。
    SuperMild
        20
    SuperMild  
       Nov 7, 2017
    还不如直接分析源码
    qsnow6
        21
    qsnow6  
       Nov 7, 2017
    1 以 kwargs 传进函数里
    2 在 locals()里找,找到了打印出来
    wcsjtu
        22
    wcsjtu  
       Nov 7, 2017
    难道是逆向 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
        23
    lrxiao  
       Nov 7, 2017
    好的 我又丧病的 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
        24
    lrxiao  
       Nov 7, 2017
    emmm 原来可以 kwargs 23333
    lrxiao
        25
    lrxiao  
       Nov 7, 2017
    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
        26
    Xiaobaixiao  
       Nov 7, 2017
    用 global 声明?

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

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

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

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

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

    @lrxiao 解法刁钻, 菜 b 一个表示看不懂
    lolizeppelin
        30
    lolizeppelin  
       Nov 8, 2017 via Android
    openstack 的 taskflow 项目里有相关代码可以直接抄

    位置就在 atom 这个基类里
    lolizeppelin
        31
    lolizeppelin  
       Nov 8, 2017 via Android
    这需求主要用来处理获取函数参数列表
    处理可选参数和必选参数

    taskflow 里的处理非常不错 还能 rebind 参数名避免冲突
    billgreen1
        32
    billgreen1  
       Nov 8, 2017
    这在 R 语言里叫做 non standard evaluation, python 也可以实现。参考
    http://www.ibis-project.org/design-composability/
    ethusdt
        33
    ethusdt  
       Nov 9, 2017
    flask 里的 render_templete 函数就有此功能
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Solana   ·   1088 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 61ms · UTC 23:22 · PVG 07:22 · LAX 16:22 · JFK 19:22
    ♥ Do have faith in what you're doing.