2

This is my dictionary:

vimvar = {'startline'   : [ 'startline' , 'int(vim.eval("s:StartLine"))'  ],
          'startline-1' : [ 'startline' , 'int(vim.eval("s:StartLine"))-1'],
          'endline'     : [ 'endline'   , 'int(vim.eval("s:EndLine"))'    ],
          'gcase'       : [ 'gCASE'     , 'vim.eval("g:CASE")'            ],
          'akeyw'       : [ 'akeyw'     , 'vim.eval("a:keyw")'            ]
         }

This is my checklist:

importlist = ['startline', 'gcase', 'akeyw']

What I want to do is to check if a value in importlist is present as key in vimvar dictionary.

If yes than:

  1. The value of the 1st field in the sublist (associated with the key) must be a new global variable.

  2. The value of the 2nd field in the sublist (associated with the key) must be executed. It imports a variable from my texteditor vim.

I created this code to realize above:

  for n in importlist:
    for key,val in vimvar.items():
      if n in vimvar:
        exec('global ' + vimvar[key][0])
        exec(vimvar[val][0] + '=vimvar[val][1]')

But whatever I do it gives errors

undefined variable 'sa'

undefined variable 'gCASE', unhashable type list

etc

What did I wrong?

nbro
  • 15,395
  • 32
  • 113
  • 196
