Info_timer return value in AppDaemon

I started with basic in high school on a dec PDP-11. All together I’ve been in IT in one form or another for around 37 years (dam, that makes me feel old). Learned fortran on punch cards in college, then went to work for the university as a programmer, wandering between assembly, C, fortran and even a little cobol (very little). I spent time writing NLM’s (Network Loadable Modules) for the old Novell servers, wrote warehouse management systems in C, and a heavy amount of Oracle PL/SQL. I’ve learned several of the scripting languages like Moca and Abap. I’ve done my share of web development in ASP and Java. I have a degree in Electrical Engineering and a Masters in Computer Information Systems, but with all of that my passion is programming.

Through all of that the changes we have seen, the ones that I felt were the best advances we have made from a programming language perspective, is structure (both flow, and data), and Objects. In Basic and Cobol, goto’s were a nightmare. Pascal and C gave us the ability to structure our code so that it was easier to follow and maintain. Yes, types were a nightmare so were linked lists. The new structures we have today, have brought us a long way from those.

Python is more like the wild west, variable types are fluid and easily converted (which is a good thing), argument passing is what it is, any problems, and just throw self. in front of it and problem solved. I just think that if we are going to have something that looks up a value, it should either just look up the value and return it or return a dictionary or list with the value(s) in it, or have an additional function that tests to see if the subject of the query exists first so you can avoid the call if it’s going to fail.

The nice thing about returning a dictionary from functions like info_timer is that it makes the return values easy to extend if you want to bring by additional information in the future without having to re-code downstream apps immediately. They only have to re-code if they want to consume that data. It also allows you to return “None” if the timer doesn’t exist. When functions return multiple values, do you have to account for all of them to store return values when they are called? If so, then adding an additional return value to a function like that would require immediate recoding of any apps that use that function.

This is really a conversation better had over a beer and some burgers. It’s conceptual and very dependent on the style of the developer. For me, I’ll just take info_timer, override it and deal with the error in the override function. That way you don’t have to change anything which would impact anyone else using the app, and my code stays clean the way I like it. :slight_smile:

to bad i missed this discussion when it was going on, but i am with @turboc as it comes to avoiding try/except.

guess turbo is becoming the 4th musketier. :wink:

i started with programming about the same time as turbo, around 34 years ago.
my path was a little like @aimc in the first few years, from basic to pascal.

but i never got to C

i stepped over to programming in visual basic as soon as that was possible.

so andrew, if you are going to make it consistent then prefer that you change the part where exceptions are raised then that you change the parts where none is returned.

i rather have code like this:

if a==c:
  do something
else:
  log something

then this:

try:
  if a==c:
    do something
except:
  log something

Cool,
Dartanian got Raquel Welch. :slight_smile:

1 Like

i rather have code like this:

if a==c:
  do something
else:
  log something

then this:

try:
  if a==c:
    do something
except:
  log something

To be nitpicky here, that’s not exactly how you’d want to use try/except. :slight_smile:

Take for example the code I have in one of my apps…

class SomeApp(appapi.Appdeamon):
    def initialize(self):
        # a way to keep track of individual users' timer-callbacks
        self.user_timer_library = {}

    def person_ishome(self, event_name, data, kwargs):
        mac = data['mac']

        try:
            # cancel any previous timers, effectively resetting the TTL clock
            self.cancel_timer(self.user_timer_library[mac])
        except KeyError:
            self.log('No timers currently set for {}'.format(mac))
        except Exception as e:
            self.log('Something went wrong...\n{}'.format(e))

        # set a timer on the user, running the event trigger for person_isout if it expires
        # add it to the users' timer callback library
        self.user_timer_library[mac] = self.run_in(self.trigger_person_isout, 
                                                   seconds=int(self.args['ttl'])*60, 
                                                   mac=mac)

The function person_ishome() is meant to be able to be called multiple times per user (mac). It is entirely possible it will be called multiple times before the callback in run_in() is able to fire. You’ll see in the middle, a try/except block that will cancel a timer if one exists for the user (mac). You could certainly rewrite the few lines to …

        if mac in self.user_timer_library:
            # cancel any previous timers, effectively resetting the TTL clock
            self.cancel_timer(self.user_timer_library[mac])
        else:
            self.log('No timers currently set for {}'.format(mac))

It’s really just a matter of preference. :slight_smile:

yeah youre right, i dont use the key error. most of the time.
in most cases i dont care why something went wrong, i just want to know THAT something went wrong.
and a keyerror is just what it says, an error.
and i dont like to have errors at all. and if there is a return zero (or any value at all), there would not be an (key)error :wink:

i hope this app you showed is a general app and you call the code from other apps?

and wont the run_in at the end Always be called?

I do find that

        try:
            self.trv_id = self.args["trv_id"]
            self.sensor_temp = self.args["sensor_temp"]
            self.slider_set_temp = self.args["slider_set_temp"]
            self.select_state = self.args["select_state"]
            self.away_temp = self.args["away_temp"]
            self.power_mode_switch = self.args["power_mode_switch"]
        except KeyError as e:
            self.log("Argument not found : {}".format(e), level="ERROR")
            return

Is a much simpler way of detecting configuration errors than checking individually.

but what if you want the different args to be optional and you want to take different actions if the args arnt there?
then you get:

try:
  self.trv_id = self.args["trv_id"]
except Keyerror as e:
  log that
