2012-01-16 9 views
5

In Python esiste un modo per un'istanza di un oggetto per visualizzare il nome della variabile a cui è assegnato? Prendiamo il seguente esempio:Un oggetto può ispezionare il nome della variabile a cui è stato assegnato?

class MyObject(object): 
    pass 

x = MyObject() 

E 'possibile per MyObject di vedere che è stato assegnato ad un nome di variabile x in qualsiasi momento? Come nel suo metodo __init__?

+0

Non è possibile - anche se di recente (3-4 mesi fa) sulle python-idee lista c'è stata qualche discussione sull'aggiunta di una capacità a qualcosa di simile. – jsbueno

+3

La risposta breve è: no, non provare ..e la vera risposta è sì, ma non provare .. :) – wim

risposta

4

No. Gli oggetti e i nomi vivono in dimensioni separate. Un oggetto può avere molti nomi durante la sua vita, ed è impossibile determinare quale potrebbe essere quello che vuoi. Anche qui:

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

x = Foo() 

due nomi indicano lo stesso oggetto (self quando __init__ piste, x in ambito globale).

4

Sì, è possibile *. Tuttavia, il problema è più difficile di quanto possa sembrare a prima vista:

  • Ci possono essere più nomi assegnati allo stesso oggetto.
  • Ci possono essere nessun nome affatto.

Indipendentemente da ciò, sapendo come trovare i nomi di un oggetto a volte può essere utile per il debugging - e qui è come farlo:

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 

Se siete mai tentati di logica base intorno i nomi delle variabili, fermati per un attimo e considera se la riprogettazione/refactoring del codice potrebbe risolvere il problema. La necessità di recuperare il nome di un oggetto dall'oggetto stesso di solito significa che le strutture di dati sottostanti nel programma richiedono un ripensamento.

* almeno in CPython

0

non può essere normalmente fatto, anche se questo può essere ottenuto utilizzando l'introspezione e strutture significava per il debug di un programma. Il codice deve essere eseguito da un file ".py", e non dal semplice codice bytecode compilato o all'interno di un modulo zippato, poiché si basa sulla lettura del codice sorgente del file, all'interno del metodo che dovrebbe trovare "dove si trova in esecuzione".

Il trucco è quello di accedere alla cornice di esecuzione in cui l'oggetto è stato inizializzato da - con inspect.currentframe - l'oggetto cornice ha un valore "f_lineno", che indica il numero di riga in cui la chiamata al metodo oggetto (in questo caso, __init__) è stato chiamato. La funzione inspect.filename consente di recuperare il codice sorgente per il file e recuperare il numero di linea appropriato.

Un'analisi naifa quindi dà un'occhiata alla parte che precede un segno "=" e presuppone che sia la variabile che conterrà l'oggetto.

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() 

che non funziona per più assignents, espressioni che compongono con l'oggetto prima della assignemtn è fatto, oggetti che vengono aggiunti alle liste o aggiunti a dizionari o in blocco, di istanze di oggetti in intialization di for loop, e Dio sa quali più situazioni - E tenete presente che dopo la prima attribuzione, l'oggetto potrebbe essere referenziato anche da qualsiasi altra variabile.

linea Botton: esso è possibile, ma come un giocattolo - non può essere utilizzato i codice di produzione - semplicemente il nome varibal da passare come stringa durante l'inizializzazione oggetto, così come si deve fare quando si crea un

il "modo giusto" collections.namedtuple per farlo, se si ha bisogno il nome, è quello di passare in modo esplicito il nome per l'inizializzazione dell'oggetto, come un parametro di stringa, come in:

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

x = A("x") 

E ancora, se assolutamente bisogno di digitare il nome degli oggetti solo una volta, lì è un altro modo - continua a leggere. A causa della sintassi di Python, alcune assegnazioni speciali, che non utilizzano l'operatore "=", consentono a un oggetto di sapere che è stato assegnato un nome. Quindi, altri statemi che eseguono assegnatari in Python sono le parole chiave for, with, def e class - È possibile abusarne, poiché in modo specifico una creazione di classe e una definizione di funzione sono istruzioni di assegnazione che creano oggetti che "conoscono" i loro nomi.

Concentriamoci sull'istruzione def. Generalmente crea una funzione. Ma usando un decoratore è possibile utilizzare "def" per creare qualsiasi tipo di oggetto - e hanno il nome usato per la funzione disponibile al costruttore:

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 

(Questo ultimo modo di farlo potrebbe essere utilizzato in codice di produzione, a differenza di quello che ricorre alla lettura del file sorgente)

+0

molto interessante! – wim

0

Ecco una semplice funzione per ottenere ciò che si desidera, presupponendo che si desideri recuperare il nome della variabile a cui è assegnata l'istanza da un metodo di chiamata :

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 

Ecco un esempio di utilizzo:

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

Come molti altri hanno detto, non può essere fatto in modo corretto. Comunque ispirato da jsbueno, ho un'alternativa alla sua soluzione.

Come la sua soluzione, ispeziono il frame dello stack dei chiamanti, il che significa che funziona correttamente solo per i chiamanti implementati in Python (vedere la nota sotto). A differenza di lui, ispeziono direttamente il bytecode del chiamante (invece di caricare e analizzare il codice sorgente). Utilizzando Python 3.4 + 's dis.get_instructions() questo può essere fatto con qualche speranza di compatibilità minima. Anche se questo è ancora un codice hacky.

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)) 

Nota: funzioni C-implementate non si presentano come Python stack frame e sono quindi nascosti a questo script. Ciò si tradurrà in falsi positivi. Si consideri la funzione Python f() che chiama a = g(). g() è implementato in C e chiama b = f2(). Quando f2() prova a cercare il nome assegnato, otterrà a anziché b perché lo script è ignaro delle funzioni C. (Almeno questo è come credo che funzionerà: P) Esempio

Usage:

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

abc = MyItem() 
assert abc.name == "abc" 
Problemi correlati