推荐学习书目
› 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 · 6590 views
    This topic created in 3293 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
    crazycabbage
    Nov 7, 2017 via Android
    1
    def get_variable_name(s):
    loc = locals()
    print loc
    for key in loc:
    if loc[key] == s:
    return key
    sagaxu
    sagaxu
    Nov 7, 2017
    2
    这个 name 不一定存在啊,get_var_literal(foo())
    ipwx
    ipwx
    Nov 7, 2017
    3
    你的这个需求是很别扭的,建议用别的设计。
    est
    est
    Nov 7, 2017
    4
    最喜欢这种扯蛋的问题了

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

    然而何必呢?我看不出来 Python 里面搞这一套的意义。换个设计不是更好?
    alber1986
    alber1986
    Nov 7, 2017
    8
    看看 Python 视频教程就懂了
    http://www.sucaihuo.com/video/172-2-0
    vtoexsir
    vtoexsir
    OP
    Nov 7, 2017
    9
    @est inspect.getargspec(get_var_literal).args # 这是获取形参的字面值,不是实参的字面值
    不过非常感谢,inspection 可能会有戏解决这个问题!
    crazycabbage
    crazycabbage
    Nov 7, 2017 via Android
    10
    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
    lrxiao
    Nov 7, 2017
    11
    @crazycabbage
    用 locals 不太好
    不如 loc = sys._getframe(1)
    lrxiao
    lrxiao
    Nov 7, 2017
    12
    loc = sys._getframe(1).f_locals
    qsnow6
    qsnow6
    Nov 7, 2017
    13
    >>> def foo(**kwargs):
    for arg_name in kwargs:
    return kwargs[arg_name], arg_name


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

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

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

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

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

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

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

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

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

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