GithubHelp home page GithubHelp logo

python-switch's Introduction

switchlang

Adds switch blocks to the Python language.

This module adds explicit switch functionality to Python without changing the language. It builds upon a standard way to define execution blocks: the with statement.

Example

from switchlang import switch

def main():
    num = 7
    val = input("Enter a character, a, b, c or any other: ")

    with switch(val) as s:
        s.case('a', process_a)
        s.case('b', lambda: process_with_data(val, num, 'other values still'))
        s.default(process_any)
    
def process_a():
    print("Found A!")
    
def process_any():
    print("Found Default!")
    
def process_with_data(*value):
    print("Found with data: {}".format(value))

main()

Installation

Simply install via pip:

pip install switchlang

Features

  • More explicit than using dictionaries with functions as values.
  • Verifies the signatures of the methods
  • Supports default case
  • Checks for duplicate keys / cases
  • Keys can be anything hashable (numbers, strings, objects, etc.)
  • Could be extended for "fall-through" cases (doesn't yet)
  • Use range and list for multiple cases mapped to a single action

Multiple cases, one action

You can map ranges and lists of cases to a single action as follows:

# with lists:
value = 4  # matches even number case

with switch(value) as s:
    s.case([1, 3, 5, 7], lambda: ...)
    s.case([0, 2, 4, 6, 8], lambda: ...)
    s.default(lambda: ...)
# with ranges:
value = 4  # matches first case

with switch(value) as s:
    s.case(range(1, 6), lambda: ...)
    s.case(range(6, 10), lambda: ...)
    s.default(lambda: ...)

Closed vs. Open ranges

Looking at the above code it's a bit weird that 6 appears at the end of one case, beginning of the next. But range() is half open/closed.

To handle the inclusive case, I've added closed_range(start, stop). For example, closed_range(1,5) -> [1,2,3,4,5]

Why not just raw dict?

The biggest push back on this idea is that we already have this problem solved. You write the following code.

switch = {
    1: method_on_one,
    2: method_on_two,
    3: method_three
}

result = switch.get(value, default_method_to_run)()

This works but is very low on the functionality level. We have a better solution here I believe. Let's take this example and see how it looks in python-switch vs raw dicts:

# with python-switch:

while True:
    action = get_action(action)

    with switch(action) as s:
        s.case(['c', 'a'], create_account)
        s.case('l', log_into_account)
        s.case('r', register_cage)
        s.case('u', update_availability)
        s.case(['v', 'b'], view_bookings)
        s.case('x', exit_app)
        s.case('', lambda: None)
        s.case(range(1,6), lambda: set_level(action))
        s.default(unknown_command)
    
    print('Result is {}'.format(s.result))

Now compare that to the espoused pythonic way:

# with raw dicts

while True:
    action = get_action(action)

    switch = {
        'c': create_account,
        'a': create_account,
        'l': log_into_account,
        'r': register_cage,
        'u': update_availability,
        'v': view_bookings,
        'b': view_bookings,
        'x': exit_app,
        1: lambda: set_level(action),
        2: lambda: set_level(action),
        3: lambda: set_level(action),
        4: lambda: set_level(action),
        5: lambda: set_level(action),
        '': lambda: None,
    }
    result = switch.get(action, unknown_command)()
    print('Result is {}'.format(result))

Personally, I much prefer to read and write the one above. That's why I wrote this module. It seems to convey the intent of switch way more than the dict. But either are options.

Why not just if / elif / else?

The another push back on this idea is that we already have this problem solved. Switch statements are really if / elif / else blocks. So you write the following code.

# with if / elif / else

while True:
    action = get_action(action)

    if action == 'c' or action == 'a':
        result = create_account()
    elif action == 'l':
        result = log_into_account()
    elif action == 'r':
        result = register_cage()
    elif action == 'a':
        result = update_availability()
    elif action == 'v' or action == 'b':
        result = view_bookings()
    elif action == 'x':
        result = exit_app()
    elif action in {1, 2, 3, 4, 5}:
        result = set_level(action)
    else:
        unknown_command()
        
    print('Result is {}'.format(result))

I actually believe this is a little better than the raw dict option. But there are still things that are harder.

  • How would you deal with fall-through cleanly?
  • Did you notice the bug? We forgot to set result in default case (else) and will result in a runtime error (but only if that case hits).
  • There is another bug. Update update_availability will never run because it's command (a) is bound to two cases. This is guarded against in switch and you would receive a duplicate case error the first time it runs at all.
  • While it's pretty clear, it's much more verbose and less declarative than the switch version.

Again, compare the if / elif / else to what you have with switch. This code is identical except doesn't have the default case bug.

while True:
    action = get_action(action)

    with switch(action) as s:
        s.case(['c', 'a'], create_account)
        s.case('l', log_into_account)
        s.case('r', register_cage)
        s.case('u', update_availability)
        s.case(['v', 'b'], view_bookings)
        s.case('x', exit_app)
        s.case('', lambda: None)
        s.case(range(1,6), lambda: set_level(action))
        s.default(unknown_command)
    
    print('Result is {}'.format(s.result))

python-switch's People

Contributors

j-dunham avatar mikeckennedy avatar zachvalenta avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

python-switch's Issues

Idea: decorator to inline function definitions

I stumbled upon this via Github's new "discover repos" feature, interesting project!

Having s.case capable of working as decorators would allow you to not have to define functions separately from their case. Here's the example from the readme written this way using functools.partial to handle that s.case wasn't meant to be used this way:

from functools import partial                                                   
from switchlang import switch                                                   
                                                                                
num = 7                                                                         
val = input("Enter a key. a, b, c or any other: ")                              
                                                                                
with switch(val) as s:                                                          
    @partial(s.case, 'a')                                                       
    def process_a():                                                            
        print("Found A!")                                                       
    @partial(s.case, 'b')                                                       
    def process_with_data():                                              
        print("Found with data: {}".format((val, num, 'other values still')))   
    @s.default                                                                  
    def process_any():                                                          
        print("Found Default!")                                                 

Duplication of effort

Far be it from me to tell you how to spend your time ;), but a cursory search revealed a 4 year old project that does almost exactly the same thing: https://pypi.python.org/pypi/switch/1.1.0 Did you try contacting the author to see if you could contribute to that, or take over maintainership?

Post to PyPI

Looks like this is stable enough and has received enough feedback that I'm ready to start thinking about posting it to PyPI.

Hold tight for pip install switchlang

import error

Hi, I encountered the problem when I try to from switchlang import switch:

>>> from switchlang import switch
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "switchlang.py", line 22
    def default(self, func: Callable[[], Any]):
                          ^
SyntaxError: invalid syntax

I am using python2.7.13.

Any ideas? Thx~

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.