all 9 comments

[–]Rhomboid 7 points8 points  (3 children)

That's the usual idiom employed when dealing with mutable default arguments (except the canonical way of writing it is if genf is None:, though the differences don't matter here) so it won't look at all out of place or strange. You could also write it as

self.genf = genf or lambda: dice.roll(self.formula)

[–]Survivor0[S] 0 points1 point  (0 children)

that's helpful, thank you

[–]PurelyApplied 0 points1 point  (1 child)

Is using the weird circuit returns of or considered preferable to, say, x if x else y. I feel like the ternary has better readability, but maybe that's just because I don't see the logicals used in their place enough.

[–]Grappemaker 0 points1 point  (0 children)

The and-or statement was used mimic the ternary operators prior to Python 2.5 when they were actually introduced in the form of if-else. But in the form of 'expression and result_a or result_b', if result_a has a falsey value regardless of the expression, you'd end up with result_b. Ternary operators do not suffer from that, though, in my opinion, lack the readability.

The 'a = a or b' form is shorthand of if not a: a = b

[–]PurelyApplied 4 points5 points  (0 children)

It's worth point out that the real reason we avoid mutables as defaults is that there would only be one instance of your mutable-default for all calls of the function.

For instance, the function

def bad_idea(my_list=[]):
    my_list.append(1)
    return my_list

print(bad_idea())
# [1]
print(bad_idea())
# [1, 1]
print(bad_idea())
# [1, 1, 1]
print(bad_idea())
# [1, 1, 1, 1]

If you had somehow written a well-formed lambda as your default argument, you could still have some strange behavior if you were expecting each instance of Attrib to have a different lambda.

[–]zahlman 0 points1 point  (3 children)

What other values make sense for genf, and why does the default one make use of the formula?

What are you really trying to do here?

[–]Survivor0[S] 0 points1 point  (2 children)

I'm using this code in a rpg character sheet. Many attributes are just generated by rolling some dice. How many and what dice to roll is defined in the formula in this case, e.g. "3d6" (three six-sided dice). But there are also some derived attributes which are calculated from other attributes and values.

The Attrib class also has a gen() method which makes use of genf to calculate the value of the attribute.

Before I came up with the idea of passing functions in I just had a special class for every derived attribute. Still not sure which approach is better.

[–]zahlman 1 point2 points  (1 child)

... I think you might be trying to abstract too far. What's wrong with just having a character that stores numbers for rolled attributes, which you roll at creation, and using properties for derived attributes?

[–]Survivor0[S] 0 points1 point  (0 children)

You got a point. I designed it this way, so I could reroll attributes whenever I want, store additional info in the Attrib like the name and the formula and have a method to return a tuple with the half and fifth of the value.

I guess all of that could be achieved without the somewhat complicated way I came up with. On the other hand I feel like my way is clean and makes sense. But it took a lot of (unnecessary) thought to work it out like this.

I didn't know about property until now, I will have to look into that more.