2012-01-16 77 views
5

在Python中,有沒有辦法讓對象的實例看到分配給它的變量名?以下面爲例:一個對象可以檢查它被分配給的變量的名字嗎?

class MyObject(object): 
    pass 

x = MyObject() 

是否有可能讓MyObject看到它已被分配給變量名稱x在任何點?就像它的__init__方法一樣?

+0

這是不可能的 - 儘管近期(3-4個月前)在Python的想法名單一直存在一些關於增加一個這樣的功能的討論。 – jsbueno 2012-01-16 03:01:37

+3

簡短的回答是:不,不要嘗試..真正的答案是肯定的,但不要嘗試.. :) – wim 2012-01-16 03:08:17

回答

4

編號對象和名稱存在不同的維度。一個對象在其生命週期中可以有許多名字,並且不可能確定哪一個可能是您想要的。即使是在這裏:

class Foo(object): 
    def __init__(self): pass 

x = Foo() 

兩個名字代表相同的對象(self__init__運行,x在全球範圍內)。

4

是的,這是可能的*。然而,問題是比較困難似乎比在第一眼:

  • 有可能分配到同一個對象多個名稱
  • 可能有根本沒有名字

無論如何,知道如何找對象的名稱有時用於調試目的是有用的 - 這裏是如何做到這一點:

import gc, inspect 

def find_names(obj): 
    frame = inspect.currentframe() 
    for frame in iter(lambda: frame.f_back, None): 
     frame.f_locals 
    obj_names = [] 
    for referrer in gc.get_referrers(obj): 
     if isinstance(referrer, dict): 
      for k, v in referrer.items(): 
       if v is obj: 
        obj_names.append(k) 
    return obj_names 

如果你曾經誘惑,基地周圍的邏輯你的變量的名字,暫停一下,並考慮是否重新設計/重構代碼可以解決問題。從對象本身恢復對象名稱的需要通常意味着程序中的基礎數據結構需要重新思考。

*至少在CPython的

0

它不能通常進行,但是這可以通過使用內省和設施用於調試的程序來實現。代碼必須從「.py」文件運行,而不是從編譯的字節碼或壓縮模塊中運行,因爲它依賴於文件源代碼的讀取,從應該找到的方法中找到「它在哪裏運行」。

訣竅是訪問執行框,其中的對象被初始化爲 - with inspect.currentframe - 框架對象具有一個「f_lineno」值,該值指出了對象方法調用的行號(在這種情況下, __init__)已被調用。函數inspect.filename允許檢索文件的源代碼,並獲取適當的行號。

一個天真的解析然後偷看部分先佔一個「=」符號,並假定它是將包含對象的變量。

from inspect import currentframe, getfile 

class A(object): 
    def __init__(self): 
     f = currentframe(1) 
     filename = getfile(f) 
     code_line = open(filename).readlines()[f.f_lineno - 1] 
     assigned_variable = code_line.split("=")[0].strip() 
     print assigned_variable 

my_name = A() 
other_name = A() 

這不會多assignents工作,表達與assignemtn前的對象撰寫而成,對象被附加到列表或添加到字典或集合,在for循環intialization對象實例化,天知道哪些更多情況 - 並且請記住,在第一個歸屬之後,該對象也可以被任何其他變量引用。

波頓線:可能的,但作爲一個玩具 - 它不能使用我廠生產的代碼 - 只是作爲對象初始化過程中的字符串傳遞的varibal名字,就像一個人做創建collections.namedtuple

「正道」什麼時候做,如果你需要的名字,是名明確地傳遞給對象的初始化,作爲字符串參數,就像:

class A(object): 
    def __init__(self, name): 
     self.name = name 

x = A("x") 

而且,如果絕對需要僅輸入一次對象的名稱,那麼是另一種方式 - 繼續閱讀。 由於Python的語法,不使用「=」運算符的一些特殊賦值確實允許對象知道它被賦予了名稱。因此,在Python中執行assignents的其他statemtns是for,with,def和class關鍵字 - 可能會濫用此類,具體來說,類創建和函數定義是創建「知道」其名稱的對象的賦值語句。

我們來關注def聲明。它通常會創建一個函數。但是,使用裝飾可以用「高清」創建任何類型的對象 - 並具有用於提供給構造函數名稱:

class MyObject(object): 
    def __new__(cls, func): 
     # Calls the superclass constructor and actually instantiates the object: 
     self = object.__new__(cls) 
     #retrieve the function name: 
     self.name = func.func_name 
     #returns an instance of this class, instead of a decorated function: 
     return self 
    def __init__(self, func): 
     print "My name is ", self.name 

#and the catch is that you can't use "=" to create this object, you have to do: 

@MyObject 
def my_name(): pass 

(這樣做的這最後一個辦法可以可以在使用生產代碼,不同於訴諸讀取源文件)

+0

非常有趣! – wim 2012-01-16 03:20:07

0

下面是一個簡單的函數來達到你想要什麼,假設你要檢索,其中實例從一個方法調用分配變量的名稱之一:

import inspect 

