0

I have a bunch of files with many tags inside of the form {my_var}, {some_var}, etc. I am looking to open them, and replace them with my_var and some_var that I've read into Python.

To do these sorts of things I've been using inspect.cleandoc():

import inspect, markdown
my_var='this'
some_var='that'
something=inspect.cleandoc(f'''
    All my vars are {some_var} and {my_var}. This is all.
''')
print(something)
#All my vars are that and this. This is all.

But I'd like to do this by reading files file1.md and file2.md

### file1.md
There are some strings such as {my_var} and {some_var}.
Done.

### file2.md
Here there are also some vars: {some_var}, {my_var}. Also done.

Here's the Python code:

import inspect, markdown
my_var='this'
some_var='that'

def filein(file):
    with open(file, 'r') as file:
        data = file.read()
    return data

for filei in ['file1.md','file2.md']:
    fin=filein(file)
    pre=inspect.cleandoc(f'''{fin}''')

However, the above does not evaluate the strings inside filei and replace them with this (my_var) and that (some_var), and instead keeps them as strings {my_var} and {some_var}.

What am I doing wrong?

Jongware
  • 22,200
  • 8
  • 54
  • 100
Sos
  • 1,783
  • 2
  • 20
  • 46
  • What is `inspect.cleandoc` expected to do? – bereal Feb 03 '20 at 11:12
  • Does this answer your question? [Replace string within file contents](https://stackoverflow.com/questions/4128144/replace-string-within-file-contents) – Zaraki Kenpachi Feb 03 '20 at 11:13
  • 1
    @bereal appologies, someone accepted an edit that excluded my preamble. I've edited my input. – Sos Feb 03 '20 at 11:16
  • @ZarakiKenpachi not really, as I'd like to avoid having to define a replace for every single variable substitution: in this work case I am suggesting 2 variables, so it is easy. But in my real case, I am importing many variables into python, which would imply that I explicitly specify each variable substitution. – Sos Feb 03 '20 at 11:17
  • @usr2564301 Sorry, I was trying to fix the function, as it would have produced an "undefined data variable" error due to its indentation. I'll try to be more careful next time. – Jatin Bansal Feb 03 '20 at 11:25

2 Answers2

2

You can use the .format method.

You can use ** to pass it a dictionary containing the variable.

Therefore you can use the locals() or globals(), which are dictionary of all the locals and globals variables.

e.g.

text = text.format(**globals())

Complete code:

my_var="this"
some_var="that"

for file in ["file1.md", "file2.md"]:
    with open(file, "r") as f:
        text = f.read()
    text = text.format(**globals())
    print(text)
Tommaso Fontana
  • 710
  • 3
  • 18
  • 1
    Awsome! Didn't think about passing `**globals()`, but this solves my problem. – Sos Feb 03 '20 at 11:23
  • 1
    In general `.format(**locals())` can "emulate" the f-strings. I mostly use it on my scripts since it's both python 2 and 3 compatible so I don't have to worry about the python version. – Tommaso Fontana Feb 03 '20 at 11:27
2
  • f-strings are a static replacement mechanism, they're an intrinsic part of the bytecode, not a general-purpose templating mechanism
  • I've no idea what you think inspect.cleandoc does, but it does not do that.
  • Python generally avoids magic, meaning it really doesn't give a rat's ass about your local variables unless you specifically make it, which is not the case here. Python generally works with explicitely provided dicts (mappings of some term to its replacement).

I guess what you want here is the format/format_map methods, which do apply to format strings using {} e.g.

filein(file).format(my_var=my_var, some_var=some_var)

This can be risky if the files you're reading are under the control of a third party though: str.format allows attribute access and thus ultimately provides tools for arbitrary code execution. In that case, tools like string.Template, old-style string substitution (%) or a proper template engine might be a better idea.

Masklinn
  • 34,759
  • 3
  • 38
  • 57
  • Dear Masklinn, thanks for your comments. I have edited my question to explain how I've been using `inspect.cleandoc()`, as it had been removed from my previous edit. I am also reading variables from another file and I'm wondering if there is a way to use them directly instead of having to specify all of them under `.format()`? I appreciate your comments – Sos Feb 03 '20 at 11:20
  • 1
    The cleandoc call still makes no sense (it's the f-string which does the work). You can use format_map and give it a map (a mapping), or use "parameter unpacking" with str.format for the same effect (`str.format(**params)`), both require that you can get your substitutions as a Python dict somehow. – Masklinn Feb 03 '20 at 11:49