Saltar al contenido

Imprimir el nombre de una variable

No busques más por todo internet porque llegaste al sitio adecuado, tenemos la respuesta que buscas sin problema.

Solución:

Java

String getParamName(String param) throws Exception 
    StackTraceElement[] strace = new Exception().getStackTrace();
    String methodName = strace[0].getMethodName();
    int lineNum = strace[1].getLineNumber();

    String className = strace[1].getClassName().replaceAll(".5$", "");
    String classPath = Class.forName(className).getProtectionDomain().getCodeSource().getLocation().getPath() + className + ".class";

    StringWriter javapOut = new StringWriter();
    com.sun.tools.javap.Main.run(new String[] "-l", "-c", classPath, new PrintWriter(javapOut));
    List javapLines = Arrays.asList(javapOut.toString().split("\r?\n"));
    int byteCodeStart = -1;
    Map byteCodePointerToJavaPLine = new HashMap();
    Pattern byteCodeIndexPattern = Pattern.compile("^\s*(\d+): ");
    for (int n = 0;n < javapLines.size();n++) 
        String javapLine = javapLines.get(n);
        if (byteCodeStart > -1 && (javapLine == null 

    int varLoadIndex = -1;
    int varTableIndex = -1;
    for (int i = byteCodePointerToJavaPLine.get(byteCodeStart) + 1;i < javapLines.size();i++) 
        if (varLoadIndex < 0 && javapLines.get(i).contains("Method " + methodName + ":")) 
            varLoadIndex = i;
            continue;
        

        if (varLoadIndex > -1 && javapLines.get(i).contains("LocalVariableTable:")) 
            varTableIndex = i;
            break;
        
    

    String loadLine = javapLines.get(varLoadIndex - 1).trim();
    int varNumber;
    try 
        varNumber = Integer.parseInt(loadLine.substring(loadLine.indexOf("aload") + 6).trim());
     catch (NumberFormatException e) 
        return null;
    
    int j = varTableIndex + 2;
    while(!"".equals(javapLines.get(j))) 
        Matcher varName = Pattern.compile("\s*" + varNumber + "\s*([a-zA-Z_][a-zA-Z0-9_]*)").matcher(javapLines.get(j));  
        if (varName.find()) 
            return varName.group(1);
        
        j++;
    
    return null;

Esto actualmente funciona con algunas trampas:

  1. Si usa un IDE para compilar esto, es posible que no funcione a menos que se ejecute como administrador (dependiendo de dónde se guarden los archivos de clase temporales)
  2. Debes compilar usando javac con el -g bandera. Esto genera toda la información de depuración, incluidos los nombres de las variables locales en el archivo de clase compilado.
  3. Esto usa una API interna de Java com.sun.tools.javap que analiza el código de bytes de un archivo de clase y produce un resultado legible por humanos. Esta API solo es accesible en las bibliotecas JDK, por lo que debe usar el tiempo de ejecución de JDK java o agregar tools.jar a su classpath.

Esto debería funcionar ahora incluso si el método se llama varias veces en el programa. Desafortunadamente, aún no funciona si tiene varias invocaciones en una sola línea. (Para uno que sí lo haga, vea a continuación)

¡Pruébelo en línea!


Explicación

StackTraceElement[] strace = new Exception().getStackTrace();
String methodName = strace[0].getMethodName();
int lineNum = strace[1].getLineNumber();

String className = strace[1].getClassName().replaceAll(".5$", "");
String classPath = Class.forName(className).getProtectionDomain().getCodeSource().getLocation().getPath() + className + ".class";

Esta primera parte obtiene información general sobre en qué clase estamos y cuál es el nombre de la función. Esto se logra creando una excepción y analizando las 2 primeras entradas del seguimiento de la pila.

java.lang.Exception
    at E.getParamName(E.java:28)
    at E.main(E.java:17)

La primera entrada es la línea en la que se lanza la excepción en la que podemos tomar el nombre del método y la segunda entrada es desde donde se llamó a la función.

StringWriter javapOut = new StringWriter();
com.sun.tools.javap.Main.run(new String[] "-l", "-c", classPath, new PrintWriter(javapOut));

En esta línea estamos ejecutando el ejecutable javap que viene con el JDK. Este programa analiza el archivo de clase (código de bytes) y presenta un resultado legible por humanos. Usaremos esto para un “análisis” rudimentario.

List javapLines = Arrays.asList(javapOut.toString().split("\r?\n"));
int byteCodeStart = -1;
Map byteCodePointerToJavaPLine = new HashMap();
Pattern byteCodeIndexPattern = Pattern.compile("^\s*(\d+): ");
for (int n = 0;n < javapLines.size();n++)  "".equals(javapLine))) 
        break;
    
    Matcher byteCodeIndexMatcher = byteCodeIndexPattern.matcher(javapLine);
    if (byteCodeIndexMatcher.find()) 
        byteCodePointerToJavaPLine.put(Integer.parseInt(byteCodeIndexMatcher.group(1)), n);
     else if (javapLine.contains("line " + lineNum + ":")) 
        byteCodeStart = Integer.parseInt(javapLine.substring(javapLine.indexOf(": ") + 2));
    

