3

My initial goal was to create a function which will print the type and memory address of the given object. To be as most universal as possible I wanted also include the variable name, something like this:

>>> a=10
>>> print type_addr(a)
a: int, 0x13b8080

For this purpose I need to know the name of an variable passed to this function. This page suggest following code (It a little bit modified version but the idea remain the same, iterate over locals().iteritems(). I know it is not safe and it has several pitfalls mentioned on given link but my plans was to improve it):

#!/usr/bin/python

a = b = c = 10
b = 11

for k, v in list(locals().iteritems()):
  if v is b:
  # if id(v) == id(b):
    print "k: %s" % k
    print "v: %s" % v
    print "a: %s" % hex(id(k))

Output of above code is:

k: b
v: 11
a: 0x7fece0f305f8

My next goal was to make subroutine which will give me desired result, so I've tried to wrap it into subroutine:

#!/usr/bin/python

a = b = c = 10
b = 11

def addr_type(obj):
  for k, v in list(locals().iteritems()):
    # if id(v) == id(a):
    if v is obj:
      print "k: %s" % k
      print "v: %s" % v
      print "a: %s" % hex(id(k))

for k, v in list(locals().iteritems()):
  if v is b:
  # if id(v) == id(b):
    print "k: %s" % k
    print "v: %s" % v
    print "a: %s" % hex(id(k))

print "#################"

addr_type(b)

The output of above code is:

k: b
v: 11
a: 0x7fc9253715f8
#################
k: obj
v: 11
a: 0x7fc9253198a0

As you can see, neither the name of variable nor the address are the same. Then I started to dig deeper and tried following:

#!/usr/bin/python
a = b = c = 10
b = 11

for k, v in list(locals().iteritems()):
   print "k: %s" % k
   print "v: %s" % v
   print "a: %s" % hex(id(k))
   print "##############"


if a is b:
    print "a and b is the same objects"
else:
    print "a and b is NOT the same objects"


if a is c:
    print "a and c is the same objects"
else:
    print "a and c is NOT the same objects"


if b is c:
    print "b and c is the same objects"
else:
    print "b and c is NOT the same objects"

Which returned:

k: a
v: 10
a: 0x7ff07d54b5d0
##############
k: c
v: 10
a: 0x7ff07d54bbe8
##############
k: b
v: 11
a: 0x7ff07d54b5f8
##############
<Some loaded modules here but nothing interesting>
##############
a and b is NOT the same objects
a and c is the same objects
b and c is NOT the same objects

Questions:

  1. How to rewrite the working code and make the function that will print the name of passed variable?
  2. Why the same objects have different addresses?
Community
  • 1
  • 1
Wakan Tanka
  • 7,542
  • 16
  • 69
  • 122

2 Answers2

2

TL; DR: Because of how name binding works in Python you generally can't get the name of a variable that you pass to a function. Some Python objects do have a name attribute, but that's not the same thing as the name that you bind to an object in an assignment statement.

You're code is giving you misleading info when you print ids. You're not actually printing the ids of the objects, you're printing the ids of the name strings in the locals() dict.

Also, when you get locals() inside the function it shows you the stuff that's local to the function. So to get info about the local objects that are outside the function you need to give the function access to the actual locals() dict that you're interested in.

Below is a modified version of your code that fixes the id stuff and also passes a copy of the locals() data to the addr_type function.

Note that when you call the function for a or c it prints the info for both names.

I've also added on a few lines that show how you can print the name of a function by using its __name__ attribute. Note that when we print new_name.__name__ it just prints addr_type, since that's the value of the function's __name__ attribute, the fact that we've also bound it to new_name is irrelevant.

#!/usr/bin/env python

def addr_type(context, obj):
    for k, v in context:
        # if id(v) == id(a):
        if v is obj:
            print "k: %s" % k
            print "v: %s" % v
            print "a: %s" % hex(id(v))
            print
    print 15 * "-"


a = b = c = 10
b = 11

for k, v in list(locals().iteritems()):
    if k.startswith('__'):
        continue
    print "k: %s" % k
    print "v: %s" % v
    print "a: %s" % hex(id(v))
    print "##############"

print

if a is b:
    print "a and b is the same objects"
else:
    print "a and b is NOT the same objects"


if a is c:
    print "a and c is the same objects"
else:
    print "a and c is NOT the same objects"


if b is c:
    print "b and c is the same objects"
else:
    print "b and c is NOT the same objects"

print

context = list(locals().iteritems())
addr_type(context, a)
addr_type(context, b)
addr_type(context, c)

new_name = addr_type
print 'Function names =', addr_type.__name__, new_name.__name__

output

k: a
v: 10
a: 0x8dafd24
##############
k: c
v: 10
a: 0x8dafd24
##############
k: b
v: 11
a: 0x8dafd18
##############
k: addr_type
v: <function addr_type at 0xb748e17c>
a: 0xb748e17cL
##############

a and b is NOT the same objects
a and c is the same objects
b and c is NOT the same objects

k: a
v: 10
a: 0x8dafd24

k: c
v: 10
a: 0x8dafd24

---------------
k: b
v: 11
a: 0x8dafd18

---------------
k: a
v: 10
a: 0x8dafd24

k: c
v: 10
a: 0x8dafd24

---------------
Function names = addr_type addr_type

You may find it helpful to study the excellent article Facts and myths about Python names and values, written by Stack Overflow veteran Ned Batchelder.


Update

Here's a simple function, show, which prints the names that you pass it, along with the value of that name in the dictionary you also pass it. By passing it the locals() dictionary this allows it to find objects by name in the local scope. Inside a function, that's the function's local variables. Outside a function, that's all the global variables.

def show(*args, names):
    for s in args:
        print(s, names.get(s, 'Not found!'))

def test(t):
    a = 1
    b = 2
    c = 3
    show('a', 'b', 'c', 't', names=locals())

test(42)

output

a 1
b 2
c 3
t 42
Community
  • 1
  • 1
PM 2Ring
  • 54,345
  • 6
  • 82
  • 182
1

I'm starting with the second part of the question. Here the problem is, that you're printing hex(id(k)). You should print hex(id(v)) instead.

It's not a good idea to search the name in the locals()

Update and return a dictionary representing the current local symbol table.

If you call locals() inside of a function, you'll only get the variables defined in this scope. That's why it prints

k: obj
v: 11
a: 0x7fc9253198a0

You would have to pass the locals() dictionary to type_addr() to have some information of the scope the variable came from.

a = 7
type_addr(a, locals())

On the other hand you'll most likely get wrong names (if you don't print all of them) if you have more than one variable with the same value.

So following The Zen of Python

Explicit is better than implicit.

I would propose to pass the name to print explicitly.

def type_addr(obj, msg):
    print '%s: %s %s' % (msg, type(obj).__name__, hex(id(obj)))

a = 11
type_addr(a, 'a')
tynn
  • 38,113
  • 8
  • 108
  • 143
  • Thank you. I've up voted this but marked @PM 2Ring post as an answer because it provides more details. Thank you again. – Wakan Tanka Jul 12 '15 at 01:27