• Sem
    link
    fedilink
    English
    610 months ago

    To be honest, for me it looks as very strange pattern… If one wants to have static container with functions, there is Enums or classes.

    • @Walnut356
      link
      210 months ago

      It’s most useful when you’re using some data you already have as the dictionary key. A usecase i had for this was a binary file parser with 10 types of event markers. It was originally coded with if/elif, but performance was a pretty big consideration. Using the event markers as keys to a dictionary dispatch improved performance by about 15% and made the code significantly more readable.

    • @[email protected]
      cake
      link
      fedilink
      110 months ago

      With classes you needs getattr in python to achieve the same. This is cleaner for simple mappings

  • voxel
    link
    fedilink
    4
    edit-2
    10 months ago

    not unique to python.

    function pointers and hashmaps exist in basically all languages

    like in lua:

    local table = {
      add = function(a, b) return a + b end
    }
    table["add"](4, 5)
    

    JavaScript:

    {
      add: (a, b) => a + b
    }
    

    Rust (using hashmaps with string keys is extremely inefficient tho, there are much better options here but whatever)

    let map = HashMap::from([
       ("add", |a, b| a + b),
    ]);
    
    //...
    
    map["add"](1, 3);
    
    
    • @[email protected]
      link
      fedilink
      010 months ago

      Match only just came out in Python 3.10, so it doesn’t universally work. I think you implied that in your comment, so I wanted to add context for others.

      • @[email protected]
        link
        fedilink
        210 months ago

        Oh, yes. I’m excited for the arrival of match. I’ve used it in rust and it’s very nifty and Pythonic

  • @sirdorius
    link
    210 months ago

    This is fun to play around and basically what Python does under the hood to implement classes. In Python2 it was even more obvious that classes are just fancy wrappers around a dict called, unsurprisingly, __dict__.

    class Foo: 
        def __init__(self):
            self.__dict__["instance_method"] = lambda: "instance_method"
            self.__dict__["shadowed_class_method"] = lambda: "shadowed_class_method_from_instance"
            
    
    Foo.__dict__["class_method"] = lambda cls: "class_method"
    Foo.__dict__["shadowed_class_method"] = lambda cls: "shadowed_class_method_from_class"
    
    f = Foo()
    f.__dict__["dynamic_instance_method"] = lambda: "dynamic_instance_method"
    
    print f.instance_method()
    print f.dynamic_instance_method()
    print f.class_method()
    print f.shadowed_class_method()
    
    OUTPUT:
    instance_method
    dynamic_instance_method
    class_method
    shadowed_class_method_from_instance
    

    Note: this won’t work in Python3 because the class.__dict__ becomes immutable at some point after declaring it, but the attribute name resolution stays the same. And it gets more interesting once you throw inheritance into the mix.

  • @[email protected]
    link
    fedilink
    210 months ago

    Broadly, this is a simple version of the Strategy Pattern, which is incredibly useful for making flexible software.

    In Python, the example given is basically the classic bodge attempt to emulate switch-case statements.

  • Zeusbottom
    link
    fedilink
    English
    2
    edit-2
    10 months ago

    My favorite way to implement this is with decorators. I used this to make a dispatch table for reading objects from a MySQL database.

    (Yes I know I should be using UserDict below. You should too and don’t subclass dict like I did.)

    class FuncRegistry(dict):
        """Creates a registry of hashable objects to function mappings."""
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
    
        def register_for(self, key: Hashable) -> Callable:
            """Decorator to register functions in the registry.
    
            Parameters
    
            key: Hashable
                The key which should point to this function
    
            Returns: Callable
                Returns a decorator that registers the function to the key"""
            def decorator(fn: Callable) -> Callable:
                self[key] = fn
                return fn
            return decorator
    
    qreg = FuncRegistry()
    
    @qreg.register_for('foobr')
    def handle_foobr(arg1, arg2):
        # do something here then
        return
    
    qreg['foobr']('ooo its an arg', 'oh look another arg')
    

    edit: formatting