Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pipe and **kwargs #510

Open
dilzeem opened this issue Feb 8, 2021 · 4 comments
Open

pipe and **kwargs #510

dilzeem opened this issue Feb 8, 2021 · 4 comments

Comments

@dilzeem
Copy link

dilzeem commented Feb 8, 2021

Hi,

I am not sure if this something that should be done, or if this bad practice. But I wanted something like this to work.

import toolz

some_dict = {"hello": "world", "foo": "bar", "number": 0}

def first_func(hello, foo, **kwargs):
    hello = "John"
    foo = "baz"
    kwargs['newvalue'] = True
    return kwargs


def second_func(number, **kwargs):
    number = number + 1
    kwargs['number'] = number
    kwargs['anothervalue'] = False
    return kwargs


running something like this works:

first_func(**some_dict)
>>> 
{'number': 0, 'newvalue': True}
second_func(**some_dict)
>>> 
{'number': 1, 'anothervalue': False}

But this won't work, which I understand why.

new_dict = toolz.pipe(**some_dict, first_func, second_func)

The idea is that I want some_dict to remain untouched, but new_dict to capture relevant information along the way.

Is this bad practice? Or not a relevant usecase? or is there a better way to do this?

@jtwool
Copy link

jtwool commented Feb 9, 2021

I like this; I'd create a new class to handle this because you only want it to work on the one object.

from copy import copy 

class PipeableDict(dict):
    
    def pipe(self, fn):
        _copy = copy(self)
        fn(_copy)
        return _copy
    
    def pipeline(self, *fns):
        _copy = copy(self)
        for fn in fns:
            _copy = _copy.pipe(fn)
        return _copy

if __name__ == "__main__":
    
    d = PipeableDict({"foo":"abc",
                      "bar": 123})

    e = d.pipe(lambda x:x.update(buzz=3))\
         .pipe(lambda x:x.update(name="John Doe"))\
         .pipe(lambda x:x.update(foo="ABC"))

    print("Pipe")
    print(d)
    print(e)

    f = d.pipeline(
        lambda x:x.update(buzz=3),
        lambda x:x.update(name="John Doe"),
        lambda x:x.update(foo="ABC")
    )
    
    print("Pipeline")
    print(d)
    print(f)      

Probably needs a better name. Maybe ImmutablePipeableDict?

@dilzeem
Copy link
Author

dilzeem commented Feb 9, 2021

Thanks for the comment. It helped look into this further, and get something that I am okay with for the time being.

I think I found what I was looking for with a minor tweak another issue/feature request.
Using the starapply mentioned here: #486

I made a minor tweak, where only key word arguments are allowed.

@curry
def unpack(func, kwargs):
    return func(**kwargs)

new_dict = toolz.pipe(some_dict, unpack(first_func), unpack(second_func))

>>>new_dict
{'newvalue': True, 'number': 1, 'anothervalue': False}

>>>some_dict
{"hello": "world", "foo": "bar", "number": 0}

some_dict remains unchanged.

This is pretty useful for me in running pipelines and tracking what arguments were used. Though the function definitions need to have the **kwargs defined.

And if you decide to use the variable, then it gets removed from **kwargs , but I guess it is nice to explicitly push the required values back into **kwargs.

@mrucci
Copy link

mrucci commented Feb 12, 2021

Is this bad practice? Or not a relevant usecase? or is there a better way to do this?

I would personally find the behaviour pretty obscure and would expect the resulting implementation to be brittle since it depends on the number and order of argument in each function in the pipe.

I've never used it before but the problem you are solving reminds me of passports, where the basic idea is that you pass around an object (the passport) and each function in the pipe can stamp it with the relevant information.

Explicit is better than implicit.

@dilzeem
Copy link
Author

dilzeem commented Feb 12, 2021

thanks for the comment and the link. I will check it out. it's nice to get some feedback here.

to clarify the order arguments don't need to be in a certain order for this solution. they just have to present in the dictionary passed to the function.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants