2

Source

def flags(*opts):
    keys = [t[0] for t in opts]
    words = [t[1] for t in opts]
    nums = [2**i for i in range(len(opts))]
    attrs = dict(zip(keys,nums))
    choices = iter(zip(nums,words))
    return type('Flags', (), dict(attrs))

Abilities = flags(
    ('FLY', 'Can fly'),
    ('FIREBALL', 'Can shoot fireballs'),
    ('INVISIBLE', 'Can turn invisible'),
)

Question

How can I add an __iter__ method to Abilities so that I can iterate over choices?

Why?

This way I can use things like

hero.abilities = Abilities.FLY | Abilities.FIREBALL

if hero.abilities & Abilities.FIREBALL:

for k, v in Abilities:
    print k, v

in my code without having to use any magic numbers or strings, and I can also save the set of flags to the DB as a single int, or display the list in a readable format.

Other improvements are welcome.

mpen
  • 272,448
  • 266
  • 850
  • 1,236
  • Note that you're not avoiding magic numbers, but rather creating them: the values of the constants are created magically instead of being clearly specified. This means that you probably don't want to save these values to a database, since they'll be invalidated any time they're renumbered due to edits, whereas explicitly-specified bitmasks won't change unexpectedly. – Glenn Maynard Jul 10 '10 at 04:21
  • Are they teaching kids that 'metaprogramming is a silver bullet' these days? This is an honest question as it reminds me of an era when new grads didn't feel they were doing their job if they couldn't force some multiple inheritance into their design. – msw Jul 10 '10 at 04:43
  • @Glenn, what you call the "magical" assignment of numbers is, of course, no worse than a good old C `enum` (which "magically" assigns 0, 1, 2, etc, without they being "clearly specified") -- which has been in frequent use for nearly 40 years and not caused (yet) the rivers to turn into blood (last I checked, at least). – Alex Martelli Jul 10 '10 at 04:49
  • @msw, if by "metaprogramming" you mean the tame assignment of successive powers of two to given names, then, "these days" must clearly go back to the '70s, since C's `enum` similarly assign successive integers, as I remarked to Glenn. "It's silly to write down `1, 2, 3, 4,...` or `1, 2, 4, 8, ...` yourself when the computer can do it better" ((the latter typically via left shits of `1` in C;-)) has indeed been taught for at least the last 40 years or so (multiple inheritance, OTOH, is hardly taught any longer, since too many colleges have moved to Java;-). – Alex Martelli Jul 10 '10 at 04:53
  • @alex: no, I was referring to consing up a type via non-obvious `return type(...)` instead of using the less magical means in my (admittedly incomplete) pseudo-code below. As you know I'm a Python tyro; it is well possible that I'm misusing the term or concepts, but if I am not, I'm all for reading less magic. (I think we may be referring to two different aspects of the question, if so, sorry). – msw Jul 10 '10 at 05:08
  • @Alex: Persistently saving the value of C enums is just as hazardous to the unwary. If people aren't made aware of the common hazards that C programmers have long been aware of, then the chances of them drowning in a bloody river are much higher. – Glenn Maynard Jul 10 '10 at 05:37
  • @msw: Didn't realize I could do this without using `type`. I was basing my sol'n off this: http://stackoverflow.com/questions/36932#1695250 – mpen Jul 10 '10 at 05:47
  • What the heck? Someone downvoted all your solutions! (I upvoted them, so don't look at me) – mpen Jul 10 '10 at 05:48

5 Answers5

4

There's no need to use a dynamic type here; I'd restructure this as a simple class, for example:

class flags(object):
    def __init__(self, *opts):
        keys = [t[0] for t in opts]
        words = [t[1] for t in opts]
        nums = [2**i for i in range(len(opts))]
        self.attrs = dict(zip(keys,nums))
        self.choices = zip(nums,words)

    def __getattr__(self, a):
        return self.attrs[a]

    def __iter__(self):
        return iter(self.choices)

Abilities = flags(
    ('FLY', 'Can fly'),
    ('FIREBALL', 'Can shoot fireballs'),
    ('INVISIBLE', 'Can turn invisible'),
)

print Abilities.FLY
for k, v in Abilities:
    print k, v
Glenn Maynard
  • 55,829
  • 10
  • 121
  • 131
  • 1
    As an aside, if you really want to define member functions for a dynamic type, the cleanest way is to declare them in a base class, and specify it in the `bases` tuple to type(). – Glenn Maynard Jul 10 '10 at 04:38
1

Why are you doing it the hard way? If you want a dict with __getattr__ overriding why not start with one:

class Flags(dict):
    def __init__(self, *args):
        dict.__init__(self, args)
    def __getattr__(self, name):
         return self[name]
...

This also has the Advantage of Least Surprise, since dict.__iter__() generates keys and dict.iteritems() yields tuples.

msw
  • 42,753
  • 9
  • 87
  • 112
0

It would be a more Pythonic solution if you implement your own __getattr__ method for accessing dynamic fields instead of dealing with metaclasses through type.

Edit: It's not clear to me what do you mean by choices, but here is an example:

class Abilities(object):
    def __init__(self, abilities):
        self.abilities = abilities

    def __getattr__(self, name):
        a = [x for x in self.abilities if x[0] == name]
        if len(a) != 1:
            raise AttributeError('attribute {0} not found'.format(name))
        title, id, help = a[0]
        return id

    def __iter__(self):
        return (id, help for title, id, help in self.abilities)

SPEC = [
    ('FLY', 10, 'Can fly'),
    ('FIREBALL', 13, 'Can shoot fireballs'),
    ('INVISIBLE', 14, 'Can turn invisible'),
]

abitilies = Abilities(SPEC)

hero.abilities = abilities.FLY | abilities.FIREBALL
for k, v in abilities:
    print k, v
Andrey Vlasovskikh
  • 16,489
  • 7
  • 44
  • 62
  • Can you do that and keep the same declarative syntax for Abilities? – mpen Jul 10 '10 at 04:07
  • @Andrey: I fail to see how that acomplishes the request of being able to iterate over the given attributes. – jsbueno Jul 10 '10 at 04:28
  • I think you've slightly misinterpreted my intentions. It's supposed to produce a re-usable class so that I can easily create many such flag-sets. "choices" is supposed to be a list of `(int, human readable string)` pairs, but its not actually supposed to be an attribute or method; the iterator is what lets me loop over it. Thanks though :) – mpen Jul 10 '10 at 04:29
  • I'm still not quite sure why you've done in this way. The "spec" is only supposed to have 2 "columns". The "ID" is supposed to be generated automatically. You getattr is inefficient; why are you looping over the list every time? That's why I originally stuffed it into a dict -- O(1) lookup. It's also not supposed to be specified to "abilities" -- it's a generic class; although you coded that way I'm not sure why you went with that name. Your point still stands about using getattr instead, but still.. – mpen Jul 10 '10 at 22:59
0

You need two key changes -- the last lines of flags should be:

choices = iter(zip(nums,words))
attrs['__iter__'] = lambda self: choices
return type('Flags', (), dict(attrs))()

Note that I've added a line setting __iter__, and a trailing () in the return to instantiate the type (to loop on a type you'd have to use a custom metaclass -- way overkill, no need).

The last line of flags should actually be:

return type('Flags', (), attrs)()

as there's no reason to make a copy of attrs, which is already a dict (but that's an innocuous redundancy, not a killer mistake;-).

Alex Martelli
  • 854,459
  • 170
  • 1,222
  • 1,395
  • 3
    @Alex: doing it that way wouldn't mean that the terator would work only once? Instead of having a "choices" var, I think the correct thing would be to do: attrs['__iter__'] = lambda self: iter(zip(nums,words)) – jsbueno Jul 10 '10 at 04:27
  • The two of you got it! Thanks so much. I'm excited about this magical function :D – mpen Jul 10 '10 at 04:32
  • 3
    You can always count on SO to get people excited about doing things the wrong way. – Glenn Maynard Jul 10 '10 at 04:43
  • @jsbueno, right: I imagined the assignment to `choices` was there for a reason (limiting the iterations to just one) -- if that's not the case (i.e., there is no reason for `choices` to exist) then the `choices = ...` line should be removed (your `lambda` is then correct, though `lambda self: itertools.izip(nums, words)` would be better -- with an `import itertools` at the top of the module, of course). – Alex Martelli Jul 10 '10 at 04:43
  • 2
    There's no difference between returning that completely useless class instance or returning the iterator itself. And even if there was, you shouldn't return a class instance, you should make the whole thing be a class and use `__init__`, and even if you *didn't* return an instance you should use the class syntax, and not that deranged `type(name, supers, d)` syntax which serves no purpose except to make your code ugly! Then you overcomplicate by mentioning metaclasses, and admit that the `dict()` call was devoid of purpose. And your answer got accepted? Edit: Ah, well, not anymore I guess. – Devin Jeanpierre Jul 10 '10 at 05:37
  • @Devin, you're totally wrong when you say "There's no difference between returning that completely useless class instance or returning the iterator itself": you're completely ignoring the need to make attribute accesses such as `.FLY` work. Using class syntax is certainly an option, but so is calling `type` -- why do you think we put what you called "deranged syntax" (it's a simple call on a callable, could hardly be simpler!) in Python in the first place?! **Exactly** to make it easy to create types on the fly (it serves the purpose of directness and conciseness). (cont.) – Alex Martelli Jul 10 '10 at 05:39
  • (cont.) custom metaclasses must be mentioned because the OP was trying to iterate on a _type_ (instead of instantiating it), and **that** would require custom metaclasses (which I explain would be overkill). Finally, not sure what you mean by "admit that the dict() call was devoid of purpose" (you originally said "stupid" then edited -- cold feet?): I **advised** the OP who had put in that redundant call of this fact. And yes, you guys apparently managed to panic Mark into retracting his acceptance and posting a twice-as-verbose A to his own Q instead -- congratulations. – Alex Martelli Jul 10 '10 at 05:44
  • 1
    @Alex, you are aware that you don't have to answer the question *exactly* as asked, yes? I'm not sure why you're getting butthurt over that he realized that turning `flags` into a class instead of a function is more sensible. – habnabit Jul 10 '10 at 05:48
  • "Cold feet", huh? I have some interest in not offending people, even if I disagree with them. That means, among other things, not being a jerk and insulting them. Don't try to goad me into being offensive, it won't work. As for your pedantry regarding the definition of the word "syntax", that is what it is: pedantry. I believe you know that I meant, "alternate form" or whatever word is sufficiently vague to characterize visual structure without being linked to a formal grammar. On to actual substance: ... ah, well, you did a two-parter, so can I. – Devin Jeanpierre Jul 10 '10 at 05:49
  • You're correct, I was hasty in my "no difference" assertion. As for the three-form type form, I disagree that it was created for conciseness. The logic doesn't bear that out: There should be one, and preferably only one, obvious way to do it. An equivalent form "for conciseness" is foolish in that context. The other use-case is metaclasses: `meta(name, supers, d)` is the metaclass interface, and any callable that supports it may be used as a metaclass. Since type is a metaclass, it must support and exemplify that. Actual use as a class builder is mildly unreadable at best. – Devin Jeanpierre Jul 10 '10 at 05:52
  • @Aaron, of course I know there is no obligation to answer the question as asked (feel free to look at my 4985 answers to see that, while I often do so, sometimes I prefer to offer alternative advice instead). I've gotten quite angry (not hurt) at Mark for deciding to penalize me because he apparently decided (under the intense wave of attacks -- not even downvotes against HIM, that I've seen!) that his question was bad, as well as the many attacks against myself. I guess some people just love making sworn enemies, and are quite good at that (must be all the practice they have). – Alex Martelli Jul 10 '10 at 06:01
  • @Devin, so after attacking me starting with an assertion that you now admit is wrong, you're trying to _teach me_, a Python core committer and PSF member since many years, why we put the 3-args form of `type` in the language?! "An equivalent form for conciseness" (and use in expressions) is exactly what `lambda` (vs `def`, similar to `class` in that it's a compound statement which binds a name) has been (since a longer time) -- I'm no lover of `lambda`, but, since it's been put and left in Python, I use it when appropriate, **exactly** like 3-args `type` (more readable than `lambda`). – Alex Martelli Jul 10 '10 at 06:07
  • 1
    @Alex I don't think Mark is trying to make you his sworn enemy. – Devin Jeanpierre Jul 10 '10 at 06:07
  • 1
    @Alex, between your 5k answers and you getting "quite angry", perhaps it might be time for a break from SO. – habnabit Jul 10 '10 at 06:09
  • 2
    This is a dispute, not an attack. I think you are and were wrong, that's all. Even the insult I removed was not against you, but your code. I don't want to teach you; as you said, I wasn't there. I actually took care to use an argument of reasoning, because logic is all I have. Regardless, I do know that using it the way you and Mark did is ugly and less maintainable. As Code Complete says, "Program *into* the language". With lambda, I believe GvR has stated some regret about that, no?: http://bit.ly/LvPE0 . And please don't browbeat me with credentials, I am not attacking your qualifications. – Devin Jeanpierre Jul 10 '10 at 06:26
  • @Alex: re: "I've gotten quite angry (not hurt) at Mark" -- Why? Your answer does exactly what I asked, and solved the problem that I had. For that, I upvoted you. I retracted the accepted answer because I had awarded it too hastily, and others' posted what I believe to be a more clean and maintainable solution. Why should you take offense to that? "not even downvotes against HIM" -- why should they downvote me? I asked the question, which means I'm obviously lacking knowledge and I'm not sure about the best way to tackle the problem, so of course there are things wrong with the code I posted! – mpen Jul 10 '10 at 23:18
0

Based on your guys' suggestions, I came up with this:

Source

class enumerable(object):
    def __init__(self, func, *opts):
        keys = func(len(opts))
        self.attrs = dict(zip([t[0] for t in opts], keys))
        self.opts = zip(keys, [t[1] for t in opts])
    def __getattr__(self, a):
        return self.attrs[a] 
    def __len__(self):
        return len(self.opts)
    def __iter__(self):
        return iter(self.opts)
    def __deepcopy__(self, memo):
        return self

class enum(enumerable):
    def __init__(self, *opts):
        return super(enum, self).__init__(range, *opts)

class flags(enumerable):
    def __init__(self, *opts):
        return super(flags, self).__init__(lambda l: [1<<i for i in range(l)], *opts)

### tests:

Abilities = enum(
    ('FLY', 'Can fly'),
    ('FIREBALL', 'Can shoot fireballs'),
    ('INVISIBLE', 'Can turn invisible'),
    ('FROST_NOVA', 'Can call down an ice storm'),
    ('BLINK', 'Can teleport short distances'),
)

print 'Fireball = %d' % Abilities.FIREBALL
print 'Number of options = %d' % len(Abilities) 
for k, v in Abilities:
    print '%d: %s' % (k, v)

Output

Fireball = 1
Number of options = 5
0: Can fly
1: Can shoot fireballs
2: Can turn invisible
3: Can call down an ice storm
4: Can teleport short distances

For whatever reason, my particular application needs __deepcopy__ to be implemented. Since these classes are for building "constants", none of their attributes should ever be changed after creation; thus I hope it's safe just to return self.

mpen
  • 272,448
  • 266
  • 850
  • 1,236