Skip to main content

Adds switch blocks to the Python language.

Project description

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

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))

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))

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

switchlang-0.1.0.tar.gz (5.6 kB view details)

Uploaded Source

Built Distribution

switchlang-0.1.0-py3-none-any.whl (6.3 kB view details)

Uploaded Python 3

File details

Details for the file switchlang-0.1.0.tar.gz.

File metadata

  • Download URL: switchlang-0.1.0.tar.gz
  • Upload date:
  • Size: 5.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.1.1 pkginfo/1.5.0.1 requests/2.23.0 setuptools/40.6.2 requests-toolbelt/0.9.1 tqdm/4.43.0 CPython/3.7.7

File hashes

Hashes for switchlang-0.1.0.tar.gz
Algorithm Hash digest
SHA256 9d19f7c58358edd08c7919f72bbb01bc0854570af6a39a9d94651a893ec01a82
MD5 d58720757ae064c1c802a4dcc8e44b2d
BLAKE2b-256 50709942bda4d515eaf0f4fe92200c070d92968d23a3c01f3dd4b1c63ddbdeb3

See more details on using hashes here.

File details

Details for the file switchlang-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: switchlang-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 6.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.1.1 pkginfo/1.5.0.1 requests/2.23.0 setuptools/40.6.2 requests-toolbelt/0.9.1 tqdm/4.43.0 CPython/3.7.7

File hashes

Hashes for switchlang-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 bb440c1e865bac15ad4230902c6bd4f4d737c0ec7ac5496c62d2028122af4bea
MD5 753e7776887c2ab46a7e4fb6f1d5f5d4
BLAKE2b-256 06c47c3745fcd6196939e8a0b9fab8c7d09e276b12e707190fff5599d10e6565

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page