1

I'm frequently finding myself in a situation where I have a group of logically connected symbols that I want to iterate over. The obvious solution is to add these symbols to a list, but the duplication is a pain to maintain and I have to trust that if my fellow devs change one they also change the other.

Is there a way to create symbols while simultaneously adding their value to, say, a list?

For example

# A group of like symbols that can be used independently in this scope
hippo = 'hippo'
gator = 'gator'
mouse = 'mouse'

# To loop across them I have to put them into a list 
valid_animals = [hippo, gator, mouse]  # Maintain me separately, fool!

Psuedo-code for what I want

# Data structure that declares symbols whose values can be iterated over
valid_animals = {  # Even your mom could maintain this
    hippo = 'hippo'
    gator = 'gator'
    mouse = 'mouse'
}

# Use the symbols by themselves
print "I had a", mouse, "in my house"

# Iterate over the symbols
print mouse in valid_animals  # True
user2859458
  • 2,285
  • 1
  • 17
  • 27
  • 1
    Would an [Enum](https://docs.python.org/3/library/enum.html) suit your needs? – rkersh Apr 26 '16 at 20:20
  • Enums are awesome, but there are subtle but significant differences. Most notably, Enums focus on the NAMES of the symbols rather than the VALUES of the symbols. This is especially noticeable when iterating. For example, if I change `valid_animals` to an Enum, the `print mouse in valid_animals` returns False. – user2859458 Apr 28 '16 at 16:41
  • It may not be what you want, but you could do `print mouse in [i.value for i in valid_animals]`, right? And, you could add a `values` class method that makes that a bit prettier (e.g., `print mouse in valid_animals.values()`). – rkersh Apr 28 '16 at 17:14

4 Answers4

2

This sounds like what object-oriented programming is for:

class Animal(object):
    list = []
    def __init__(self,name):
        self.name = name
        Animal.list.append(self.name)


mouse = Animal("mouse")
cat = Animal("cat")

print(mouse)      # <test.Animal object at 0x7f835e146860>
print(mouse.name)   # 'mouse'
print(cat.name)    # 'cat'
print(Animal.list)   # ['mouse', 'cat']

Typically, in Python, classes have an init method. This can seem mysterious, but all it really is is some code that is called when an object is instantiated based on the class. (Think of the class as a template for creating objects, and the init method runs when the object is created.)

Inside the class, make an empty list. This is a class-level list and can be accessed in your code with Animal.list. It's not connected with any particular instantiated object (i.e., cat or mouse).

When the init method is called, the name of the newly-created object is added to the class-level list. So if you create ten animals (Animal('ocelot'), Animal('kangaroo'), etc), you can call Animal.list to see the names of all the animals.

EDIT: You requested a more general solution to your problem:

class Symbol(object):
    types = []
    def __init__(self,name):
        self.name = name
        Symbol.types.append(self.name)
        self.item_list = []


    def add(self,item):
        self.item_list.append(item)


animal = Symbol('animal')

print(animal.item_list)    # []

animal.add('tiger')
animal.add('llama')

print(animal.item_list)    # ['tiger', 'llama']

food = Symbol('food')

food.add('carrot')
food.add('beans')

print(food.item_list)   # ['carrot', 'beans']

print(Symbol.types)   # ['animal', 'food']
T. Arboreus
  • 1,067
  • 1
  • 9
  • 17
  • Using a class value to store the list is an excellent idea and this solves the basic use case in my example, but I'm looking for something more generic that can be used without having to create a new class every time I want a new group. For example, if I had a group of food symbols in addition to the animals group, I'd need a class to store the food symbols lest I mung my animals up. – user2859458 Apr 26 '16 at 20:34
  • The trouble with the new code is the added animals are now no longer symbols in the local scope. :( For example, I cannot call animal.tiger to get "tiger". However, this is great food for thought :) – user2859458 Apr 26 '16 at 21:49
  • If you want to use the dot syntax in that way, you could add the items to a dictionary instead of a list. Also, you can add the action objects to the lists instad of the object names, which is actually more powerful. But you seemed concerned with the symbol names in your initial post. Interesting problem, anyway...thank you – T. Arboreus Apr 26 '16 at 22:26
0

A idea it to just maintain the the list : valid=['foo','bar','baz'].

If at a moment you want for convenience to define variable with the same name,locals().update(zip(valid,valid)) will do it.

But it's surely a bad idea in serious projects.

B. M.
  • 18,243
  • 2
  • 35
  • 54
  • `locals` might not work: http://stackoverflow.com/a/8028772/4996248 . It doesn't really matter though since you are surely correct that this sort of thing isn't a good idea in any serious project. – John Coleman Apr 26 '16 at 20:43
  • I just use that in interactive mode when I am tired to type (and forget some) multiples ' in intensive trash string manipulations.... – B. M. Apr 26 '16 at 20:49
0

I'm not sure if this is a good idea for any large project, but you could do the following:

def varsFromStrings(*strings):
    newVars = []
    for s in strings:
        globals()[s] = s
        newVars.append(s)
    return newVars

The function will take a list of strings, create variables for each string, enter these variables into the globals dictionary, and assign the string to each variables, returning the list of strings as a result. Then even your Mom could type:

valid_animals = varsFromStrings('hippo','gator','mouse')

after which,

print("I had a", mouse, "in my house")

for critter in valid_animals: print(critter)

both work as expected.

This might be helpful in some smallish programs, but if program maintenance is a concern -- why not just use a regular dictionary of valid animals?

John Coleman
  • 51,337
  • 7
  • 54
  • 119
  • So far as I know, dictionaries are the closest thing to what I'm dreaming about here, but in the end, dictionary keys and symbols are very different beasts and don't quite play the same. Since keys must be primitives, they usually end up being magic strings and numbers, which I'd rather avoid if possible. Also, linting and other code intelligence tools are significantly more helpful with symbols. – user2859458 Apr 26 '16 at 21:34
0

@T. Arboreus was right; the solution was object oriented programing. In order to declare symbols in the local scope while simultaneously adding them to an iterable, all I needed was a return value on an append action which could then be assigned to my symbol.

I did this by inheriting from list and adding a bind method.

# Iterable object that has a return on append
class SymGroup(list):
    def add(self, value):
        self.append(value)
        return value



# Create my list that represents a group of symbol values
valid_animals = SymGroup()

# Assign and append values at the same time
hippo = valid_animals.bind("hippo")
gator = valid_animals.bind("gator")
mouse = valid_animals.bind("mouse")



# Symbol can be used by itself
print "There is a", mouse, "in my house"

# Can iterate across the symbol values
print mouse in valid_animals  # True
print "horse" in valid_animals  # False

Now I only have to maintain the one declaration.

user2859458
  • 2,285
  • 1
  • 17
  • 27