challenge ローカル変数の一覧を取得

リフレクション系のお題の続編です。 ローカル変数の内容を取得して連想配列(ハッシュ、辞書など)に詰める コードを書いてください。

Pythonで表現すると、下のコードの???部分を埋めることになります。

>>> def foo():
	x = 1
	y = "hello"
	???
	return result

>>> foo()
{'y': 'hello', 'x': 1}

Posted feedbacks - Nested

Flatten Hidden
これはpythonからの挑戦状かな?
1
2
3
4
5
6
def foo():
  x = 1
  y = "hello"
  result = locals()
  return result
print foo()
他の言語からの挑戦状があまりたくさん届かないので
どうしても僕の得意なPythonからの挑戦状が増えます…
RubyのuniqとかPerlのマジカルインクリメントとか
いろいろ手を出してはいるんですけどね…
> foo()
      x       y 
    "1" "hello" 
1
2
3
4
5
6
foo <- function(){
    x = 1
    y = "hello"
    result <- sapply(ls(), function(s) eval(as.symbol(s)))
    return(result)
}
1
2
3
4
5
6
def foo
  x = 1
  y = "hello"
  Hash[*local_variables.map{|v| [v,eval(v)]}.flatten]
end
foo()                           # => {"x"=>1, "y"=>"hello"}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
def foo
  x = 1
  y = "hello"
  local_variables.inject({}) {|result, name|
    result[name] = eval(name)
    result
  }
end

puts "{"+foo.map{|k,v| "'#{k}' : #{v}'"}.join(",")+"}"
これはキツイ…
Gaucheでは最適化によって、定数で初期化されて一度も
変更されないローカル変数とかは実行時には消えてしまいますし、
逆にマクロ展開によってプログラム上には無いローカル変数が
挿入される場合もあります。

やるとしたらマクロで処理系そのものを置き換えるしか
なさそうです。ここではletだけ再定義していますが、
ちゃんと動かすにはdefine, lambda, let*, letrec,
あたりの束縛系を全部再定義しないと…
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
(use util.match)

