elem_init is the method you're looking for, but there are a couple of problems with the way you're trying to use it..
First of all, as the error you experienced indicates, parse result objects don't support assignment using the obj[index] = value syntax. If you really need to, the way to get around this would be to modify the object's "elements" attribute instead (obj[index] is really just a shortcut for saying obj.elements[index] anyway).
The other problem with your code, however, is that L('@') is a grammar definition (class), not a parse result (object), and the 'elements' list should contain parse results, not grammar classes. You would instead need to create a result object that looks like the result you would get from matching L('@') to some text, and then use that. Faking match objects is possible to do, but not really documented well, because it's generally the wrong way to go about things anyway.
I think part of the problem is that you're trying to make Modgrammar behave like pyparsing, when Modgrammar is actually quite a bit more sophisticated, and has better ways to do most things. If you really want .tokens() to return something else for a DoubleAt match, instead of trying to fake a parse result by replacing elements with other ones in the parse tree, the more modgrammar-ish way to do things would be something like the following:
class DoubleAt(Grammar):
grammar = ("@@")
grammar_terminal = True
def elem_init(self, sesiondata):
self.string = "@"
('grammar_terminal = True' tells .tokens() to treat this object as the terminal instead of the (L('@@')) sub-match, and then we just set the DoubleAt result's 'string' attribute to "@" instead, so that's what ends up in the .tokens() output)
Even this is a bit of a hack, though. The better way is to actually have your application get its information from the full result objects instead of just the matched text strings (for example, by using .terminals() instead of .tokens()), so that instead of doing 'if token == "@":' somewhere, you could do 'if isinstance(elem, AtSign):' instead. Then, for example, you could make AtSign match '@' or '@@' (or make subclasses of AtSign, etc), and your code would still do the right thing (and you wouldn't need any 'elem_init' hacks).
-alex