def get_instance_var_name(method_frame, instance): 
    parent_frame = method_frame.f_back 
    matches = {k: v for k,v in parent_frame.f_globals.items() if v is instance} 
    assert len(matches) < 2 
    return matches.keys()[0] if matches else None 

下面是一個使用示例:

class Bar: 
    def foo(self): 
     print get_instance_var_name(inspect.currentframe(), self) 

bar = Bar() 
bar.foo() # prints 'bar' 

def nested(): 
    bar.foo() 
nested() # prints 'bar' 

Bar().foo() # prints None 
0

正如許多其他人說,它不能正確地完成。不過,受jsbueno's的啓發,我可以選擇他的解決方案。

就像他的解決方案一樣,我檢查調用者堆棧框架,這意味着它只適用於Python實現的調用者(請參閱下面的註釋)。與他不同,我直接檢查調用者的字節碼(而不是加載和解析源代碼)。使用Python 3.4 +的dis.get_instructions()這可以完成一些希望最小的兼容性。雖然這仍然是一些黑客代碼。

import inspect 
import dis 

def take1(iterator): 
    try: 
     return next(iterator) 
    except StopIteration: 
     raise Exception("missing bytecode instruction") from None 

def take(iterator, count): 
    for x in range(count): 
     yield take1(iterator) 

def get_assigned_name(frame): 
    """Takes a frame and returns a description of the name(s) to which the 
    currently executing CALL_FUNCTION instruction's value will be assigned. 

    fn()     => None 
    a = fn()    => "a" 
    a, b = fn()    => ("a", "b") 
    a.a2.a3, b, c* = fn() => ("a.a2.a3", "b", Ellipsis) 
    """ 

    iterator = iter(dis.get_instructions(frame.f_code)) 
    for instr in iterator: 
     if instr.offset == frame.f_lasti: 
      break 
    else: 
     assert False, "bytecode instruction missing" 
    assert instr.opname.startswith('CALL_') 
    instr = take1(iterator) 
    if instr.opname == 'POP_TOP': 
     raise ValueError("not assigned to variable") 
    return instr_dispatch(instr, iterator) 

def instr_dispatch(instr, iterator): 
    opname = instr.opname 
    if (opname == 'STORE_FAST'    # (co_varnames) 
      or opname == 'STORE_GLOBAL'  # (co_names) 
      or opname == 'STORE_NAME'  # (co_names) 
      or opname == 'STORE_DEREF'): # (co_cellvars++co_freevars) 
     return instr.argval 
    if opname == 'UNPACK_SEQUENCE': 
     return tuple(instr_dispatch(instr, iterator) 
        for instr in take(iterator, instr.arg)) 
    if opname == 'UNPACK_EX': 
     return (*tuple(instr_dispatch(instr, iterator) 
        for instr in take(iterator, instr.arg)), 
       Ellipsis) 
    # Note: 'STORE_SUBSCR' and 'STORE_ATTR' should not be possible here. 
    # `lhs = rhs` in Python will evaluate `lhs` after `rhs`. 
    # Thus `x.attr = rhs` will first evalute `rhs` then load `a` and finally 
    # `STORE_ATTR` with `attr` as instruction argument. `a` can be any 
    # complex expression, so full support for understanding what a 
    # `STORE_ATTR` will target requires decoding the full range of expression- 
    # related bytecode instructions. Even figuring out which `STORE_ATTR` 
    # will use our return value requires non-trivial understanding of all 
    # expression-related bytecode instructions. 
    # Thus we limit ourselfs to loading a simply variable (of any kind) 
    # and a arbitary number of LOAD_ATTR calls before the final STORE_ATTR. 
    # We will represents simply a string like `my_var.loaded.loaded.assigned` 
    if opname in {'LOAD_CONST', 'LOAD_DEREF', 'LOAD_FAST', 
        'LOAD_GLOBAL', 'LOAD_NAME'}: 
     return instr.argval + "." + ".".join(
      instr_dispatch_for_load(instr, iterator)) 
    raise NotImplementedError("assignment could not be parsed: " 
           "instruction {} not understood" 
           .format(instr)) 

def instr_dispatch_for_load(instr, iterator): 
    instr = take1(iterator) 
    opname = instr.opname 
    if opname == 'LOAD_ATTR': 
     yield instr.argval 
     yield from instr_dispatch_for_load(instr, iterator) 
    elif opname == 'STORE_ATTR': 
     yield instr.argval 
    else: 
     raise NotImplementedError("assignment could not be parsed: " 
            "instruction {} not understood" 
            .format(instr)) 

注意:C實現的函數不會顯示爲Python堆棧幀,因此對此腳本隱藏。這將導致誤報。考慮Python函數f(),它調用a = g()g()是C實現的並且調用b = f2()。當f2()試圖查找分配的名稱時,它將得到a而不是b,因爲該腳本無視C函數。 (至少這是我怎麼猜到它會工作:P)

用例:

class MyItem(): 
    def __init__(self): 
     self.name = get_assigned_name(inspect.currentframe().f_back) 

abc = MyItem() 
assert abc.name == "abc" 
相關問題