ejemplo - metodo__str__ python
Obtener un nombre de instancia dentro de la clase__init__() (10)
Esta pregunta ya tiene una respuesta aquí:
- Obteniendo el nombre de una variable como una cadena 15 respuestas
Mientras construyo un nuevo objeto de clase en python, quiero poder crear un valor predeterminado basado en el nombre de instancia de la clase sin pasar un argumento adicional. ¿Cómo puedo lograr esto? Aquí está el pseudo-código básico que estoy intentando:
class SomeObject():
defined_name = u""
def __init__(self, def_name=None):
if def_name == None:
def_name = u"%s" % (<INSTANCE NAME>)
self.defined_name = def_name
ThisObject = SomeObject()
print ThisObject.defined_name # Should print "ThisObject"
Bueno, hay casi una manera de hacerlo:
#!/usr/bin/env python
import traceback
class SomeObject():
def __init__(self, def_name=None):
if def_name == None:
(filename,line_number,function_name,text)=traceback.extract_stack()[-2]
def_name = text[:text.find(''='')].strip()
self.defined_name = def_name
ThisObject = SomeObject()
print ThisObject.defined_name
# ThisObject
El módulo de rastreo le permite mirar el código utilizado para llamar a SomeObject (). Con una pequeña secuencia de cadenas, text[:text.find(''='')].strip()
puedes adivinar cuál debería ser el def_name.
Sin embargo, este truco es frágil. Por ejemplo, esto no funciona tan bien:
ThisObject,ThatObject = SomeObject(),SomeObject()
print ThisObject.defined_name
# ThisObject,ThatObject
print ThatObject.defined_name
# ThisObject,ThatObject
Por lo tanto, si tuviera que usar este truco, debe tener en cuenta que debe llamar a SomeObject () usando una simple declaración de Python:
ThisObject = SomeObject()
Por cierto, como otro ejemplo de uso de rastreo, si define
def pv(var):
# stack is a list of 4-tuples: (filename, line number, function name, text)
# see http://docs.python.org/library/traceback.html#module-traceback
#
(filename,line_number,function_name,text)=traceback.extract_stack()[-2]
# (''x_traceback.py'', 18, ''f'', ''print_var(y)'')
print(''%s: %s''%(text[text.find(''('')+1:-1],var))
entonces puedes llamar
x=3.14
pv(x)
# x: 3.14
para imprimir tanto el nombre de la variable como su valor.
Creo que los nombres son importantes si son los indicadores de cualquier objeto. No importa si:
foo = 1
bar = foo
Sé que foo señala a 1 y la barra apunta al mismo valor 1 en el mismo espacio de memoria. pero supongamos que quiero crear una clase con una función que le agregue un objeto.
Class Bag(object):
def __init__(self):
some code here...
def addItem(self,item):
self.__dict__[somewaytogetItemName] = item
Entonces, cuando hago una instancia de la clase de bolsa como a continuación:
newObj1 = Bag()
newObj2 = Bag()
newObj1.addItem(newObj2)I can do this to get an attribute of newObj1:
newObj1.newObj2
En Python, todos los datos se almacenan en objetos. Además, un nombre se puede vincular con un objeto, después de lo cual ese nombre se puede utilizar para buscar ese objeto.
No importa al objeto a qué nombres, si los hay, podría estar vinculado. Puede estar vinculado a docenas de nombres diferentes, o ninguno. Además, Python no tiene ningún "vínculo de retroceso" que apunte desde un objeto a un nombre.
Considera este ejemplo:
foo = 1
bar = foo
baz = foo
Ahora, suponga que tiene el objeto entero con el valor 1, y desea trabajar hacia atrás y encontrar su nombre. ¿Qué imprimirías? Tres nombres diferentes tienen ese objeto vinculado a ellos, y todos son igualmente válidos.
print(bar is foo) # prints True
print(baz is foo) # prints True
En Python, un nombre es una forma de acceder a un objeto, por lo que no hay forma de trabajar con nombres directamente. Puede buscar en varios espacios de nombres hasta que encuentre un nombre que esté vinculado con el objeto de interés, pero no lo recomiendo.
¿Cómo obtengo la representación de cadena de una variable en python?
Hay una famosa presentación llamada "Code Like a Pythonista" que resume esta situación como "Otros idiomas tienen ''variables''" y "Python tiene ''nombres''"
http://python.net/~goodger/projects/pycon/2007/idiomatic/handout.html#other-languages-have-variables
Esto no puede funcionar, solo imagine esto: a = b = TheMagicObjet()
. Los nombres no tienen efecto en los valores, solo apuntan a ellos.
Inspirado por las respuestas de unutbu y Saullo Castro, he creado una clase más sofisticada que incluso puede ser subclasificada. Resuelve lo que se pidió en la pregunta.
"cree un valor predeterminado basado en el nombre de instancia de la clase sin pasar un argumento extra".
Esto es lo que hace, cuando se crea una instancia de esta clase o una subclase:
- Suba en la pila de cuadros hasta el primer cuadro que no pertenece a un método de la instancia actual.
- Inspeccione este marco para obtener los atributos
self.creation_(name/file/module/function/line/text)
. - Realice una verificación adicional de si un objeto con nombre
self.creation_name
se definió realmente en el espacio de nombres locals () del marco para asegurarse al 100% de que el nombre de creación encontrado sea correcto o genere un error de lo contrario.
El código:
import traceback, threading, time
class InstanceCreationError(Exception):
pass
class RememberInstanceCreationInfo:
def __init__(self):
for frame, line in traceback.walk_stack(None):
varnames = frame.f_code.co_varnames
if varnames is ():
break
if frame.f_locals[varnames[0]] not in (self, self.__class__):
break
# if the frame is inside a method of this instance,
# the first argument usually contains either the instance or
# its class
# we want to find the first frame, where this is not the case
else:
raise InstanceCreationError("No suitable outer frame found.")
self._outer_frame = frame
self.creation_module = frame.f_globals["__name__"]
self.creation_file, self.creation_line, self.creation_function, /
self.creation_text = /
traceback.extract_stack(frame, 1)[0]
self.creation_name = self.creation_text.split("=")[0].strip()
super().__init__()
threading.Thread(target=self._check_existence_after_creation).start()
def _check_existence_after_creation(self):
while self._outer_frame.f_lineno == self.creation_line:
time.sleep(0.01)
# this is executed as soon as the line number changes
# now we can be sure the instance was actually created
error = InstanceCreationError(
"/nCreation name not found in creation frame./ncreation_file: "
"%s /ncreation_line: %s /ncreation_text: %s/ncreation_name ("
"might be wrong): %s" % (
self.creation_file, self.creation_line, self.creation_text,
self.creation_name))
nameparts = self.creation_name.split(".")
try:
var = self._outer_frame.f_locals[nameparts[0]]
except KeyError:
raise error
finally:
del self._outer_frame
# make sure we have no permament inter frame reference
# which could hinder garbage collection
try:
for name in nameparts[1:]: var = getattr(var, name)
except AttributeError:
raise error
if var is not self: raise error
def __repr__(self):
return super().__repr__()[
:-1] + " with creation_name ''%s''>" % self.creation_name
Un ejemplo simple:
class MySubclass(RememberInstanceCreationInfo):
def __init__(self):
super().__init__()
def print_creation_info(self):
print(self.creation_name, self.creation_module, self.creation_function,
self.creation_line, self.creation_text, sep=", ")
instance = MySubclass()
#out: instance, __main__, <module>, 68, instance = MySubclass()
Si el nombre de la creación no se puede determinar correctamente, se genera un error:
variable, another_instance = 2, MySubclass()
# InstanceCreationError:
# Creation name not found in creation frame.
# creation_file: /.../myfile.py
# creation_line: 71
# creation_text: variable, another_instance = 2, MySubclass()
# creation_name (might be wrong): variable, another_instance
La mejor manera es realmente pasar el nombre al constructor como en la respuesta elegida. Sin embargo, si REALMENTE desea evitar pedirle al usuario que le pase el nombre al constructor, puede hacer lo siguiente:
Si está creando la instancia con ''ThisObject = SomeObject ()'' desde la línea de comandos, puede obtener el nombre del objeto de la cadena de comandos en el historial de comandos:
import readline
import re
class SomeObject():
def __init__(self):
cmd = readline.get_history_item(readline.get_current_history_length())
self.name = re.split(''=| '',cmd)[0]
Si está creando la instancia con el comando ''exec'', puede manejar esto con:
if cmd[0:4] == ''exec'': self.name = re.split(''/'|=| '',cmd)[1] # if command performed using ''exec''
else: self.name = re.split(''=| '',cmd)[0]
Las instancias no tienen nombre. En el momento en que el nombre global ThisObject
se enlaza a la instancia creada al evaluar el constructor SomeObject
, el constructor ha terminado de ejecutarse.
Si desea que un objeto tenga un nombre, simplemente pase el nombre en el constructor.
def __init__(self, name):
self.name = name
Puede crear un método dentro de su clase que verifique todas las variables en el marco actual y use hash()
para buscar la variable self
.
La solución propuesta aquí devolverá todas las variables que apuntan al objeto de instancia.
En la clase a continuación, isinstance()
se usa para evitar problemas al aplicar hash()
, ya que algunos objetos como un numpy.array
o una list
, por ejemplo, no se pueden lavar.
import inspect
class A(object):
def get_my_name(self):
ans = []
frame = inspect.currentframe().f_back
tmp = dict(frame.f_globals.items() + frame.f_locals.items())
for k, var in tmp.items():
if isinstance(var, self.__class__):
if hash(self) == hash(var):
ans.append(k)
return ans
Se ha realizado la siguiente prueba:
def test():
a = A()
b = a
c = b
print c.get_my_name()
El resultado es:
test()
#[''a'', ''c'', ''b'']
Si desea un nombre de instancia único para una clase, intente __repr__()
o id(self)
class Some:
def __init__(self):
print(self.__repr__()) # = hex(id(self))
print(id(self))
Imprimirá la dirección de memoria de la instancia, que es única.
Una manera horrible, horrible de lograr esto es revertir las responsabilidades:
class SomeObject():
def __init__(self, def_name):
self.defined_name = def_name
globals()[def_name] = self
SomeObject("ThisObject")
print ThisObject.defined_name
Si quisieras apoyar algo que no fuera el alcance global, tendrías que hacer algo aún más horrible.