0

How do I iterate through a list whose name will be dynamically generated?

boneList_head =['def_neck', 'def_armbase']#hard coded list
itemType='head'# result of a user button press
...
def selectBones():
    global itemType
    bones =('boneList_'+itemType)# evaluates as a string , not name of a list
    for bone in bones:
        cmds.select(bone, tgl=True)

the problem is bones is getting evaluated as a string, when I need it to evalute as the name of a list.

Mambo4
  • 182
  • 7
  • 17
  • 4
    What's the matter with passing the correct list into `selectBones` as an argument? Depending too much on global variables, especially to switch between more global variables, tends to result in brittle code. – Adam Mihalcin May 08 '12 at 00:09
  • Closely related to http://stackoverflow.com/q/230896/535275 – Scott Hunter May 08 '12 at 00:11
  • 4
    Can you just put the lists into a dictionary keyed on their name? – sje397 May 08 '12 at 00:22
  • [keep data out of your variable names!](http://nedbatchelder.com/blog/201112/keep_data_out_of_your_variable_names.html) – wim May 08 '12 at 01:14

3 Answers3

7

Dynamically generating variable names is almost always a bad approach. Use a dictionary!

bonedict = {'boneList_head': ['def_neck', 'def_armbase']}
itemType='head'

def selectBones(itemType):
    bones = bonedict['boneList_' + itemType]
    for bone in bones:
        cmds.select(bone, tgl=True)

Please ignore my previous answer (visible in my edit history) which was stupid -- boneheaded, even. But I blame its stupidity on dynamic variable name generation!

Let me elaborate on why dynamic variable name generation is a bad idea.

  1. Because dynamic variable generation masks variable name definitions. It's hard to tell what has been defined and what hasn't, so it's easy to accidentally redefine a variable. This is a major source of potential bugs.

  2. Because dynamic variable manipulation hides state changes under another layer of obfuscation. To some degree, this is true anytime you create a dictionary or a list. But one expects lists and dictionaries to demand a little extra thinking. Variable names, on the other hand, should be dead simple. When variable definitions and redefinitions require deep thought to understand, something is wrong.

  3. Because dynamic variable generation pollutes the namespace. If you have so many variables that you have to automatically generate them, then they should live in their own namespace, not in the locals of a function, and definitely not in the global namespace. In his style guide for the linux kernel, Linus Torvalds advises that if a function has more than 5-10 local variables, you're doing something wrong.

  4. Because dynamic variable generation contributes to high coupling, which is a bad thing. If you assign to values to a dictionary, you can pass that dictionary back and forth until the cows come home, and all anyone has to know about is that dictionary. If you dynamically create variable names in the global namespace of a module, then if another module wants to access those variable names, it has to know all about the way they were generated, what other variables in that module are defined, and so on. Also, passing the variables around becomes much more complex -- you have to pass around a reference to the module itself, probably using sys.modules or other questionable constructs.

  5. Because dynamic variable generation is ugly. eval looks neat and clean, but it really isn't. It can do anything. Functions that can do anything are bad, because you can't tell at first glance what they're doing here. A well-defined function does one thing, and does it well; that way, whenever you see that function, you know exactly what's happening. When you see eval, literally anything could be happening. In this sense, eval is like goto. The problem with goto is not that you can't use it correctly; it's that for every possible correct use of goto, there are 500,000,000 terrifyingly wrong ways to use it. I won't even discuss the security problems here, because in the end, that's not the real problem with eval.

Community
  • 1
  • 1
senderle
  • 145,869
  • 36
  • 209
  • 233
  • Iterating over the tuple won't give anything useful - the OP is looking to iterate over the list that is *named* `boneList_head`. I would downvote *except* for the excellent suggestion to use a dictionary. – Adam Mihalcin May 08 '12 at 00:14
  • @AdamMihalcin, ah, yes, quite so. Whoops! – senderle May 08 '12 at 00:19
  • Dictionary works, as well as eval('boneList_' + itemType). why is dictionary superior? – Mambo4 May 08 '12 at 00:53
  • This is part of a GUI tool for 3D artists, everything is button clicks , no text entry. – Mambo4 May 08 '12 at 01:08
  • @Mambo4, dictionary access is not only safer and cleaner, it's also *much* faster -- about 200 times for this use case in my tests with `timeit`. This is because it doesn't have to parse and compile Python code like `eval()` does. – Ben Hoyt May 08 '12 at 01:59
  • @Mambo4, take a look at my edit. You say "no text entry." Fine. I can come up with five reasons not to use `eval` without even getting into security concerns. – senderle May 08 '12 at 02:25
  • oddly enough I got sidetrack with some JSFL tasks that required an object used as an associative array (http://www.quirksmode.org/js/associative.html) and having done this, I can see more clearly the advantages. – Mambo4 May 21 '12 at 17:09
2

I agree with the other comments that your approach is probably not the best. But the following should work:

bones = eval('boneList_' + itemType)

This will run the python interpreter on "boneList_head", and return the list.

NOTE: As Adam Mihalcin mentioned in the comments, you should be very careful about only running eval on data that you trust or have validated. A malicious user could inject arbitrary code into the itemType variable to access the os, etc.

happydave
  • 7,127
  • 1
  • 26
  • 25
  • 3
    Note to anyone viewing this post in the future: **DO NOT** use this code if `itemType` is any kind of user-supplied string (or is otherwise generated outside your control). Python's `eval` will execute arbitrary code, allowing the person who generates `itemType` to do all kinds of nasty things to your system. – Adam Mihalcin May 08 '12 at 00:26
  • You cannot stress @AdamMihalcin 's comment enough, however, +1 for a very simple solution. – mgilson May 08 '12 at 00:46
  • @AdamMihalcin: thanks, I updated my answer to include your warning – happydave May 08 '12 at 02:03
0

This is an ugly hack, but it works...(of course, you need to get the correct module)

import sys
boneList_head =['def_neck', 'def_armbase']
itemType='head'
...
def selectBones():
    global itemType
    bones=vars(sys.modules["__main__"])['boneList_'+itemType]
    for bone in bones:
        cmds.select(bone, tgl=True)

This really isn't different than what other people are saying however-- We're using vars to construct a dictionary to get the list you want -- why not just pass a dictionary (or the correct list) to the function selectBones in the first place?

mgilson
  • 300,191
  • 65
  • 633
  • 696