-1

I have a variable, abc, defined as 'batting_stats' and want to run a line of code that is 'pyb.batting_stats(2020)'. I want to use string syntax to create the line by joining abc with 'pyb.' and '(2020)' and then run that line of code - how can I do this? I seem to be creating a larger string instance rather than a runnable line of code. Cheers!

David
  • 45
  • 3
  • 1
    Does this answer your question? [How do I execute a string containing Python code in Python?](https://stackoverflow.com/questions/701802/how-do-i-execute-a-string-containing-python-code-in-python) – wjandrea Oct 10 '21 at 03:35
  • 1
    Or maybe this would be better: [How to access object attribute given string corresponding to name of that attribute](/q/2612610/4518341) – wjandrea Oct 10 '21 at 03:58

1 Answers1

1

You probably don't want to do this; it's possible, but in the vast majority of circumstances, it's a bad idea.

Options:

  • If possible, try to rewrite the code so you don't need to look up by name at all; for instance, you could change the other code so that it stores pyb.batting_stats as a function rather than as a string

    abc = pyb.batting_stats  # note no brackets
    
    # later
    result = abc(2020)
    
  • If you do need to look up by name, you can use getattr, like this:

    # At the top of the script
    ALLOWED_NAMES = ['batting_stats', ...]
    
    # in the code where you need it
    if abc not in ALLOWED_NAMES:
        raise ValueError("Invalid name passed: %s" % abc)
    result = getattr(pyb, abc)(2020)
    
  • Probably a better way would be to use a dictionary as a dispatch table:

    dispatch_table = {
         'batting_stats': pyb.batting_stats,
         ...: ...,
    }
    result = dispatch_table[abc](2020)
    

    This automatically raises an exception if an unexpected name is passed.

    It also has the benefit that you can use a different string in the abc variable than the method name; for example, if you need to rename the function but maintain the names in an API or vice versa:

    dispatch_table = {
         'battingstats': pyb.batting_stats,  # legacy name
         'batting_stats': pyb.batting_stats,
         ...: ...,
    }
    result = dispatch_table[abc](2020)
    
  • If you absolutely must run a piece of code from a string, you can use the eval or exec builtin functions; however, it's almost always a bad idea.

    Use of eval and exec is so frequently dangerous and insecure (Common Weakness #95) that it's better to avoid it altogether. Luckily, at least in Python, there's almost always an alternative; moreover, those alternatives are typically cleaner, more flexible, faster to run and easier to debug. Cases where there's no alternative are vanishingly rare.

Jiří Baum
  • 6,697
  • 2
  • 17
  • 17
  • Thanks that is really helpful! At the top of the comment when you said "You probably don't want to do this; it's possible, but in the vast majority of circumstances, it's a bad idea." was that specifically in reference to the 'eval' and 'exec' code? In general, if I wanted to standardize running methods with certain parameters and wanted to change only the method name (string), what would be the best practice here? Cheers! Really appreciate your help – David Oct 12 '21 at 20:38
  • Or rather, being new to coding, what is the best practice for standardizing running the line of code `pyb.XYZ(2020)` where XYZ changes each time run? Thanks again for the guidance – David Oct 12 '21 at 20:47
  • Best practice would probably be the "dispatch table" option, either written explicitly (like Django routes) or via decorators on each of the methods that may be run (like Flask routes) – Jiří Baum Oct 12 '21 at 22:10
  • I'm assuming that you're getting the method name because you're serving some sort of API; if it's coming from within the same program, probably better not to use a string at all – Jiří Baum Oct 12 '21 at 22:13
  • Thanks, it is actually not for an API (I am not sure what that is.) The use is for an internal program where pyb has different 'methods' (I think that's the name) that I want to call, and all with the same specifications. Basically pyb.batting_stats(2020) and a series of code after, then pyb.XXXX(2020) with the same series of code afterward to format. – David Oct 17 '21 at 20:33
  • In that case, don't use a string; just put the method itself in a variable, or otherwise rearrange it so you don't need to use a string – Jiří Baum Oct 17 '21 at 20:58
  • Ok that is super helpful, thank you so much! One other question, what if I want to create variables based on the input dictionary. So for example if the dictionary contains methods that produce a dataframe, I modify the dataframe, and then want to name and have part of the original variable name can that be done? To use the above example, it could be a variable called 'battingstatsMOD' which is based off the 'battingstats' key in the dictionary. I am not sure how to avoid using a string here to create the new variable name. – David Oct 19 '21 at 17:59
  • Why not put it in a new dictionary? `modified['battingstats'] = ...` – Jiří Baum Oct 20 '21 at 01:24
  • Hmm, I'm not sure I see how that would work. Basically if the input dictionary contains {'xyz': corresponding method, etc} and I then want to create a variable output called xyz1, I could manipulate the 'xyz' string although then would not be sure how to define a variable based on that. – David Oct 22 '21 at 23:57
  • Instead of putting it in a variable called `xyz1`, put it in `results['xyz']`; creating local variables based on a string is usually a bad idea – Jiří Baum Oct 23 '21 at 06:32
  • Ok got it. For a different example, if I have a function that takes a dataframe as input `pyth_wins(df)` and I want the name of the dataframe to create a new variable `total_DF` where DF references the original input name how would you do that? So basically total_ & the string of the input (name of that dataframe). This variable would equal a sum function applied to the dataframe after some manipulation. If I wanted to run `pyth_wins(2020)` it would create variable `total_2020` and if `pyth_wins(2021)`, `total_2021`. This would also be an output of the function, so reference able later on – David Oct 23 '21 at 23:28
  • Same thing, use a dictionary or some other data structure. – Jiří Baum Oct 24 '21 at 05:27
  • Right, but the key to the dataframe would be a string. So if I wanted to manipulate it and add to the end, then use that to create a new variable, can that be done? Not sure I am articulating this well at all, so please let me know if I am not being clear. – David Oct 24 '21 at 20:32
  • Just keep using dictionaries (or similar) for the rest of the program – Jiří Baum Oct 25 '21 at 21:08