1

How can I extract only the names of global variables referenced in a function or its code object?

I was using the co_names attribute of code object base on this post, but then noticed this picks up any names other than the names of local or free variables. For example, if foo is defined as,

def foo():
    return x.y

Then foo.__code__.co_names yields ('x', 'y') where I only want x.

fumito
  • 324
  • 2
  • 13
  • For reference, *"`co_names` is a tuple containing the names used by the bytecode"*, nothing to do with scope. (per the top answer on the linked question) – wjandrea Mar 11 '20 at 15:44
  • 1
    I'm not following the example. Without context and without a `global` declaration, you can't tell that `x` is a global, just that it's free in the scope of `foo`. Is that what you're actually looking for? - a list of free variables? – wjandrea Mar 11 '20 at 15:48
  • Somebody just suggested to look for the ```LOAD_GLOBAL``` instructions using ```dis``` and I thought it's a good idea but the comment was deleted. What's wrong? – fumito Mar 11 '20 at 16:06

1 Answers1

1

I figured out how to extract global names, although the function below doesn't capture globals in nested functions.

def global_names(func):

    insts = list(dis.get_instructions(func.__code__))

    names = []
    for inst in insts:
        if inst.opname == "LOAD_GLOBAL" and inst.argval not in names:
            names.append(inst.argval)

    return tuple(names)
fumito
  • 324
  • 2
  • 13
  • This is somewhat fragile to implementation details. Especially now, with faster-cpython work getting rolling. – creanion Jun 04 '22 at 13:18
  • @creanion can you clarify how the faster cpython work makes this fragile? The byte code changes do not seem to be relevant. https://docs.python.org/3.11/whatsnew/3.11.html#cpython-bytecode-changes – fumito Jun 04 '22 at 17:12
  • Lots of instructions have changed according to that list. Yes, not `LOAD_GLOBAL` - but what guarantees that it doesn't change in coming Python versions? The faster-cpython work is (hopefully) not done yet. The long list of instruction changes is evidence to me, that parsing the disassembly is fragile to changes between Python versions. – creanion Jun 04 '22 at 17:15
  • Looking closer, `LOAD_GLOBAL` did change in Python 3.11 - in which arguments it has - so you should double-check the argval part of the code. – creanion Jun 04 '22 at 17:22
  • @creanion Where is the change documented? – fumito Jun 04 '22 at 17:28
  • https://docs.python.org/3.11/library/dis.html#opcode-LOAD_GLOBAL – creanion Jun 04 '22 at 17:28
  • Just installed 3.11.0b3 and it worked as previous versons. `argval` works as before. As answered in the link below, most of the efforts around specializations are internal and not visible as bytecode instruction changes. https://mail.python.org/archives/list/python-dev@python.org/message/JLD3PDRU6YXPIGXVUDE3JP4EEBI2PWJ7/ – fumito Jun 05 '22 at 15:54
  • Great. The dis module has this section to further underline my point. *CPython implementation detail: Bytecode is an implementation detail of the CPython interpreter. No guarantees are made that bytecode will not be added, removed, or changed between versions of Python. Use of this module should not be considered to work across Python VMs or Python releases.* And that's what I mean by fragile. – creanion Jun 05 '22 at 17:52