Reman
  • 7,931
  • 11
  • 55
  • 97
  • Do you have a lines of the errors specified in the error messages? – nbro Jan 09 '17 at 20:08
  • 2
    What you did wrong is assigning global values with `exec`. Do you have an _incredibly_ good reason to be doing that? – TigerhawkT3 Jan 09 '17 at 20:10
  • 1
    one big problem: The problem is that there's one loop too much in your loop. You check if `n` is in the dictionary, and then you use the _current_ dictionary iteration, which leads to def for n in importlist: if n in vimvar: exec('global ' + vimvar[n][0]) exec('='.join(vimvar[n])) – Jean-François Fabre Jan 09 '17 at 20:11
  • @TigerhawkT3, not at all. I've seen that on another page, telling that eval() doesn't work. BTW why these downgrades? What's wrong with this question? – Reman Jan 09 '17 at 20:13
  • It is a blatant [XY Problem](http://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). If you don't have a really good reason to be messing with `global` and `exec`, why are you doing it that way instead of with a more sensible and stable way? – TigerhawkT3 Jan 09 '17 at 20:16
  • @TigerhawkT3, I tried whatever I could do without success, so I thought I'm gonna ask it. Want to know what's wrong. – Reman Jan 09 '17 at 20:17
  • @Jean-FrançoisFabre, Thanks Jean-François. Nice... however it still gives an error `exec('global ' + vimvar[n][0]) exec('='.join(vimvar[n])` - indicating under `vimvar[n][0]` – Reman Jan 09 '17 at 20:18
  • @Reman I think you would benefit a lot from posting the problem you're trying to solve by doing things this way. `global` and `eval` are considered bad practice by a lot of people, and they tend to cause more problems than they solve. You'd probably learn a lot more (and have a more effective and stable solution) if we could deal with your original problem. – Patrick Haugh Jan 09 '17 at 20:23

4 Answers4

2

Two problems:

Firstly, you're looping twice, when you only need to loop once:

for n in importlist:
  if n in vimvar:
    name, val = vimvar[n]
    ...

Secondly, you don't need to use exec() for assignment. Either assign to a container object (recommended):

data = {}
for n in importlist:
  if n in vimvar:
    name, val = vimvar[n]
    data[name] = exec(val)

Or change the globals() dict (if you really needed it to be a global variable):

for n in importlist:
  if n in vimvar:
    name, val = vimvar[n]
    globals()[name] = exec(val)

If you can, try to avoid storing it in a global variable. And only use exec() on strings you can trust, is there any reason why this code can't be in your source? Eg

vimvar = {
  'startline': ['startline', int(vim.eval("s:StartLine"))],
  'startline-1': ['startline', int(vim.eval("s:StartLine"))-1],
  'endline': ['endline', int(vim.eval("s:EndLine"))],
  'gcase': ['gCASE', vim.eval("g:CASE")],
  'akeyw': ['akeyw', vim.eval("a:keyw")]
}

for n in importlist:
  if n in vimvar:
    name, val = vimvar[n]
    globals()[name] = val
Will Hardy
  • 14,588
  • 5
  • 44
  • 43
  • Thank you very much Will. Yes I need the globals because I need them in another (non python) script. What I don't understand in your solution (btw it doesn't seem to work in my script) is that you take as 'key' the first value in the sublist and as 'val' the 2nd value in the sublist. Globals must be the 1st value in the sublist and the 2nd value in the sublist must be executed like this...`global gCASE` and `gCASE = vim.eval("g:CASE")` – Reman Jan 09 '17 at 20:46
  • What error are you getting? Assuming the `vim.eval()` statements are ok, the code should work as you want. – Will Hardy Jan 09 '17 at 20:56
  • (I've edited for clarity, the word `key` might have been confusing). You can set global variables on the python console to see this in action: `globals()['gCASE'] = 1234` – Will Hardy Jan 09 '17 at 21:01
  • Thank you. That resolved my question. I just had to change `globals()[name] = val` to `globals()[name] = eval(val)` – Reman Jan 09 '17 at 21:13
  • Will, do you know if it is possible to return the variables without using global? (first assign them, then return variables) – Reman Jan 13 '17 at 18:33
2

There are many problems here

  • one loop too much in the dictionary, desyncing values
  • using exec and global when you only perform calls to vim.eval command.

I would drop all that exec stuff and re-do a vim evaluation engine:

vimvar = {'startline'   : [ 'startline' , "s:StartLine" , 0],
          'startline-1' : [ 'startline' , 's:StartLine' , -1 ],
          'endline'     : [ 'endline'   , "s:EndLine" , 0 ],
          'gcase'       : [ 'gCASE'     , "g:CASE" ],
          'akeyw'       : [ 'akeyw'     , "a:keyw"  ]
         }

importlist = ['startline', 'gcase', 'akeyw']

results = dict()

for n in importlist:
  if n in vimvar:  # key found in dict (no need for inner loop)
    data = vimvar[n]  # get value list
    varname = data[0]  # dict key name
    command = data[1]  # vim command to call
    # call vim command
    result = vim.eval(command)

    if len(data)>2:
        # perform optional conversion / offset, could work for string too
        to_add = data[2]
        result = type(to_add)(result) + to_add

    # create dictionary entry
    results[varname] = result

print(results)

note that the dictionary now only contains the parameter for the vim command. There's an extra parameter in the value list when a conversion/add is needed. If it's 0, result is just converted to integer, if it's -1, it's converted then 1 is substracted. Not sure it would cover all your commands, but certainly gives some ideas.

Then, the data is not stored in variables, but in a dictionary of variables: example:

{'akeyw': 'something', 'gCASE': 'other', 'startline': 10}

you can access it very easily, and it doesn't involve bad coding practice.

Jean-François Fabre
  • 137,073
  • 23
  • 153
  • 219
  • Jean-François, to avoid globals I created a dictionary like yours but how do I return the dictionary? return 'dictionary name' doesn't work: Error: 'dictionary name' is not defined. – Reman Jan 16 '17 at 16:01
  • just return `results`. This is the dictionary. – Jean-François Fabre Jan 16 '17 at 16:03
  • You mean without 'return' ? – Reman Jan 16 '17 at 16:26
  • The only way that is does work is global results and at the end return results. Then I have to unpack results to make local variables (`for k,v in results.items(): locals()[k] = v`) – Reman Jan 16 '17 at 16:30
  • but _why_ do you want to create variables, instead of using the dict? That I wonder... – Jean-François Fabre Jan 16 '17 at 16:33
  • why do you want to create variables --> my dict is always different. Never the same variables in the dict. If I use the dict I have to do a search if a variable is in it, for every variable I use. – Reman Jan 16 '17 at 16:37
1

The problem is at line

exec(vimvar[val][0] + '=vimvar[val][1]')

I think you're confused about indexing a dictionary: you tried to use val as an index, and that just doesn't make sense. For instance, one of your references expands to:

vimvar[['akeyw', 'vim.eval("a:keyw")']]

First, you can't use a list as a dictionary key -- that's what the "unhashable" problem is. Second, there is no such element in the dictionary keys.

Perhaps you just want val[0] at that point? Since you're doing some funny stuff, I'm not certain what you want to accomplish. As TigerHawk already pointed out, assigning a global this way is generally a Bad Practice.

Prune
  • 76,765
  • 14
  • 60
  • 81
  • Tnx Prune: what I want is to execute in above case `global gCASE` and `gCASE = vim.eval("g:CASE")` etc – Reman Jan 09 '17 at 20:22
1

Here is how you would do this without global or 'global' or exec.

First, if you absolutely need a global variable and you can't save a returned value (the ordinary way in which a function provides a value to its caller), use a mutable object, like a dictionary (see How do I create a variable number of variables?).

Secondly, the values you want to assign to those global variables (which is now a dictionary you can simply mutate) can be ordinary expressions instead of strings to add to an = and execute with exec. The values in the vimvar dictionary will be the desired "variable name" (now a key), the functions you want to use, and the final offset.

my_global_dict = {}

def f():
    vimvar = {'startline'   : ["s:StartLine", (vim.eval, int), 0],
          'startline-1' : ["s:StartLine", (vim.eval, int), -1],
          'endline'     : ["s:EndLine", (vim.eval, int), 0],
          'gcase'       : ["g:CASE", (vim.eval,), 0],
          'akeyw'       : ["a:keyw", (vim.eval,), 0]
         }
    importlist = ['startline', 'gcase', 'akeyw']
    for k in importlist:
        if k in vimvar:
            s,funcs,offset = vimvar[k]
            for func in funcs:
                s = func(s)
            if offset:
                s += offset
            my_global_dict[k] = s
Community
  • 1
  • 1
TigerhawkT3
  • 48,464
  • 6
  • 60
  • 97