-2

On a Windows PC using python3 I'm using a Raspberry Pi simulator to develop a system whilst building the hardware. I'm now at the point where I would like to test on a real Raspberry Pi. It would be nice to have the same code and switch between the two modes (simulator & real hardware) by just changing a switch.

The code using the simulator is at the start (after some import json code) is:

# load the Rasp Pi tkgpio simulator
from tkgpio import TkCircuit
# get simulator configuration settings
with open("EMSSimV8.json", "r") as file:
    configuration = load(file)
circuit = TkCircuit(configuration)
@circuit.run

def main():

I would like to do:

simulation = True     # or False
if simulation:
    # load the Rasp Pi tkgpio simulator
    from tkgpio import TkCircuit
    # get simulator configuration settings
    with open("EMSSimV8.json", "r") as file:
        configuration = load(file)
    circuit = TkCircuit(configuration)
    @circuit.run
else:               # production on a real Pi
    main().run

def main():

but python gives an "unexpected unindent" at the else: statement

Is there some way to achieve this?

Alan
  • 9
  • 2
  • 7
    You can't decorate a statement other than a function or class declaration. Despite the formatting, `@circuit.run` is attached to `def main():` - it's not a separate command. – jonrsharpe Aug 31 '21 at 21:33
  • 3
    Maybe pose the question as "_How_ do I make a decorator conditional?" -- that way you're focusing on the thing you actually want to accomplish, not the syntax you're trying to use to do it. Because this syntax isn't actually _expected_ to work, so "how do I make this syntax work?" isn't a question that makes any sense, but "how do I accomplish the intent behind this syntax?" can be a good one. – Charles Duffy Aug 31 '21 at 21:40
  • 1
    I can't understand the question as posed. Please show: 1) a *complete* example of `main`; 2) unconditional code that shows *exactly* what should happen when `simulation == True`; 3) unconditional code that shows *exactly* what should happen when `simulation == False`. – Karl Knechtel Aug 31 '21 at 22:16
  • 1
    Does https://stackoverflow.com/questions/739654/how-to-make-function-decorators-and-chain-them-together help (read the long, second answer)? How about [the section in the original PEP](https://www.python.org/dev/peps/pep-0318/#current-syntax) where it explains what decorators actually do? – Karl Knechtel Aug 31 '21 at 22:17
  • 1
    Alan I did my best to answer your question but I agree with the other commenters. You are asking two questions, "how can I selectively run code with a decorator two different ways in two different environments" and "why am I getting an unexpected indent error". @jonrsharpe answered the 2nd question very well in the comment above, so I tried to answer the first question. I recommend editing your question so it is useful to others based on all of our feedback. Please ask questions in the comments if we can help more. – Jessica Pennell Aug 31 '21 at 23:02
  • Many thanks to all those who have commented, especially @Jessica Pennell and CrazyChucky who both provided code examples. In the past I had not needed or knew about decorators; if I had that would have aided my research. I will change the title. Karl-Knechtel ref looks well worth reading. We have visitors staying tomorrow but I will try to find time tonight to try the suggestions. Once I have modified the code I will report back. Alan – Alan Sep 01 '21 at 16:24
  • 1
    If you didn't actually know what the `@` syntax was doing, you should have tried to figure that out *first* before trying to write code around it. – Karl Knechtel Sep 01 '21 at 17:01
  • I was simply following the instructions by the developer of tkgpio who provided the code template. I've managed to write over 2,500 lines of code in main() for an energy management system, testing it with the tkgpio simulator without any issues. Now I understand about decorators I remember back in c1971 I wrote a online/database simulator using a similar technique on an IBM 360 mainframe. I used the PL/1 preprocessor to wrap the code written by the application programmers to catch the terminal and database calls. It speeded up development considerably. – Alan Sep 01 '21 at 22:26

2 Answers2

2

The @ in Python means you're dealing with a decorator, which is a (usually) more convenient way of wrapping or modifying a function, method, or class to do something slightly different. This code:

@some_decorator
def my_function():
    ...

is essentially the same as this:

def my_function():
    ...

my_function = some_decorator(my_function)

After either one of these, anything later in the code that calls my_function() will actually be calling whatever some_decorator(my_function) returned; this will usually be a wrapped or modified version of the original my_function that has some additional behavior.

I'm not familiar with Raspberry Pi or the packages you're using, so I can't exactly promise this will work, but you should be able to do something very much like this:

from tkgpio import TkCircuit

simulation = True     # or False

def main():
    ...

if simulation:
    with open("EMSSimV8.json", "r") as file:
        configuration = load(file)
    circuit = TkCircuit(configuration)
    main = circuit.run(main)

if __name__ == '__main__':
    main()

That way you only create the TkCircuit and use it to wrap main if needed.

CrazyChucky
  • 3,263
  • 4
  • 11
  • 25
  • 1
    That's great @CrazyChucky. Your code works! I slightly altered it to put the line "from tkgpio import TkCircuit" within the conditional statement "if simulation:" so tat it is only loaded when simulating. – Alan Sep 01 '21 at 22:07
-1

If you are short on time skip to the bottom. The example I provided is not guaranteed as I do not have enough information from you.

To clarify I am answering the underlying question, "how can I selectively decorate a function". I don't have your underlying code or a definition of the decorator function to work with.

