2012-01-16 1 views
5

Pythonでは、オブジェクトのインスタンスが割り当てられている変数名を見る方法がありますか?たとえば、次のようにします。オブジェクトは、割り当てられた変数の名前を検査できますか?

class MyObject(object): 
    pass 

x = MyObject() 

MyObjectは変数名xに割り当てられていることはいつでも確認できますか?それは__init__メソッドのように?

+0

- Pythonの - アイデアにかかわらず、最近(3-4ヶ月前)があったリストこのようなものに機能を追加することに関する議論。 – jsbueno

+3

短い答えは:いいえ、試してみません..本当の答えははいですが、試してみません.. :) – wim

答えて

4

いいえ。オブジェクトと名前は別々の次元に存在します。 1つのオブジェクトは、その存続期間中に多くの名前を持つことができます。また、どちらが目的のオブジェクトであるかを判断することは不可能です。でも、ここで:

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

x = Foo() 

2つの名前が同じオブジェクト(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"ファイルから実行されなければならず、コンパイルされたバイトコードからではなく、zipモジュール内で実行されなければなりません - ファイルソースコードの読み込みに依存するため、ランニング"。

トリックは、オブジェクトが初期化された実行フレームにアクセスすることです - inspect.currentframe - フレームオブジェクトは、オブジェクトメソッド(この場合、オブジェクトメソッド)への呼び出しが行番号を示す "f_lineno" __init__)が呼び出されました。関数inspect.filenameは、ファイルのソースコードを取得し、適切な行番号を取得することができます。

"="記号を宣言する部分を単純に解析し、それがオブジェクトを含む変数であると仮定します。

複数assignents、assignemtnが行われる前にオブジェクトを構成する表現のために動作しません、オブジェクトがリストに追加や辞書やセットに追加され
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() 

forループの使う初期におけるオブジェクトのインスタンス化、および神は知っていますより多くの状況 - 最初の帰属の後、オブジェクトは他の変数によっても参照される可能性があることを覚えておいてください。

ボトンライン:それはが可能ですが、おもちゃとして - それは、生産コードI使用することはできません - 一つだけが関係していると同じように、オブジェクトの初期化時に文字列として渡されるvaribal名前を持っています

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

x = A("x") 

:それを行うにはcollections.namedtuple

「正しい方法」を作成するときに名前を必要としている場合は、明示のように、文字列パラメータとして、オブジェクトの初期化に名を渡すことですそして、オブジェクトの名前を絶対に一度入力する必要がある場合は、別の方法です - 読んでください。 Pythonの構文のために、 "="演算子を使用しない特別な割り当てでは、オブジェクトに名前が割り当てられていることを知ることができます。したがって、Pythonで代理人を実行する他のステートメントは、for、with、def、およびclassキーワードです。クラスの作成と関数定義は、名前を「知っている」オブジェクトを作成する代入ステートメントなので、これを乱用することができます。

defに注目しましょう。これは通常、関数を作成します。しかし、あなたは任意の種類のオブジェクトを作成するには、「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

0

ここでは、インスタンスが割り当てられている変数の名前を取得することを想定して、単純な関数を使用して、メソッド呼び出しから

ここでは10

は使用例である:多くの人が言ったように

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スタックフレームとして表示されず、したがってこのスクリプトには表示されません。これにより、偽陽性が生じます。 a = g()を呼び出すPython関数f()を考えてみましょう。 g()はC実装で、b = f2()を呼び出します。 f2()が割り当てられた名前をルックアップしようとすると、bの代わりにaが得られます。スクリプトにはCの機能がないからです。

使用例:(Pを少なくともこれは、私はそれがうまくいくと思う方法です):それは不可能です

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

abc = MyItem() 
assert abc.name == "abc" 
関連する問題