Predefine .format strings (e.g. '{a1},{a2},{a3}'.format(arbitrarystringlist)?

Discuss how to use the Ren'Py engine to create visual novels and story-based games. New releases are announced in this section.
Forum rules
This is the right place for Ren'Py help. Please ask one question per thread, use a descriptive subject like 'NotFound error in option.rpy' , and include all the relevant information - especially any relevant code and traceback messages. Use the code tag to format scripts.
Post Reply
Message
Author
Arcadia
Newbie
Posts: 13
Joined: Sat Aug 28, 2021 11:31 pm
Contact:

Predefine .format strings (e.g. '{a1},{a2},{a3}'.format(arbitrarystringlist)?

#1 Post by Arcadia »

Howdy again folks!

As I work through my project I find myself having to use the .format function quite a lot, since direct interpolation doesn't seem to play nicely with dictionary-based key:value pairs, and the alternative might add up to ten lines of code for some lines of dialogue. Most of these are consistent instances - so, using an arbitrary example string, I might call {a1} across ten different dialogue strings. At the moment, for each .format string, I need to provide that .format(a1=object.arbitrary['a1']), and so on. This isn't so bad, and I can always just copy paste a bulk .format(a1=...,a2=...,a3=...) for these recurrent string substitutions - but it's messy and adds unnecessary length.

Is there a way to essentially predefine a .format string set itself? So that at start, I might set say, 'define astrings = .format(a1=...)' etc, and then just be able to follow up the string I'm using .format on with the predefined string set (e.g. '{a1}'.astrings)? I've tried doing more or less that and get syntax errors out of it.

Code: Select all

Broken examples:
define astrings = .format(a1=object.arbitrarydic[a1])

$astrings = .format(a1=object.arbitrarydic[a1])

$SetVariable(astrings, .format(a1=object.arbitrarydic[a1]))
These all produce a syntax error.

User avatar
hell_oh_world
Miko-Class Veteran
Posts: 777
Joined: Fri Jul 12, 2019 5:21 am
Contact:

Re: Predefine .format strings (e.g. '{a1},{a2},{a3}'.format(arbitrarystringlist)?

#2 Post by hell_oh_world »

you can try to unpack the dict instead.

Code: Select all

>>> dict_ = dict(k1=1, k2=2, k3=3)
>>> template = "{k1} {k2} {k3}"
>>> template.format(**dict_)
"1 2 3"
This works if the dict has the necessary keys as you provided in your string format.

Arcadia
Newbie
Posts: 13
Joined: Sat Aug 28, 2021 11:31 pm
Contact:

Re: Predefine .format strings (e.g. '{a1},{a2},{a3}'.format(arbitrarystringlist)?

#3 Post by Arcadia »

hell_oh_world wrote: Mon Sep 06, 2021 7:32 am you can try to unpack the dict instead.

Code: Select all

>>> dict_ = dict(k1=1, k2=2, k3=3)
>>> template = "{k1} {k2} {k3}"
>>> template.format(**dict_)
"1 2 3"
This works if the dict has the necessary keys as you provided in your string format.
That seems to have done the trick! Thanks - I knew there had to be a better way to handle it but I'm pretty new to coding anything more advanced than 'hello world'.

To further complicate matters, I'm actually using two different dictionaries to store the information in the object class (I know, I know, but there's a good reason!) and that seems to break the template.format(**dict_) approach. My kludge for getting around that is:

Code: Select all

abdict = dict(object.a_arbitrary.items() + object.b_arbitrary.items())
template = '{a1} ... {b1}...'
template.format{**abdict)
Is that the right way to go about it, or is there something obvious I'm missing?

User avatar
hell_oh_world
Miko-Class Veteran
Posts: 777
Joined: Fri Jul 12, 2019 5:21 am
Contact:

Re: Predefine .format strings (e.g. '{a1},{a2},{a3}'.format(arbitrarystringlist)?

#4 Post by hell_oh_world »

Sadly you cannot add (+) dicts like tuples or list... In newer version of python you can simply do .format(**d1, **d2). However, renpy is still python 2.7.
Your workaround is actually pretty neat.
If I can suggest, you can probably make use of helper functions to simulate the "adding" of dicts...

Code: Select all

init python:
  def add(*dicts):
    rv = {}
    for _ in dicts: rv.update(_)
    return rv

Code: Select all

template.format(**add(dict1, dict2, dict3, dict4,...))

Arcadia
Newbie
Posts: 13
Joined: Sat Aug 28, 2021 11:31 pm
Contact:

Re: Predefine .format strings (e.g. '{a1},{a2},{a3}'.format(arbitrarystringlist)?

#5 Post by Arcadia »

I haven't tried your helper function yet, but it did give me an idea. For anyone else who's using a set of dicts frequently like this, a quick function does the job beautifully and renders it into a single line of code in the dialogue sections.
Function:

Code: Select all

def somedescriptivebutshorttitle(string): # I'm actually using cgenSay as this is a set of defs that are constant throughout character creation. Something short, snappy, and clear is always the ideal - so if it's going to handle past dialogue variables set it to pastChat and so on! We want it to be less hassle than typing out the codes below, afterall.)
        abdict = dict(object.a_dict.items() + object.b_dict.items()) # Presumably this will work for loose dictionaries too.
        renpy.say(N, string.format(**abdict)) # I use N as my narrator shortref, which is probably bad practice. You need to wrap .format in a renpy.say(x,...) if it's going to be displayed using the dialogue system, or else it throws a no character exception. Swap N with whatever character the dialogue belongs to.
Calling the code

Code: Select all

$somedescriptivebutshorttitle('This is a sample string {a1} {b1}')
If you're not using a fixed dict for the string but want a more general function you'd just add dict1,dict2 as arguments to the function and redefine abdict (or whatever you're calling it) to pull dict(dict1.items()+dict2.items()). The function call would then need those added ($x('string',dict1,dict2)) etc) but has the benefit of being basically universal for any time you need to do this and call multiple dictionaries to fill in the .format {} strings. For my purpose fixing the dictionary to my equivalent of object.a_dict, object.b_dict is easier and a time saver, but a general purpose function is probably better practice.

The downside is each time it calls it it's still having to compile abdict, so if your dictionaries are static you could predefine them earlier and just use **abdict without the abdict = ... line. All it's really doing is keeping the code in your labels a little less messy rather than actually optimizing much (though it does cut down on the repetition - if you've only got one string that you need to do this in, then this'd be a bad way. Once you're at ten strings though, you've cut the amount of lines of code actually present in your file for this purpose by half!) Doing it this way also locks it to certain character voices, but as for my purpose this one is solely used for narrator's voice sections that's not an issue. If that needs to change too, you'd just set it to have character name in there, but by then you're probably better off doing it all directly so you can also properly do emotion changes and whatnot.

Presumably it should be fairly trivial to integrate your helper function code in and do it that way too.

Post Reply

Who is online

Users browsing this forum: Semrush [Bot], simple_human, WladekProd