Differences
This shows you the differences between two versions of the page.
| Both sides previous revision Previous revision Next revision | Previous revision | ||
| developer:python_tips_and_tricks [2018/09/18 17:20] – jay | developer:python_tips_and_tricks [2025/10/23 22:42] (current) – [Execute actions in a Trigger only on certain days of the week] davel17 | ||
|---|---|---|---|
| Line 1: | Line 1: | ||
| + | ====== Python Tips and Tricks ====== | ||
| + | |||
| + | This page is a place to find Python code snippits that do something useful (we'll focus on Python 2.7 since that's what Indigo uses as of Indigo 7). Python is a great language but many of its features are often overlooked. Here's a place to find real-world examples. | ||
| + | |||
| + | The developer section (in which this page resides) is open to anyone who wants to contribute. We've turned back on the self-signup, | ||
| + | ==== Convert List of Integers in a String to Python List of Ints ==== | ||
| + | |||
| + | The [[https:// | ||
| + | |||
| + | < | ||
| + | >>> | ||
| + | [1, 2, 3] | ||
| + | </ | ||
| + | |||
| + | ==== Convert List of Primitives in a String to a Python List of Primitives ==== | ||
| + | |||
| + | As a followup to the previous snippit, you can even supply your own function in place of **int** in the [[https:// | ||
| + | |||
| + | < | ||
| + | # Test for an int | ||
| + | try: | ||
| + | int_value = int(in_string) | ||
| + | return int_value | ||
| + | except: | ||
| + | pass | ||
| + | # Test for a float | ||
| + | try: | ||
| + | float_value = float(in_string) | ||
| + | return float_value | ||
| + | except: | ||
| + | pass | ||
| + | # Test for a boolean | ||
| + | if in_string.lower() in (" | ||
| + | return False | ||
| + | elif in_string.lower() in (" | ||
| + | return True | ||
| + | # If it's not one of the above, then just return the string | ||
| + | return in_string | ||
| + | </ | ||
| + | |||
| + | Then call it from map(): | ||
| + | |||
| + | < | ||
| + | >>> | ||
| + | [False, True, False, 1.0, 3, 'Some Text'] | ||
| + | |||
| + | </ | ||
| + | |||
| + | (I stripped any extra spaces from the beginning and end of each element in this example.) | ||
| + | ==== Different Ways to Test Multiple Flags at Once ==== | ||
| + | |||
| + | < | ||
| + | # flags at once in Python | ||
| + | x, y, z = 0, 1, 0 | ||
| + | |||
| + | if x == 1 or y == 1 or z == 1: | ||
| + | print(' | ||
| + | |||
| + | if 1 in (x, y, z): | ||
| + | print(' | ||
| + | |||
| + | # These only test for truthiness: | ||
| + | if x or y or z: | ||
| + | print(' | ||
| + | |||
| + | if any((x, y, z)): | ||
| + | print(' | ||
| + | </ | ||
| + | |||
| + | Courtesy of [[https:// | ||
| + | |||
| + | ==== Sorting a dictionary ==== | ||
| + | |||
| + | Python dictionaries are inherently unsorted. Sometimes you want to sort them (for instance to iterate in key or value order). Here's the easiest way to do that. By key: | ||
| + | |||
| + | < | ||
| + | >>> | ||
| + | [(' | ||
| + | </ | ||
| + | |||
| + | Or, by value: | ||
| + | |||
| + | < | ||
| + | >>> | ||
| + | [(' | ||
| + | </ | ||
| + | |||
| + | Both return a list of tuples, each one with the first item being the key and the second the value. To iterate: | ||
| + | |||
| + | < | ||
| + | ... print(" | ||
| + | ... | ||
| + | d: 1 | ||
| + | c: 2 | ||
| + | b: 3 | ||
| + | a: 4 | ||
| + | </ | ||
| + | |||
| + | Idea courtesy of [[https:// | ||
| + | ==== Using Ordered Dictionaries ==== | ||
| + | |||
| + | Python dictionaries are inherently unsorted. However, with Python 2.7 (and 3+) there is a new OrderedDict object. It functions exactly like a dict except that it stores the objects in the order in which they were inserted. | ||
| + | |||
| + | In the [[#Sorting a Dictionary]] snippit, we see how to sort a dictionary. However, the results are transient since it's just converting them to a list of tuples. If you need to use the sorted contents of the dictionary multiple times, then you can convert the sorted dict elements in an OrderedDict object once then just use that. | ||
| + | |||
| + | Using the dictionary from the above snippit: | ||
| + | |||
| + | < | ||
| + | >>> | ||
| + | >>> | ||
| + | >>> | ||
| + | </ | ||
| + | |||
| + | After the code above, you can use the two new sorted OrderedDicts exactly the same way you would use any other dict. And if you iterate, you'll notice that you'll see the results ordered correctly: | ||
| + | |||
| + | < | ||
| + | ... print(" | ||
| + | ... | ||
| + | a: 4 | ||
| + | b: 3 | ||
| + | c: 2 | ||
| + | d: 1 | ||
| + | >>> | ||
| + | ... print(" | ||
| + | ... | ||
| + | d: 1 | ||
| + | c: 2 | ||
| + | b: 3 | ||
| + | a: 4 | ||
| + | </ | ||
| + | |||
| + | ==== Pretty Printing Python Collections ==== | ||
| + | |||
| + | Python has the pprint module to assist with printing collection objects (dicts, lists, etc.). Unfortunately, | ||
| + | |||
| + | < | ||
| + | >>> | ||
| + | >>> | ||
| + | {' | ||
| + | |||
| + | # The " | ||
| + | >>> | ||
| + | >>> | ||
| + | { | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | } | ||
| + | |||
| + | # Note this only works with dicts containing | ||
| + | # primitive types (check out the " | ||
| + | >>> | ||
| + | TypeError: keys must be a string | ||
| + | </ | ||
| + | |||
| + | Courtesy of [[https:// | ||
| + | |||
| + | ==== Namedtuples ==== | ||
| + | |||
| + | Named tuples are a sort of short-hand for creating a read-only class: | ||
| + | |||
| + | < | ||
| + | # Using namedtuple is way shorter than | ||
| + | # defining a class manually: | ||
| + | >>> | ||
| + | >>> | ||
| + | |||
| + | # Our new " | ||
| + | >>> | ||
| + | >>> | ||
| + | ' | ||
| + | >>> | ||
| + | 3812.4 | ||
| + | |||
| + | # We get a nice string repr for free: | ||
| + | >>> | ||
| + | Car(color=' | ||
| + | |||
| + | # Like tuples, namedtuples are immutable: | ||
| + | >>> | ||
| + | AttributeError: | ||
| + | </ | ||
| + | |||
| + | One other use-case is if you want to create constants (read-only variables) which is often useful if you're creating a library/ | ||
| + | |||
| + | < | ||
| + | >>> | ||
| + | >>> | ||
| + | >>> | ||
| + | 'value of const1' | ||
| + | >>> | ||
| + | 'value of const2' | ||
| + | >>> | ||
| + | Traceback (most recent call last): | ||
| + | File "< | ||
| + | AttributeError: | ||
| + | </ | ||
| + | |||
| + | (Python convention is to put values that are considered constant in UPPERCASE). | ||
| + | |||
| + | Courtesy of [[https:// | ||
| + | |||
| + | ==== Allowing a method definition to collect an unspecified number of arguments ==== | ||
| + | |||
| + | Python method calls define the parameters that are to be passed in by callers: | ||
| + | |||
| + | < | ||
| + | print(" | ||
| + | </ | ||
| + | |||
| + | When you call it, you must pass in 3 arguments or you get an error: | ||
| + | |||
| + | < | ||
| + | Traceback (most recent call last): | ||
| + | File "< | ||
| + | TypeError: fixed() takes exactly 3 arguments (1 given) | ||
| + | </ | ||
| + | |||
| + | When you pass in the correct number, it works: | ||
| + | |||
| + | < | ||
| + | one: 1, two: 2, three: 3 | ||
| + | </ | ||
| + | |||
| + | You may specify defaults as one way to define optional parameters: | ||
| + | |||
| + | < | ||
| + | print(" | ||
| + | </ | ||
| + | |||
| + | So it works with just one param: | ||
| + | |||
| + | < | ||
| + | one: 1, two: 2, three: 3 | ||
| + | </ | ||
| + | |||
| + | But this still requires that you define the complete possible list of arguments up-front. What if you wanted your method to collect up any additional arguments? For instance, depending on some of the defined parameters, there might be a difference in what other arguments are passed in? | ||
| + | |||
| + | You can do this using *args and //''< | ||
| + | |||
| + | < | ||
| + | def extra_positionals(one, | ||
| + | print(" | ||
| + | print(" | ||
| + | for arg in args: | ||
| + | print(arg) | ||
| + | </ | ||
| + | |||
| + | Will result in this: | ||
| + | |||
| + | < | ||
| + | >>> | ||
| + | one: 1 | ||
| + | the rest of the positional args | ||
| + | 2 | ||
| + | 3 | ||
| + | </ | ||
| + | |||
| + | If you'd rather allow for named parameters, you can do that too: | ||
| + | |||
| + | < | ||
| + | def extra_named(one, | ||
| + | print(" | ||
| + | print(" | ||
| + | for k, v in kwargs.items(): | ||
| + | print(" | ||
| + | </ | ||
| + | |||
| + | Call it with any number of named args: | ||
| + | |||
| + | < | ||
| + | >>> | ||
| + | one: 1 | ||
| + | the rest of the named args | ||
| + | two: 2 | ||
| + | three: 3 | ||
| + | </ | ||
| + | |||
| + | These options are often useful if you're building a library that others will use. Rather than defining a lot of similarly named methods, you can just define one and then use the fixed params to determine what the rest of the params should be. | ||
| + | |||
| + | ==== Execute actions in a Trigger only on certain days of the week ==== | ||
| + | |||
| + | A new user recently asked how to execute actions in a Trigger only if it's either a weekday or a weekend day. Here's a simple script that illustrates how to do that in a Python script action: | ||
| + | |||
| + | < | ||
| + | |||
| + | # Days in Python go from Monday (0) through Sunday (6) | ||
| + | WEEKDAY = [0, 1, 2, 3, 4] # this is a list of weekdays | ||
| + | WEEKEND = [5, 6] # this is a list of weekend days | ||
| + | |||
| + | # Here's an example of executing actions only on a weekday | ||
| + | if date.today().weekday() in WEEKDAY: | ||
| + | # It's a week day, so execute actions here | ||
| + | # You could directly execute actions if they' | ||
| + | indigo.device.turnOn(12345) | ||
| + | # Or, if you have complicated actions, you would create an Action Group | ||
| + | # with all the actions in it and execute the group from here: | ||
| + | indigo.actionGroup.execute(67890) # 67890 is the ID of the Action Group | ||
| + | |||
| + | # Here's an example of executing actions only on a weekend | ||
| + | if date.today().weekday() in WEEKEND: | ||
| + | # It's a weekend day, so execute actions here | ||
| + | # You could directly execute actions if they' | ||
| + | indigo.device.turnOn(12345) | ||
| + | # Or, if you have complicated actions, you would create an Action Group | ||
| + | # with all the actions in it and execute the group from here: | ||
| + | indigo.actionGroup.execute(67890) # 67890 is the ID of the Action Group | ||
| + | </ | ||
| + | |||
| + | Obviously, this could be easily made more specific for certain days of the week, etc. | ||