(define-macro (let binds . body)
  (match binds
    [((var init) ...)
     `((lambda (local-vars) (apply (lambda ,var ,@body) (map cdr local-vars)))
       (list ,@(map (lambda (v i) `(cons ',v ,i)) var init)))]))

;;;

(define (foo)
  (let ((x 1)
        (y "hello"))
    local-vars))
;; => ((x . 1) (y . "hello"))
Squeak Smalltalk で。
1
thisContext tempsAndValues
すみません。タグを付け間違えていました(^_^;)。 お手数をおかけし恐縮ですが可能でしたら、Squeak_Smalltalk への変更を、お願いいたします。あわせて、#82 のタグも Squeak_Smalltalk に変えて、元のタグの Squeak は(今回の s といっしょに)削除していただければ幸いです。不注意でもうしわけありません。
修正しました。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
function locals()
   local i = 1
   local t = {}
   while true do
     local k, v = debug.getlocal(2, i)
     if k == nil then break end
     t[k] = v
     i =  i + 1
   end
   return t
end

function foo()
  local x = 1
  local y = "hello"
  return locals(), nil
end

for k, v in pairs(foo()) do print(k, v) end 
--> y       hello
--> x       1
正直、C#では厳しいので、静的な情報出力でお茶を濁している。
変数の名前も内容も出力していない。
できるとすれば、以下のような感じかな。デバッガ作るようなもんですね。
・コンパイルしてアセンブリにする。
・ホスティングプロセスでアセンブリを読込んで実行する。
・ProgramDebugDatabaseを読みこんでシンボルを取得。
・LocalVariablesを取得して表示。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
using System;
using System.Reflection;
using System.Diagnostics;
class Program
{
  static void Main()
  {
    int x = 1;
    string y = "abc";
    ShowLocalVariables(new StackFrame(true));
    // System.Int32 (0)
    // System.String (1)
  }
  static void ShowLocalVariables(StackFrame sf)
  {
    foreach (LocalVariableInfo lv in sf.GetMethod().GetMethodBody().LocalVariables)
      Console.WriteLine(lv);
  }
}
それ、get_defined_vars() で
1
2
3
4
5
6
<?php
function foo(){
    $x = 1;
    $y = 'hello';
    return get_defined_vars();
}

それだとglobalも含まれている気が・・。

Perlでは局所変数を扱うためにはPadWalkerモジュールを使います。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
use PadWalker qw(peek_my);
use Data::Dumper;

sub foo {
    my $x = 1;
    my $y = 'hello';
    my $result = peek_my(0);
    return $result;
}

warn Dumper foo;
ひたすら厳しい・・・。 とりあえずJDIで無理やり。 JAVA_OPTSに -Xrunjdwp:transport=dt_socket,address=5056,server=y,suspend=n と設定して実行します。なにかいい方法があったら是非教えてほしいです。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import com.sun.jdi._
import com.sun.jdi.connect._
import com.sun.jdi.connect.Connector._
import scala.collection.mutable.JavaMapAdaptor
import scala.collection.mutable.HashMap

class _localVariables(threadName:String) extends Thread {
 var result:HashMap[String, AnyRef] = null
 override def run = {
   val vmm = Bootstrap.virtualMachineManager
   val acs = vmm.attachingConnectors().toArray.map(_.asInstanceOf[AttachingConnector])
   val ac = acs.find(_.name == "com.sun.jdi.SocketAttach").get
   val args = ac.defaultArguments
   val portNumber = args.get("port").asInstanceOf[IntegerArgument]
   portNumber.setValue(5056)
   val hostname   = args.get("hostname").asInstanceOf[StringArgument]
   hostname.setValue("localhost")

   val vm = ac.attach(args)
   val threads = vm.allThreads.toArray.map(_.asInstanceOf[ThreadReference])
   val mainthread = threads.find(_.name==threadName).get
   mainthread.suspend

   val f = mainthread.frame(4).asInstanceOf[StackFrame]
   val result = new
JavaMapAdaptor[LocalVariable,Value](f.getValues(f.visibleVariables))
   this.result = result.foldLeft(new HashMap[String, AnyRef]){(r, p) =>
     r(p._1.name) = p._2
     r
   }
   mainthread.resume
 }
}

def localVariables = {
 val th = new _localVariables(currentThread.getName)
 th.start
 th.join
 th.result
}

def foo = {
 val x = 1
 val y = "hoge"
 localVariables
}
println(foo)
コンパイルオプションで -g を指定することでクラスファイルにローカル変数名を入れることができるのですが、それを認識するAPIはありません。 クラスパスからクラスファイルを取得・解析してローカル変数名を取得することも多分可能ですが、変数名に対応する値を取得する方法がありません。 どうしようもないので内部クラスを使って似たようなことをやってみました。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import java.util.HashMap;
import java.util.Map;

import java.lang.reflect.Field;

public class Test {
	public static void main(String[] args) {
		Map map = foo();
		System.out.println(map);
	}
	static Map foo() {
		Map map = new HashMap();
		class Hoge {
			int x = 1;
			String y = "hello";
			
			Hoge() {
			}
		}
		
		Hoge hoge = new Hoge();
		Field[] fields = hoge.getClass().getDeclaredFields();
		for (int i = 0; i < fields.length; i++) {
			try {
				map.put(fields[i].getName(), fields[i].get(hoge));
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		return map;
	}
}
ふと、自分自身(orソースコード)をgrepとかしてしまってもいいんじゃないかと思ってしまった今日この頃。 宣言と同時でも良ければそれで十分できそうな予感。 でもやらない(笑
関数のソースを解析して、変数名を抽出し、evalで値を求めています。 ソース解析処理がすごく適当なので、一つのvarに複数の変数が定義されている場合に対応していないです。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
Function.prototype.listLocalVariableNames = function() {
  var regexp = /var\s+(\w+)/g;
  var source = this.toString();
  var match = null;
  var variables = [];
  while(match = regexp.exec(source)) {
    variables.push(match[1]);
  }
  return variables;
}

Array.prototype.toHash= function(iter) {
  var hash = {};
  for(var i=0; i<this.length; i++) {
    try {
      hash[this[i]] = iter(this[i]);
    } catch(e) {
      // エラーはパス
    }
  }
  return hash;
};

function func() {
  var x = 1;
  var y = "hello";

  return arguments.callee.listLocalVariableNames().toHash(function(p) {
    return eval(p);
  });
}

var variablesMap = func();
for(var prop in variablesMap) {
  alert(prop + " => " + variablesMap[prop]);
}
ネストできない><
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
(defmacro let-inspect (spec &body body)
  (let* ((vars (mapcar (lambda (x) (if (consp x) (car x) x)) spec))
         (expr (loop for v in vars collect `(list ',v (eval ,v)))))

  `(let ,spec
     (symbol-macrolet ((inspect (list ,@expr)))
     ,@body))))

(defparameter *gvar* 1)
(defun foo ()
  (let-inspect ((x 1)
                (y "hello")
                z)
    inspect))

(foo)                                   ; => ((X 1) (Y "hello") (Z NIL))
(list 'x (eval x)) だと x が二回評価されちゃいます。 ネストも扱うならこんな感じでしょうか。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
(defpackage :doukaku-lisp (:use :cl) (:shadow #:let))
(in-package :doukaku-lisp)

(define-symbol-macro local-variables nil)
(defmacro let (binding &body body)
  `(cl:let ,binding
     (cl:let ((local-variables (append
                                (list ,@(mapcar (lambda (e)
                                                  (if (consp e)
                                                     `(cons ',(car e) ,(car e))
                                                     `(cons ',e nil)))
                                                 binding))
                                 local-variables)))
       ,@body)))

(defun test ()
  (let ((x 1)
        (y "hello"))
    local-variables))
C 言語の場合ローカル変数名はデバッグ情報にしか含まれませんので gcc の -g オプションが必須です。
デバッグ情報を得るために libelf, libdwarf と、ハッシュテーブルを使うために glib を使用しました。
コンパイルこんな感じです。

% gcc -g `pkg-config glib --cflags` locals.c -o locals `pkg-config glib --libs` -lelf -ldwarf

実行結果

% ./locals
x=hello
y=1

スタックフレームにある変数を得るのに複数の種類の型が混在していると厄介だったのでローカル変数は文字列(char*)のみとなってしまいました。
あと、X86 32bit 環境でしか動作しないと思います。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <libelf.h>
#include <libdwarf/libdwarf.h>
#include <libdwarf/dwarf.h>
#include <glib.h>

GHashTable *locals();

GHashTable *foo(){
    char *x = "1";
    char *y = "hello";
    return locals();
}

GHashTable *locals(){
    GHashTable *hash;
    int fd;
    Elf *elf;
    int ret;
    Dwarf_Debug dbg;
    Dwarf_Die cu_die;
    Dwarf_Die func_die;
    Dwarf_Die var_die;
    Dwarf_Die var_die_tmp;
    Dwarf_Error err;
    Dwarf_Unsigned cu_header_length = 0;
    Dwarf_Unsigned abbrev_offset = 0;
    Dwarf_Half version_stamp = 0;
    Dwarf_Half address_size = 0;
    Dwarf_Unsigned next_cu_offset = 0;
    Dwarf_Half tag;
    Dwarf_Attribute attr;
    char *name;
    Dwarf_Addr high_pc;
    Dwarf_Addr low_pc;
    int i;
    void *frame = __builtin_frame_address(1);
    unsigned long retaddr = (unsigned long)__builtin_return_address(0);

    hash = g_hash_table_new((GHashFunc)g_str_hash, (GCompareFunc)g_str_equal);
    elf_version(EV_CURRENT);
    fd = open("/proc/self/exe", O_RDONLY);
    elf = elf_begin(fd, ELF_C_READ, (Elf*)0);
    ret = dwarf_elf_init(elf, DW_DLC_READ, NULL, NULL, &dbg, &err);
    while ((ret = dwarf_next_cu_header(
                dbg, &cu_header_length, &version_stamp, &abbrev_offset,
                &address_size, &next_cu_offset, &err)) == DW_DLV_OK){
        cu_die = NULL;
        while(dwarf_siblingof(dbg, cu_die, &cu_die, &err) != DW_DLV_NO_ENTRY){
            dwarf_tag(cu_die, &tag, &err);
            if(tag != DW_TAG_compile_unit) continue;
            ret = dwarf_child(cu_die, &func_die, &err);
            if(ret != DW_DLV_OK) continue;
            do{ 
                dwarf_tag(func_die, &tag, &err);
                if(tag != DW_TAG_subprogram) continue;
                dwarf_attr(func_die, DW_AT_high_pc, &attr, &err);
                dwarf_formaddr(attr, &high_pc, &err);
                dwarf_attr(func_die, DW_AT_low_pc, &attr, &err);
                dwarf_formaddr(attr, &low_pc, &err);
                if(low_pc > retaddr || retaddr > high_pc) continue;
                ret = dwarf_child(func_die, &var_die, &err);
                if(ret != DW_DLV_OK) continue;
                i=0;
                var_die_tmp = var_die;
                while(dwarf_siblingof(dbg, var_die, &var_die, &err) != DW_DLV_NO_ENTRY) i++;
                var_die = var_die_tmp;
                do{ 
                    dwarf_tag(var_die, &tag, &err);
                    if(tag != DW_TAG_variable) continue;
                    dwarf_attr(var_die, DW_AT_name, &attr, &err);                                                 
                    dwarf_formstring(attr, &name, &err);                                                          
                    g_hash_table_insert(hash, strdup(name),
                                        strdup((frame - 4 - (4 * i--))));
                }while(dwarf_siblingof(dbg, var_die, &var_die, &err) != DW_DLV_NO_ENTRY);

            }while(dwarf_siblingof(dbg, func_die, &func_die, &err) != DW_DLV_NO_ENTRY);
        }
    }
    dwarf_finish(dbg, &err);
    elf_end(elf);
    close(fd);
    return hash;
}

int main(int argc, char *argv[]){
    GHashTable *hash = foo();
    printf("x=%s\n", *(char**)g_hash_table_lookup(hash, "x"));
    printf("y=%s\n", *(char**)g_hash_table_lookup(hash, "y"));
    g_hash_table_destroy(hash);
    return EXIT_SUCCESS;
}
ローカル変数の取得はリフレクションというよりはデバッグ用という感じがします。
そこで、Java Debug Interface を使ってみました。

public class Sample {
    public static void main(String[] args) {
        int x = 1;
        String y = "hello";
    }
}

というサンプルの場合は、-g オプションつきで Sample をコンパイルした後に、
    java TinyDB Sample
で起動すると、mainメソッドのローカル変数(引数つき)が出力されます。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import com.sun.jdi.*;
import com.sun.jdi.connect.LaunchingConnector;
import com.sun.jdi.connect.Connector;
import com.sun.jdi.event.*;
import com.sun.jdi.request.*;
import java.util.Map;

public class TinyDB {
    public static void main(String[] args) throws Exception {
        LaunchingConnector lc = Bootstrap.virtualMachineManager().
            defaultConnector();
        Map<String, Connector.Argument> arg = lc.defaultArguments();
        arg.get("main").setValue(args[0]);

        VirtualMachine vm = lc.launch(arg);
        EventQueue q = vm.eventQueue();
        EventSet e = q.remove(); // get VMStartEvent
        EventRequestManager mgr = vm.eventRequestManager();
        MethodExitRequest exitReq = mgr.createMethodExitRequest();
        exitReq.addClassFilter(args[0]);
        exitReq.enable();
        e.resume();
        e = q.remove(); // MethodExitEvent
        LocatableEvent ev = (LocatableEvent)e.eventIterator().nextEvent();
        ThreadReference tr = ev.thread();
        StackFrame frame = tr.frame(0);
        for (LocalVariable var : frame.visibleVariables()) {
            System.out.print(var.name() + ": ");
            Value val = frame.getValue(var);
            if (val instanceof PrimitiveValue) {
                System.out.println(((PrimitiveValue)val).doubleValue());
            } else if (val instanceof StringReference) {
                System.out.println(((StringReference)val).value());
            } else if (val instanceof ObjectReference) {
                System.out.println(val.type().name());
            }
        }

        e.resume();
        e = q.remove(); // get VMDisconnectEvent
    }
}
この技は pnuts -pure で起動したときだけ使えます。
1
2
3
4
5
6
7
import pnuts.tools.StackFrameInspector
function f(){
  x=1
  y=2
  StackFrameInspector.localSymbols(getContext())
}
println(f())
Haskellでは実行時に変数を操作する手段がないので、Template Haskellでコンパイル時にコードを挿入することで対処します。力ずくです。
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
{-# OPTIONS_GHC -fth #-}

module LocalVariables(localVariables, withLocalVariables) where

import Control.Monad
import Data.Dynamic
import Language.Haskell.TH
import qualified Data.Map as Map

localVariables :: Map.Map String Dynamic
localVariables = error "localVariables outside withLocalVariables"

localVariablesName :: Name
localVariablesName = 'localVariables

withLocalVariables :: Q [Dec] -> Q [Dec]
withLocalVariables = (>>=trDecs)
  where
    trDecs decs = mapM (trDec []) decs

    trDec vars dec = case dec of
      FunD name cls -> liftM (FunD name) $ mapM (trClause vars) cls
      ValD pat body decs -> do
        decs' <- mapM (trDec vars) decs
        body' <- trBody (vars ++ concatMap decVars decs) body
        return $ ValD pat body' decs'
      _ -> return dec

    trClause vars (Clause pats body decs) = do
      decs' <- mapM (trDec vars) decs
      body' <- trBody (vars ++ concatMap patVars pats ++ concatMap decVars decs) body
      return $ Clause pats body' decs'

    trBody vars (GuardedB ges) = liftM GuardedB $ mapM trGuarded ges
      where
        trGuarded (guard, exp) = do
          exp' <- trExp vars exp
          return (guard, exp')
    trBody vars (NormalB exp) = liftM NormalB $ trExp vars exp

    trExp :: [Name] -> Exp -> Q Exp
    trExp vars exp = case exp of
      VarE name
        | name == localVariablesName -> replacement vars
        | otherwise -> return exp
      AppE x y -> liftM2 AppE (rec x) (rec y)
      InfixE x op y -> liftM3 InfixE (maybeMapM rec x) (rec op) (maybeMapM rec y)
      LamE pats e -> liftM (LamE pats) $ trExp (vars ++ concatMap patVars pats) e
      TupE es -> liftM TupE $ mapM rec es
      CondE c t f -> liftM3 CondE (rec c) (rec t) (rec f)
      LetE decs e -> do
        decs' <- mapM (trDec vars) decs
        e' <- trExp (vars ++ concatMap decVars decs) e
        return $ LetE decs' e'
      CaseE e matches -> liftM2 CaseE (rec e) (mapM (trMatch vars) matches)
      DoE ss -> liftM DoE $ trStmts vars ss
      CompE ss -> liftM CompE $ trStmts vars ss
      ArithSeqE rng -> liftM ArithSeqE $ trRange vars rng
      ListE es -> liftM ListE $ mapM rec es
      SigE e tp -> liftM2 SigE (rec e) (return tp)
      RecConE name fexps -> liftM (RecConE name) $ mapM (trFexp vars) fexps
      RecUpdE e fexps -> liftM2 RecUpdE (rec e) (mapM (trFexp vars) fexps)
      _ -> return exp
      where
        rec e = trExp vars e
        maybeMapM f Nothing = return Nothing
        maybeMapM f (Just v) = liftM Just $ f v

    trFexp vars (name, e) = do
      e' <- trExp vars e
      return (name, e')

    trRange vars rng = case rng of
      FromR x -> liftM FromR $ tr x
      FromThenR x y -> liftM2 FromThenR (tr x) (tr y)
      FromToR x y -> liftM2 FromToR (tr x) (tr y)
      FromThenToR x y z -> liftM3 FromThenToR (tr x) (tr y) (tr z)
      where
        tr e = trExp vars e

    trStmts :: [Name] -> [Stmt] -> Q [Stmt]
    trStmts vars [] = return []
    trStmts vars (stmt:rest) = case stmt of
      BindS pat exp -> let vars' = vars ++ patVars pat
        in liftM2 (:) (liftM (BindS pat) (trExp vars' exp)) (trStmts vars' rest)
      LetS decs -> liftM2 (:) (liftM LetS $ mapM (trDec vars) decs) (trStmts (vars ++ concatMap decVars decs) rest)
      NoBindS exp -> liftM2 (:) (liftM NoBindS $ trExp vars exp) (trStmts vars rest)
      ParS _ -> error "ParS: what's this?"

    trMatch vars (Match pat body decs) = do
      decs' <- mapM (trDec vars) decs
      body' <- trBody (vars ++ patVars pat ++ concatMap decVars decs) body
      return $ Match pat body' decs'

    decVars dec = case dec of
      FunD name _ -> [name]
      ValD pat _ _ -> patVars pat
      _ -> []

    patVars pat = case pat of
      VarP name -> [name]
      TupP pats -> concatMap patVars pats
      ConP name pats -> concatMap patVars pats
      InfixP p0 _ p1 -> patVars p0 ++ patVars p1
      TildeP p -> patVars p
      AsP name p -> name : patVars p
      RecP _ fps -> concatMap fpatVars fps
      ListP pats -> concatMap patVars pats
      _ -> []

    fpatVars (_, pat) = patVars pat

    replacement vars = [| Map.fromList $(list) |]
      where
        list = listE $ map entry vars
        entry name = [| ($(str), toDyn $(obj)) |]
          where
            str = return $ LitE $ StringL $ show name
            obj = return $ VarE name

{-
  使い方:
    別モジュールで

    import LocalVariables

    $(withLocalVariables [d|

      foo :: IO (Map.Map String Dynamic)
      foo = do
        let x = 1 :: Int
        let y = "hello"
        return localVariables

      |])

    のようにすると、localVariablesの出現を置換した上でfooが定義される
-}
Tcl にも info locals というずばりなコマンドがありますが Python と違って返されるのは名前のリストのみなので記述量は若干増えます。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
proc foo {} {
  set x 1
  set y hello

  set locals [info locals]
  set result {}
  foreach k $locals { lappend result $k [set $k] }
  return $result
}

# % foo
# x 1 y hello
BASHには連想配列がないのですが、表示がそのまま連想配列になってるように見えるというところでかんべんしてください。
1
2
3
4
5
foo () {
  local -i x=1
  local y="Hello"