if self.trv_id == somevalue:
  do something

for all your keys instead of

self.trv_id = self.args["trv_id"]
if self.trv_id == somevalue:
  do something

i wouldnt mind the try/except that much if it would bring me the same as the if then.
but i always seem to need both to examine 1 value if a keyerror can be raised.

In the same initialization I have

        try:
            self.overshoot = float(self.args["overshoot"])
        except KeyError:
            self.overshoot = 1.3
        except ValueError:
            self.log("Argument overshoot must be a float.  Using default 1.3",
                    level = "WARNING")
            self.overshoot = 1.3

Which has not greater merits than checking, other than it is the same style as the previous code.

without errors that would be:

self.overshoot = 1.3
if isfloat(self.args["overshoot"]):
  self.overshoot = float(self.args["overshoot"])
else:
  self.log("Argument overshoot must be a float.  Using default 1.3",
                    level = "WARNING")

its still a matter of preference, where i prefer not to use errors as a part of my code. :wink:

Exceptions are not necessarily errors.

Your code also needs an extra check to see if self.args["overshoot"] exists before you use it.

I completely agree.

not if it return Null if it isnt there :wink:

you say exceptions arent necessarily errors but you use:
exept keyERROR or except valueERROR

the whole principle is
try something and if it doesnt work(except) try something else.

“in the old days” that was considered bad programming.

In Python it doesn’t. It would be interesting to construe another language that did that, but it would be even more off topic :grinning:

You are taking one specific example, whereas I am making a general point.

Exceptions are normally regarded as ‘exceptions to the normal flow of the program’. These are often errors, but are not necessarily so. You can throw an exception to indicate any kind of condition to be handled by higher level of the program Calling them errors is not acknowledging their full usage.

I don’t understand. You are saying

if (try_something()) {
  do_something()
} else {
  try_something_else()
}

is bad programming?

Wow I didn’t mean to stir up a hornets nest on coding style. :smile:

There are definitely places for try/except clauses and for if/elif/else clauses. I guess to me it comes down to catching errors with the try/except clauses and controlling their impact. There are times where an error occurs and it should not be a critical error and a try/except clause allows you to recover from that situation. In those cases the try/except clauses are needed. Where I have an issue is where functions return an error as a valid response. For example lets say I have a function to get the light switch associated with someone’s room based on a dictionary that is passed in. My function returns the switch entity. If the person is not in the dictionary, I can do one of two things. I can return a key error, or None. Which is more appropriate? Both work. But “my” style would dictate that I return None so that I can easily test for it in an “if” statement.
Maybe it’s just a paradigm I need to get over. But to me errors are for catastrophic problems that we didn’t expect and really need to stop the program for.
But like I said, maybe it’s just me. Just like there is more than one way to skin a cat, there is more than one way to write a program.

Or thirdly, you could raise an exception, which any method in the calling stack can handle.

I am not advocating doing this, but exceptions are another tool in the toolbox, available to use in whatever situation you like.

Agreed they are another tool we can use. There is a time and a place for every tool in a language. I just question if we all (yes including me) get into the run of using the same tool all the time and forgetting about the others we have that might do a better job. Personally, I think raising an error to handle a known state in the code is using it where other solutions would be better. But that’s just me. Each to his own…

I find it interesting that you also use this terminology, rather than ‘raising an exception’.

Is there something about the mechanism that it was only used for errors?

It may just be history and habit on my part. When I have looked it up in the past it has always been a part of error handling. It’s probably just a paradigm shift I need to go through. It’s what happens when you have been doing something for 30 years and are making a transition into yet another language. It’s more like PLSQL than “C type” languages in this case.

This is a great discussion, let me add by saying that I think you all should read this response from Raymond Hettinger on this topic. Raymond is a core developer to the language and has many, many great talks that explain some of the intricacies of the language. :slight_smile:

it python it does if the function is constructed to do it.

yes and no.

in no languages i know there is a “if (try something)”
you dont try to compare values, you compare values.
in the line if (try_something()):
try_something() must be a fuction that returns a boolean in any case.

so yes it would be bad if you would actually try something. and no if it is a function that returns a booleabvalue, but then the name would be misleading.

@turbo explains it just like i see it. i can see the additional value from try/except, but i see it absolute as an extra tool and not as an replacing tool.

if i create a function there are two possibilities:
a) it is used to return a value and used like myvar = somecalc(), that would leave you with a filled myvar in any case
or
b) it is used to perform an action then it would be used as someaction() and would return nothing.

the b) case could be extended to returning a boolean that tells if all actions were handled without error.

i know that a lot of “old rules” are not normally used by every one anymore, and a few of them are not needed anymore in some cases and in some languages.
but that doesnt mean that those “old rules” have no value at all, in my eyes…

The problem I am trying to point out is that self.args[“overshoot”] gets referenced before the isfloat() function is called, and causes an error if it doesn’t exist.

To make the code equivalent to my try…except example here it needs to check that “overshoot” exists in args first. Something like

self.overshoot = 1.3
if "overshoot" in self.args:
  if isfloat(self.args["overshoot"]):
    self.overshoot = float(self.args["overshoot"])
  else:
    self.log("Argument overshoot must be a float.  Using default 1.3",
                    level = "WARNING")

The link SupahNoob gave in the previous post is far more informative than anything I could say, so I will not spend any more time on the subject.