V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
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 · 2017-11-07 09:52:51 +08:00 · 5499 次点击
    这是一个创建于 2572 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

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

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

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


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

    如果有源码,想不到在程序里分析参数名称的用处。
    SuperMild
        20
    SuperMild  
       2017-11-07 11:27:26 +08:00
    还不如直接分析源码
    qsnow6
        21
    qsnow6  
       2017-11-07 11:27:30 +08:00
    1 以 kwargs 传进函数里
    2 在 locals()里找,找到了打印出来
    wcsjtu
        22
    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
        23
    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
        24
    lrxiao  
       2017-11-07 11:50:45 +08:00
    emmm 原来可以 kwargs 23333
    lrxiao
        25
    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
        26
    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
        27
    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
        28
    takeoffyoung  
       2017-11-07 14:16:19 +08:00   ❤️ 1
    这个不是叫变量名么?字面量是什么意思?
    https://stackoverflow.com/questions/932818/retrieving-a-variables-name-in-python-at-runtime

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

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

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

    taskflow 里的处理非常不错 还能 rebind 参数名避免冲突
    billgreen1
        32
    billgreen1  
       2017-11-08 11:41:56 +08:00
    这在 R 语言里叫做 non standard evaluation, python 也可以实现。参考
    http://www.ibis-project.org/design-composability/
    FaiChou
        33
    FaiChou  
       2017-11-09 10:15:53 +08:00
    flask 里的 render_templete 函数就有此功能
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2534 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 21ms · UTC 01:36 · PVG 09:36 · LAX 17:36 · JFK 20:36
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.