如何获取传递给函数的变量的原始变量名[重复]

2025-01-10 08:46:00
admin
原创
19
摘要:问题描述:是否可以获取传递给函数的变量的原始变量名?例如foobar = "foo" def func(var): print var.origname 以便:func(foobar) 返回:foobar我试图制作如下功能:def log(soup): f = open(...

问题描述:

是否可以获取传递给函数的变量的原始变量名?例如

foobar = "foo"

def func(var):
    print var.origname

以便:

func(foobar)

返回:

foobar

我试图制作如下功能:

def log(soup):
    f = open(varname+'.html', 'w')
    print >>f, soup.prettify()
    f.close()

并让函数根据传递给它的变量的名称生成文件名。

我想如果不可能的话我每次只需要将变量和变量的名称作为字符串传递。


解决方案 1:

编辑:说清楚一点,我完全不建议使用它,它会损坏、会造成混乱、不会以任何方式帮助你,但出于娱乐/教育目的它是可行的。

您可以随意修改该inspect模块,我不推荐这样做,但您可以这样做......

import inspect

def foo(a, f, b):
    frame = inspect.currentframe()
    frame = inspect.getouterframes(frame)[1]
    string = inspect.getframeinfo(frame[0]).code_context[0].strip()
    args = string[string.find('(') + 1:-1].split(',')
    
    names = []
    for i in args:
        if i.find('=') != -1:
            names.append(i.split('=')[1].strip())
        
        else:
            names.append(i)
    
    print names

def main():
    e = 1
    c = 2
    foo(e, 1000, b = c)

main()

输出:

['e', '1000', 'c']

解决方案 2:

为了补充Michael Mrozek 的回答,您可以通过以下方式提取完整代码的精确参数:

import re
import traceback

def func(var):
    stack = traceback.extract_stack()
    filename, lineno, function_name, code = stack[-2]
    vars_name = re.compile(r'((.*?)).*$').search(code).groups()[0]
    print vars_name
    return

foobar = "foo"

func(foobar)

# PRINTS: foobar

解决方案 3:

看起来 Ivo 已经打败我了inspect,但是这里还有另一种实现:

import inspect

def varName(var):
    lcls = inspect.stack()[2][0].f_locals
    for name in lcls:
        if id(var) == id(lcls[name]):
            return name
    return None

def foo(x=None):
    lcl='not me'
    return varName(x)

def bar():
    lcl = 'hi'
    return foo(lcl)

bar()
# 'lcl'

当然,它是可以被愚弄的:

def baz():
    lcl = 'hi'
    x='hi'
    return foo(lcl)

baz()
# 'x'

寓意:不要这样做。

解决方案 4:

不能。它在传递给函数之前会被评估。你所能做的就是将其作为字符串传递。

解决方案 5:

如果您知道调用代码是什么样子的,您可以尝试的另一种方法是使用traceback

def func(var):
    stack = traceback.extract_stack()
    filename, lineno, function_name, code = stack[-2]

code将包含用于调用的代码行func(在您的示例中,它将是字符串func(foobar))。您可以解析它以提取参数

解决方案 6:

@Ivo Wetzel 的答案适用于在一行中进行函数调用的情况,例如

e = 1 + 7
c = 3
foo(e, 100, b=c)

如果函数调用不在一行中,例如:

e = 1 + 7
c = 3
foo(e,
    1000,
    b = c)

下面的代码有效:

import inspect, ast

def foo(a, f, b):
    frame = inspect.currentframe()
    frame = inspect.getouterframes(frame)[1]
    string = inspect.findsource(frame[0])[0]

    nodes = ast.parse(''.join(string))

    i_expr = -1
    for (i, node) in enumerate(nodes.body):
        if hasattr(node, 'value') and isinstance(node.value, ast.Call)
            and hasattr(node.value.func, 'id') and node.value.func.id == 'foo'  # Here goes name of the function:
            i_expr = i
            break

    i_expr_next = min(i_expr + 1, len(nodes.body)-1)  
    lineno_start = nodes.body[i_expr].lineno
    lineno_end = nodes.body[i_expr_next].lineno if i_expr_next != i_expr else len(string)

    str_func_call = ''.join([i.strip() for i in string[lineno_start - 1: lineno_end]])
    params = str_func_call[str_func_call.find('(') + 1:-1].split(',')

    print(params)

您将获得:

[u'e', u'1000', u'b = c']

但这仍有可能破裂。

解决方案 7:

您可以使用 python-varname 包

from varname import nameof

s = 'Hey!'

print (nameof(s))

输出:

s

包装如下:

https://github.com/pwwang/python-varname

解决方案 8:

为了方便后人理解,下面是我为这项任务编写的一些代码,总的来说,我认为 Python 中缺少一个模块,以便让每个人都能对调用方环境进行良好而强大的检查。类似于 rlang eval 框架为 R 提供的。

import re, inspect, ast

#Convoluted frame stack walk and source scrape to get what the calling statement to a function looked like.
#Specifically return the name of the variable passed as parameter found at position pos in the parameter list.
def _caller_param_name(pos):
    #The parameter name to return
    param = None
    #Get the frame object for this function call
    thisframe = inspect.currentframe()
    try:
        #Get the parent calling frames details
        frames = inspect.getouterframes(thisframe)
        #Function this function was just called from that we wish to find the calling parameter name for
        function = frames[1][3]
        #Get all the details of where the calling statement was
        frame,filename,line_number,function_name,source,source_index = frames[2]
        #Read in the source file in the parent calling frame upto where the call was made
        with open(filename) as source_file:
            head=[source_file.next() for x in xrange(line_number)]
        source_file.close()

        #Build all lines of the calling statement, this deals with when a function is called with parameters listed on each line
        lines = []
        #Compile a regex for matching the start of the function being called
        regex = re.compile(r'.?s*%ss*(' % (function))
        #Work backwards from the parent calling frame line number until we see the start of the calling statement (usually the same line!!!)
        for line in reversed(head):
            lines.append(line.strip())
            if re.search(regex, line):
                break
        #Put the lines we have groked back into sourcefile order rather than reverse order
        lines.reverse()
        #Join all the lines that were part of the calling statement
        call = "".join(lines)
        #Grab the parameter list from the calling statement for the function we were called from
        match = re.search('.?s*%ss*((.*))' % (function), call)
        paramlist = match.group(1)
        #If the function was called with no parameters raise an exception
        if paramlist == "":
            raise LookupError("Function called with no parameters.")
        #Use the Python abstract syntax tree parser to create a parsed form of the function parameter list 'Name' nodes are variable names
        parameter = ast.parse(paramlist).body[0].value
        #If there were multiple parameters get the positional requested
        if type(parameter).__name__ == 'Tuple':
            #If we asked for a parameter outside of what was passed complain
            if pos >= len(parameter.elts):
                raise LookupError("The function call did not have a parameter at postion %s" % pos)
            parameter = parameter.elts[pos]
        #If there was only a single parameter and another was requested raise an exception
        elif pos != 0:
            raise LookupError("There was only a single calling parameter found. Parameter indices start at 0.")
        #If the parameter was the name of a variable we can use it otherwise pass back None
        if type(parameter).__name__ == 'Name':
            param = parameter.id
    finally:
        #Remove the frame reference to prevent cyclic references screwing the garbage collector
        del thisframe
    #Return the parameter name we found
    return param

解决方案 9:

如果您想要一个键值对关系,也许使用字典会更好?

...或者如果您试图从您的代码中创建一些自动文档,也许像 Doxygen ( http://www.doxygen.nl/ ) 这样的东西可以为您完成这项工作?

解决方案 10:

我想知道IceCream是如何解决这个问题的。所以我查看了源代码,并想出了以下(略微简化的)解决方案。它可能不是 100% 可靠的(例如,我删除了get_text_with_indentation并且我假设只有一个函数参数),但它适用于不同的测试用例。它不需要解析源代码本身,因此它应该比以前的解决方案更强大、更简单。

#!/usr/bin/env python3
import inspect
from executing import Source
    
def func(var):
    callFrame = inspect.currentframe().f_back
    callNode = Source.executing(callFrame).node
    source = Source.for_frame(callFrame)
    expression = source.asttokens().get_text(callNode.args[0])
    print(expression, '=', var)
    
i = 1
f = 2.0
dct = {'key': 'value'}
obj = type('', (), {'value': 42})
    
func(i)
func(f)
func(s)
func(dct['key'])
func(obj.value)

输出:

i = 1
f = 2.0
s = string
dct['key'] = value
obj.value = 42

更新:如果您想将“魔法”移到一个单独的函数中,您只需再往后退一帧并使用额外的 即可f_back

def get_name_of_argument():
    callFrame = inspect.currentframe().f_back.f_back
    callNode = Source.executing(callFrame).node
    source = Source.for_frame(callFrame)
    return source.asttokens().get_text(callNode.args[0])

def func(var):
    print(get_name_of_argument(), '=', var)

解决方案 11:

如果您想要获取调用者参数(如@Matt Oates答案中所示)而不使用源文件(即来自 Jupyter Notebook),此代码(结合@Aeon 答案)可以解决问题(至少在某些简单情况下):

def get_caller_params():
    # get the frame object for this function call
    thisframe = inspect.currentframe()

    # get the parent calling frames details
    frames = inspect.getouterframes(thisframe)

    # frame 0 is the frame of this function
    # frame 1 is the frame of the caller function (the one we want to inspect)
    # frame 2 is the frame of the code that calls the caller
    caller_function_name = frames[1][3]
    code_that_calls_caller = inspect.findsource(frames[2][0])[0]

    # parse code to get nodes of abstract syntact tree of the call
    nodes = ast.parse(''.join(code_that_calls_caller))

    # find the node that calls the function
    i_expr = -1
    for (i, node) in enumerate(nodes.body):
        if _node_is_our_function_call(node, caller_function_name):
            i_expr = i
            break

    # line with the call start
    idx_start = nodes.body[i_expr].lineno - 1

    # line with the end of the call
    if i_expr < len(nodes.body) - 1:
        # next expression marks the end of the call
        idx_end = nodes.body[i_expr + 1].lineno - 1
    else:
        # end of the source marks the end of the call
        idx_end = len(code_that_calls_caller)

    call_lines = code_that_calls_caller[idx_start:idx_end]
    str_func_call = ''.join([line.strip() for line in call_lines])
    str_call_params = str_func_call[str_func_call.find('(') + 1:-1]
    params = [p.strip() for p in str_call_params.split(',')]

    return params


def _node_is_our_function_call(node, our_function_name):
    node_is_call = hasattr(node, 'value') and isinstance(node.value, ast.Call)
    if not node_is_call:
        return False

    function_name_correct = hasattr(node.value.func, 'id') and node.value.func.id == our_function_name
    return function_name_correct

然后您可以像这样运行它:

def test(*par_values):
    par_names = get_caller_params()
    for name, val in zip(par_names, par_values):
        print(name, val)


a = 1
b = 2
string = 'text'
test(a, b,
  string
)

获得所需的输出:

a 1
b 2
string text

解决方案 12:

由于您可以拥有多个具有相同内容的变量,因此不必传递变量(内容),将其名称传递到字符串中并从调用者堆栈框架中的本地字典中获取变量内容可能会更安全(并且更简单)。:

def displayvar(name):
    import sys
    return name+" = "+repr(sys._getframe(1).f_locals[name])

解决方案 13:

如果恰巧该变量是一个可调用函数,它将具有一个__name__属性。

例如,用于记录函数执行时间的包装器:

def time_it(func, *args, **kwargs):
    start = perf_counter()
    result = func(*args, **kwargs)
    duration = perf_counter() - start

    print(f'{func.__name__} ran in {duration * 1000}ms')

    return result
相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1000  
  华为作为全球领先的信息与通信技术(ICT)解决方案提供商,其全球化项目的成功离不开高效的项目管理方法。其中,集成产品开发(IPD)流程是华为项目管理体系的核心组成部分。IPD流程不仅帮助华为在复杂的全球化项目中实现了资源的高效整合,还通过跨部门协作和持续优化,确保了项目的高质量交付。本文将通过具体案例,分析华为IPD流...
IPD测试流程   0  
  IPD(Integrated Product Development)是一种以跨职能团队协作为核心的产品开发流程,旨在通过整合资源、优化流程和提高决策效率,实现产品从概念到市场的快速、高效交付。IPD流程的核心思想是将传统的串行开发模式转变为并行开发模式,通过跨部门协作和早期风险识别,减少开发周期中的浪费和返工。这种方...
IPD流程分为几个阶段   0  
  华为的集成产品开发(IPD)流程是企业项目管理中的经典实践,其核心在于通过跨部门协同实现高效的产品开发。IPD流程强调从市场需求到产品交付的全生命周期管理,而跨部门沟通则是这一流程成功的关键。在华为的实践中,跨部门沟通不仅仅是信息的传递,更是团队协作、目标对齐和资源整合的重要手段。本文将深入探讨IPD流程中的跨部门沟通...
IPD项目管理咨询   0  
  IPD流程全称是集成产品开发(Integrated Product Development),它是一种以客户需求为导向、跨部门协作的产品开发模式。与传统产品开发模式相比,IPD强调在产品开发的早期阶段就整合市场、研发、制造、采购等多个部门的资源和能力,通过并行工程和协同工作来提升开发效率。IPD流程的核心在于打破部门壁...
IPD产品开发流程   0  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用