Python Tricks - Dictionary Tricks(2)

Emulating Switch/Case Statements With Dicts

用字典仿制一个switch+case语句的写法
Python doesn’t have switch/case statements so it’s sometimes necessary to write long if...elif...else chains as a workaround. In this chapter you’ll discover a trick you can use to emulate switch/case statements in Python with dictionaries and first-class functions. Sound exciting? Great—here we go!

Imagine we had the following if-chain in our program:

>>> if cond == 'cond_a': 
...   handle_a()
... elif cond == 'cond_b': 
...   handle_b()
... else:
...   handle_default()

Of course, with only three different conditions, this isn’t too horrible yet. But just imagine if we had ten or more elif branches in this statement. Things would start to look a little different. I consider long if- chains to be a code smell that makes programs more difficult to read and maintain.

One way to deal with long if...elif...else statements is to replace them with dictionary lookup tables that emulate the behavior of switch/case statements.

The idea here is to leverage the fact that Python has first-class functions. This means they can be passed as arguments to other functions, returned as values from other functions, and assigned to variables and stored in data structures.
Function as the first citizen It can be passed to another function, the return value from the other functions, and the value assigned to the variable, stored in the data structure.

For example, we can define a function and then store it in a list for later access:

>>> def myfunc(a, b):
...   return a + b
...
>>> funcs = [myfunc]
>>> funcs[0]
<function myfunc at 0x107012230>

The syntax for calling this function works as you’d intuitively expect— we simply use an index into the list and then use the “()” call syntax for calling the function and passing arguments to it:

 >>> funcs[0](2, 3) 5

Now, how are we going to use first-class functions to cut our chained if-statement back to size? The core idea here is to define a dictionary that maps lookup keys for the input conditions to functions that will carry out the intended operations:

>>> func_dict = {
...     'cond_a': handle_a,
...     'cond_b': handle_b
... }

This is indeed a good idea.

Instead of filtering through the if-statement, checking each condition as we go along, we can do a dictionary key lookup to get the handler function and then call it:

>>> cond = 'cond_a' 
>>> func_dict[cond]()

This implementation already sort-of works, at least as long as cond can be found in the dictionary. If it’s not in there, we’ll get a KeyError exception.

So let’s look for a way to support a default case that would match the original else branch. Luckily all Python dicts have a get() method on them that returns the value for a given key, or a default value if the key can’t be found. This is exactly what we need here:

>>> func_dict.get(cond, handle_default)()

This code snippet might look syntactically odd at first, but when you break it down, it works exactly like the earlier example. Again, we’re using Python’s first-class functions to pass handle_default to the get()-lookup as a fallback value. That way, if the condition can’t be found in the dictionary, we avoid raising a KeyError and call the default handler function instead.

Let’s take a look at a more complete example for using dictionary lookups and first-class functions to replace if-chains. After reading through the following example, you’ll be able to see the pattern needed to transform certain kinds of if-statements to a dictionary-based dispatch.

We’re going to write another function with an if-chain that we’ll then transform. The function takes a string opcode like "add" or "mul" and then does some math on the operands x and y:

>>> def dispatch_if(operator, x, y):
      if operator == 'add': 
        return x + y
      elif operator == 'sub': 
        return x - y
      elif operator == 'mul': 
        return x * y
      elif operator == 'div': 
        return x / y

To be honest, this is yet another toy example (I don’t want to bore you with pages and pages of code here), but it’ll serve well to illustrate the underlying design pattern. Once you “get” the pattern, you’ll be able to apply it in all kinds of different scenarios.

You can try out this dispatch_if() function to perform simple calculations by calling the function with a string opcode and two numeric operands:

>>> dispatch_if('mul', 2, 8)
16
>>> dispatch_if('unknown', 2, 8) 
None

Please note that the 'unknown' case works because Python adds an implicit return None statement to the end of any function.

So far so good. Let’s transform the original dispatch_if() into a new function which uses a dictionary to map opcodes to arithmetic operations with first-class functions.

>>> def dispatch_dict(operator, x, y):
      return {
        'add': lambda: x + y, 
        'sub': lambda: x - y, 
        'mul': lambda: x * y, 
        'div': lambda: x / y,
}.get(operator, lambda: None)()

The above written really beautiful

This dictionary-based implementation gives the same results as the original dispatch_if(). We can call both functions in exactly the same way:

>>> dispatch_dict('mul', 2, 8)
16
>>> dispatch_dict('unknown', 2, 8) 
None

There are a couple of ways this code could be further improved if it was real "production-grade" code.
But there are still many areas for improvement
First of all, every time we call dispatch_dict (), it creates a temporary dictionary and a bunch of lambdas for the opcode lookup . This is not ideal from a performance perspective. for code that needs to be fast, it makes more sense to create the dictionary once as a constant and then to reference it when the function is called. we do not want to recreate the dictionary every time we need to do a lookup.
every time we call the dispatch function when we are all created temporary dictionary and a series of lambda expressions, from a performance perspective is not ideal. We might as well put a quantitative value to create the dictionary, then every time you use the time went cite.

Second, if we really wanted to do some simple arithmetic like x + y, then we'd be better off using Python's built-in operator module instead of the lambda functions used in the example. The operator module provides implementations for all of Python's operators , for example operator.mul, operator.div, and so on. This is a minor point, though. I intentionally used lambdas in this example to make it more generic. This should help you apply the pattern in other situations as well.
we If you need to use an expression such as x + y our best to use some method of operation inside the built-in library, such as the operator of the add mul div method and so on.

Well, now you’ve got another tool in your bag of tricks that you can use to simplify some of your if-chains should they get unwieldy. Just remember—this technique won’t apply in every situation and some- times you’ll be better off with a plain if-statement.

Key Takeaways
  • Python doesn’t have a switch/case statement. But in some cases you can avoid long if-chains with a dictionary-based dispatch table.
  • Once again Python’s first-class functions prove to be a powerful tool. But with great power comes great responsibility.

Reproduced in: https: //www.jianshu.com/p/71babe046db1

Guess you like

Origin blog.csdn.net/weixin_34062469/article/details/91186828