EDIT An "avoid repeating yourself" concern was raised about the main function. You can conditionally include modules through the same mechanism in python. Just add your main routine to two different files and include the routine you want to use. I have demonstrated how to do this at the bottom.

There are many ways to skin this cat, this is just the method I prefer since everything stays nice and encapsulated. The underlying principle is the same though : you can include decorators and other symbols that don't exist without generating "compile time" errors in python provided the section including them is "dead code". Python is a lot more lenient than other languages are where this is concerned.

I will include an example that might work at the bottom, but again, I don't have your underlying code.

First the principle.

To selectively decorate a function, you can just decorate a method

def external_func(func):
  def dostuff():
    print("hi there!")
  return dostuff

class selectively_decorate :
  def __init__(self):
    global dodecorate
    if dodecorate:
      """
      Try replacing this with @doesntexist
      if dodecorate is falsy you can still create an instance
      """
      @external_func
      def doTheThing():
        return 1
    else:
      def doTheThing():
        print("well hello yourself!")
        return 1
    doTheThing()

Example

dodecorate=0
a=selectively_decorate()

Outputs

well hello yourself!

Example

dodecorate=1
a=selectively_decorate()

Outputs

hi there!

In your case this might work :

# load the Rasp Pi tkgpio simulator
from tkgpio import TkCircuit

is_simulation = 0

class config_loader:
  def __init__(self, jsonFile):
    global is_simulation

    # get simulator configuration settings
    with open(jsonFile, "r") as file:
      self.configuration = load(file)
    self.circuit = TkCircuit(self.configuration)

    """"
    You do not need to repeat the decorator or definition of main
    It is included here only to show that it can be selectively
    included
    """
    if is_simulation :
      from module1 import main_routine
      """
      In other words, you can delete the next three lines...
      """
      @circuit.run
      def main() :
        main_routine()
    else :
      from module2 import main.routine
      """
      ... and decrease the indent for the next three one level
      """
      @circuit.run
      def main() :
        main_routine()
    main()

load = config_loader("EMSSimV8.json")

Obviously this example can be simplified further, but hopefully the idea has been adequately demonstrated.

Jessica Pennell
  • 578
  • 4
  • 14
  • Wouldn't this require the entire `main` function to be copied and pasted? That sounds like a nightmare to maintain as updates are made. – CrazyChucky Sep 01 '21 at 01:06
  • Many thanks @Jessica-Pennell for taking the time to produce an explanation and a detailed response. As CrazyChucky says, if I understand correctly, I would need copy and paste *and indent* over 2,500 lines of code! That's why I didn't include the main() code in my question. – Alan Sep 01 '21 at 22:13
  • Not at all @CrazyChucky . You can conditionally use a from statement as well, anywhere in your code. Just include main from a different module. No need to copy paste and indent. – Jessica Pennell Sep 03 '21 at 16:18
  • @Alan if it helps at all, this method is analagous to using preprocessor commands to cause different code execution in different environments in c. All the same patterns work in python. Just as in C, you don't repeat yourself when doing things this way, you just add the code you want to selectively include to different files. – Jessica Pennell Sep 03 '21 at 16:24
  • You can't import a function when defining it... the code you posted has two separate `def main():`s, one decorated and one not. If you mean for it to be used differently, it's not clear (at least not to me). – CrazyChucky Sep 03 '21 at 21:32
  • 1
    P.S. I notice in your edit you mention, "Just add your main routine to two different files and include the routine you want to use." Do you mean moving the `main` function definition to one module, then copying into a second one where it's decorated, and then importing either the decorated or undecorated version in the main script? If so, I don't see how that avoids the issue of maintaining two copies of the same function. (Also @Alan, if you have one function that's 2,500 lines, you should really consider breaking it down into manageable, reusable pieces.) – CrazyChucky Sep 03 '21 at 22:17
  • Hi @CrazyChucky. My code is not 1 function but (according to a source code find on "def " I have 169 functions/methods, which = on average 15 lines per function/method (and many of those will be comments) – Alan Sep 04 '21 at 20:33
  • @Alan ...Are they defined inside `main` for some reason? Usually you would define those things at the top level and just call them from where you need them. – CrazyChucky Sep 04 '21 at 20:37
  • @CrazyChucky I was just following the instructions (template) provided by tkgpio simulator which said "add you own code here" under main(). If it was all my own code then yes, I would define those functions/objects at the top level. Maybe that would still work with tkgpio. Some more generic code I put into separate files and imported them. – Alan Sep 06 '21 at 10:53
  • I see where my example is unclear @CrazyChucky thank you for pointing it out. I'll edit my method. I can agree that what I've posted is a bit convoluted. Hopefully the edit makes the intent more clear. – Jessica Pennell Sep 07 '21 at 05:07
  • "the code you posted has two separate def main():s, one decorated and one not" This was intentional. In Alan's code, this is what he was doing in his if/else statement. While it's likely the case Alan wasn't aware of what the decorator was doing, others searching for Alan's question (conditionally decorating is a frequent topic) would benefit from having all the possible cases covered, and this also demonstrates to Alan the difference between the decorator and the function, and the decorator's role. Also note the decorator symbol might only exist in one environment. – Jessica Pennell Sep 07 '21 at 05:19