Estamos haciendo un par de cosas diferentes aquí. Primero, estamos leyendo la salida de javap línea por línea en una lista. En segundo lugar, estamos creando un mapa de índices de línea de código de bytes a índices de línea de Java. Esto nos ayuda más tarde a determinar qué método de invocación queremos analizar. Finalmente, estamos usando el número de línea conocido del seguimiento de la pila para determinar qué índice de línea de código de bytes queremos mirar.

int varLoadIndex = -1;
int varTableIndex = -1;
for (int i = byteCodePointerToJavaPLine.get(byteCodeStart) + 1;i < javapLines.size();i++) 
    if (varLoadIndex < 0 && javapLines.get(i).contains("Method " + methodName + ":")) 
        varLoadIndex = i;
        continue;
    

    if (varLoadIndex > -1 && javapLines.get(i).contains("LocalVariableTable:")) 
        varTableIndex = i;
        break;
    

Aquí estamos iterando sobre las líneas javap una vez más para encontrar el lugar donde se invoca nuestro método y donde comienza la tabla de variables locales. Necesitamos la línea donde se invoca el método porque la línea anterior contiene la llamada para cargar la variable e identifica qué variable (por índice) cargar. La tabla de variables locales nos ayuda a buscar el nombre de la variable en función del índice que obtuvimos.

String loadLine = javapLines.get(varLoadIndex - 1).trim();
int varNumber;
try 
    varNumber = Integer.parseInt(loadLine.substring(loadLine.indexOf("aload") + 6).trim());
 catch (NumberFormatException e) 
    return null;

Esta parte en realidad analiza la llamada de carga para obtener el índice de la variable. Esto puede generar una excepción si la función no se llama realmente con una variable, por lo que podemos devolver un valor nulo aquí.

int j = varTableIndex + 2;
while(!"".equals(javapLines.get(j))) 
    Matcher varName = Pattern.compile("\s*" + varNumber + "\s*([a-zA-Z_][a-zA-Z0-9_]*)").matcher(javapLines.get(j));  
    if (varName.find()) 
        return varName.group(1);
    
    j++;

return null;

Finalmente, analizamos el nombre de la variable de la línea en la tabla de variables locales. Devuelve null si no se encuentra, aunque no he visto ninguna razón por la que esto deba suceder.

Poniendolo todo junto

  public static void main (java.lang.String[]);  Código: ... 18: getstatic # 19 // Campo java / lang / System.out: Ljava / io / PrintStream;  21: aload_1 22: aload_2 23: invokevirtual # 25 // Método getParamName: (Ljava / lang / String;) Ljava / lang / String;  ... LineNumberTable: ... línea 17:18 línea 18:29 línea 19:40 ... LocalVariableTable: Longitud de inicio Nombre de ranura Firma 0 83 0 args   [Ljava/lang/String;
          8      75     1     e   LE;
         11      72     2   str   Ljava/lang/String;
         14      69     3  str2   Ljava/lang/String;
         18      65     4  str4   Ljava/lang/String;
         77       5     5    e1   Ljava/lang/Exception;

This is basically what we're looking at. In the example code the first invocation is line 17. line 17 in the LineNumberTable shows that the beginning of that line is bytecode line index 18. That is the System.out load. Then we have aload_2 right before the method call so we look for the variable in slot 2 of the LocalVariableTable which is str in this case.


For fun, here's one which handles multiple function calls on the same line. This causes the function to not be idempotent but that's kind of the point. Try it online!

Python 2

This is about the most dirty code I've written but it works. ¯_(ツ)_/¯ Throws an error on a non-existent variable as Python will immediately dislike you for calling the function with one. Also throws an error on non-variables but this can be fixed with a try/except if needed.

import inspect
import re

def name_of(var):
    for i in inspect.getframeinfo(inspect.currentframe().f_back)[3]: return re.search (r ' bnombre_de  s *  ( s * ([A-Za-z_][A-Za-z0-9_]*)  s * ) ', i) .groups ()[0]

¡Pruébelo en línea!

Si se nos permite tomar el argumento como una cadena, esto satisface los requisitos de generar un valor falso en una entrada no válida.

import inspect
import re

def name_of(var):
    # return var :P

    try:
        eval(var)
    except NameError:
        return False

    for i in inspect.getframeinfo(inspect.currentframe().f_back)[3]:
        try:
            return re.search(r'bname_ofs*(s*['"]([A-Za-z_][A-Za-z0-9_]*)['"]s*)', i).groups()[0]
        except AttributeError:
            return False

¡Pruébelo en línea!

Mathematica

f[x_] := ValueQ @ x && ToString @ HoldForm @ x
SetAttributes[f, HoldFirst]

los HoldFirst atributo previene f de evaluar su argumento antes de invocar la función. ValueQ @ x luego comprueba si el argumento dado es una variable a la que se le ha dado un valor. Si no, simplemente regresamos False debido a un cortocircuito. De lo contrario, obtenemos el nombre de la variable con ToString @ HoldForm @ x.

Recuerda algo, que tienes la opción de valorar esta noticia si te ayudó.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *