Secretly passing parameter to function

Discussion in 'Python' started by Olivier Scalbert, Dec 5, 2012.

  1. Hi all !

    I have a problem that is not easy to explained, so I have tried to
    reduce it a lot.

    We are using a framework, that we can not modify.

    in framework.py:
    def do(something):
    '''
    Here we are in a framework that can not be modified ...
    It does a lot of things

    and finally:
    '''
    something()

    in test.py:
    from framework import *

    def step1():
    print "Do step1"

    def step2():
    print "Do step2"


    # We ask the framework to do some work.
    do(step1)
    do(step2)
    do(step3)


    We are writing step1, step2, ... and asking the framework to process them.
    Everything is ok, until we want to add a parameter to some steps.
    We want to be able to do that:

    in test.py:
    from framework import *

    def step1(param):
    print "Do step1 with param"

    def step2():
    print "Do step2"


    # We ask the framework to do some work.

    do(step1, param = None)
    do(step1, param = [0, 1, 5]) # again
    do(step2)

    Of course it does not work ...
    TypeError: do() takes exactly 1 argument (2 given)

    And we can not modify the framework (in which "do" is defined.

    One solution would be to use a global variable that can be set before
    each step. But it is not very elegant ...

    One other approach would be to add dynamically an attribute the the
    step1 function, and retrieve it inside the function, but it is perhaps
    overkill.

    Do you have some ideas ?

    Thanks,


    Olivier
    Olivier Scalbert, Dec 5, 2012
    #1
    1. Advertising

  2. Olivier Scalbert

    Chris Kaynor Guest

    On Wed, Dec 5, 2012 at 10:50 AM, Olivier Scalbert <
    > wrote:

    > Hi all !
    >
    > I have a problem that is not easy to explained, so I have tried to reduce
    > it a lot.
    >
    > We are using a framework, that we can not modify.
    >
    > in framework.py:
    > def do(something):
    > '''
    > Here we are in a framework that can not be modified ...
    > It does a lot of things
    >
    > and finally:
    > '''
    > something()
    >
    > in test.py:
    > from framework import *
    >
    > def step1():
    > print "Do step1"
    >
    > def step2():
    > print "Do step2"
    >
    >
    > # We ask the framework to do some work.
    > do(step1)
    > do(step2)
    > do(step3)
    >
    >
    > We are writing step1, step2, ... and asking the framework to process them.
    > Everything is ok, until we want to add a parameter to some steps.
    > We want to be able to do that:
    >
    > in test.py:
    > from framework import *
    >
    > def step1(param):
    > print "Do step1 with param"
    >
    > def step2():
    > print "Do step2"
    >
    >
    > # We ask the framework to do some work.
    >
    > do(step1, param = None)
    > do(step1, param = [0, 1, 5]) # again
    > do(step2)
    >
    > Probably the easiest solution would be to use functools.partial to create

    a proxy function, as follows:

    import functools
    do(functools.partial(step1, param=None))
    do(functools.partial(step1, param=[0,1,5]))
    do(step2)

    Effectively what functools.partial does is returns a new function with the
    arguments defined in the constructor. It looks something like:

    def partial(func, *args, **kwargs):
    def newFunc(*cargs, **ckwargs):
    return func(*args+cargs, **kwargs+ckwargs)
    return newFunc

    Note, that implementation of partial may not exactly match the semantics of
    the real thing, and is untested and may just flat out not work...I'd
    recommend using the real one instead.

    In this case, it will produce a function that really takes no additional
    arguments, as all arguments are defined as part of the creation, meaning
    the new function will match the requirements by the framework of taking 0
    arguments, despite the actual function taking and receiving one keyword
    argument (param).

    There is one caveat of using functools.partial: for positional arguments,
    you cal only fill the arguments in left-to-right order; you cannot specify
    the second argument of a list, other than specifying it by name. For
    example, for the function "def test(a, b, c)", you can specify arguments
    "a", "a and b", or "a and b and c" by position, but you cannot specify only
    the arguments "b", "c", "a and c", or "b and c" by position. You can,
    however, specify them by name, as keyword-arguments.



    An alternative approach to the problem is to either use lamdas or write a
    specialized wrapper like my example partial above to specify the arguments.
    These eliminate the restriction on argument order for partial.

    An example for lamdas would be:
    do(lambda: step1(param=None))
    do(lambda: step1(param=[0,1,5]))
    do(step2)

    This works much the same way as partial, in that it creates a new, unnamed
    function which specifies the arguments.



    > Of course it does not work ...
    > TypeError: do() takes exactly 1 argument (2 given)
    >
    > And we can not modify the framework (in which "do" is defined.
    >
    > One solution would be to use a global variable that can be set before each
    > step. But it is not very elegant ...
    >
    > One other approach would be to add dynamically an attribute the the step1
    > function, and retrieve it inside the function, but it is perhaps overkill.
    >
    > Do you have some ideas ?
    >
    > Thanks,
    >
    >
    > Olivier
    >
    > --
    > http://mail.python.org/**mailman/listinfo/python-list<http://mail.python.org/mailman/listinfo/python-list>
    >
    Chris Kaynor, Dec 5, 2012
    #2
    1. Advertising

  3. Olivier Scalbert

    Dave Angel Guest

    On 12/05/2012 01:50 PM, Olivier Scalbert wrote:
    > Hi all !
    >
    > I have a problem that is not easy to explained, so I have tried to
    > reduce it a lot.
    >
    > We are using a framework, that we can not modify.
    >
    > in framework.py:
    > def do(something):
    > '''
    > Here we are in a framework that can not be modified ...
    > It does a lot of things
    >
    > and finally:
    > '''
    > something()
    >
    > in test.py:
    > from framework import *
    >
    > def step1():
    > print "Do step1"
    >
    > def step2():
    > print "Do step2"
    >
    >
    > # We ask the framework to do some work.
    > do(step1)
    > do(step2)
    > do(step3)
    >
    >
    > We are writing step1, step2, ... and asking the framework to process
    > them.
    > Everything is ok, until we want to add a parameter to some steps.
    > We want to be able to do that:
    >
    > in test.py:
    > from framework import *
    >
    > def step1(param):
    > print "Do step1 with param"
    >
    > def step2():
    > print "Do step2"
    >
    >
    > # We ask the framework to do some work.
    >
    > do(step1, param = None)
    > do(step1, param = [0, 1, 5]) # again
    > do(step2)
    >
    > Of course it does not work ...
    > TypeError: do() takes exactly 1 argument (2 given)
    >
    > And we can not modify the framework (in which "do" is defined.
    >
    > One solution would be to use a global variable that can be set before
    > each step. But it is not very elegant ...
    >
    > One other approach would be to add dynamically an attribute the the
    > step1 function, and retrieve it inside the function, but it is perhaps
    > overkill.
    >
    > Do you have some ideas ?
    >

    Other approaches are lamba, default-argument trick, a function closure,
    a callable class instance, and functools.partial.

    The real question you have to ask is what is the scope AND LIFETIME of
    this parameter. Suppose you want to want to have five of these same
    calls, with five different parameters? (Example, a GUI where you have a
    single function which might be called on an event of any of five buttons
    -- you want to pass the button-object to the function)


    import functools

    def step1(param):
    print "Do step1 with param", param

    def step2():
    print "Do step2"

    class Framework:
    def __init__(self):
    self.pending = []
    def do(self, func):
    print "current", self.pending
    self.pending.append(func)
    def flush(self):
    for func in self.pending:
    func()


    frame = Framework()
    frame.do(step2)
    frame.do(step2)
    frame.do(step2)
    frame.do(functools.partial(step1, 45))

    frame.flush()



    --

    DaveA
    Dave Angel, Dec 5, 2012
    #3
  4. Olivier Scalbert

    Modulok Guest

    > Hi all !
    >
    > I have a problem that is not easy to explained, so I have tried to
    > reduce it a lot.
    >
    > We are using a framework, that we can not modify.
    >
    > in framework.py:
    > def do(something):
    > '''
    > Here we are in a framework that can not be modified ...
    > It does a lot of things
    >
    > and finally:
    > '''
    > something()
    >
    > in test.py:
    > from framework import *
    >
    > def step1():
    > print "Do step1"
    >
    > def step2():
    > print "Do step2"
    >
    >
    > # We ask the framework to do some work.
    > do(step1)
    > do(step2)
    > do(step3)
    >
    >
    > We are writing step1, step2, ... and asking the framework to process them.
    > Everything is ok, until we want to add a parameter to some steps.
    > We want to be able to do that:
    >
    > in test.py:
    > from framework import *
    >
    > def step1(param):
    > print "Do step1 with param"
    >
    > def step2():
    > print "Do step2"
    >
    >
    > # We ask the framework to do some work.
    >
    > do(step1, param = None)
    > do(step1, param = [0, 1, 5]) # again
    > do(step2)
    >
    > Of course it does not work ...
    > TypeError: do() takes exactly 1 argument (2 given)
    >
    > And we can not modify the framework (in which "do" is defined.
    >
    > One solution would be to use a global variable that can be set before
    > each step. But it is not very elegant ...
    >
    > One other approach would be to add dynamically an attribute the the
    > step1 function, and retrieve it inside the function, but it is perhaps
    > overkill.
    >
    > Do you have some ideas ?


    Olivier,

    I would create a partial object using the functools module, but I would also
    wrap it in a decorator so I could call my functions as usual. Here's an
    example:


    # File: framework.py:
    def do(something):
    print("Framework in action...")
    return something()



    # File: test.py:
    import functools
    import framework

    def pack(func):
    """Return a function object to be called later."""
    def f(*args, **kwargs):
    """Call the framework passing a partial object to be called."""
    print("Wrapper in action...")
    part = functools.partial(func, *args, **kwargs)
    return framework.do(part) #<-- Call the simplified function.
    return f #<-- Return the function object to-be-called.


    # Usage: Just wrap your defs with the decorator '@pack':
    @pack
    def step1(x, y):
    print(x, y)

    @pack
    def step2(a):
    return sum(a)

    @pack
    def step3():
    print("Amazing!")


    # Call your functions as usual e.g: step1(3, 5)...


    In theory everything should just work. I tested the above example and it seemed
    to work just fine with my limited testing.

    Good luck!
    -Modulok-
    Modulok, Dec 6, 2012
    #4
  5. Olivier Scalbert

    Paul Rubin Guest

    Olivier Scalbert <> writes:
    > # We ask the framework to do some work.
    > do(step1, param = None)


    from functools import partial
    do(partial(step1, param = None))
    Paul Rubin, Dec 6, 2012
    #5
  6. Olivier Scalbert

    Paul Rubin Guest

    Paul Rubin <> writes:
    > from functools import partial
    > do(partial(step1, param = None))


    Or more directly:

    do(lambda: step1(param = None))
    Paul Rubin, Dec 6, 2012
    #6
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. alf
    Replies:
    2
    Views:
    17,867
    azoozey
    Apr 1, 2011
  2. Newsgroup - Ann
    Replies:
    5
    Views:
    587
    John Carson
    Jul 30, 2003
  3. Azdo
    Replies:
    2
    Views:
    425
  4. Mister B
    Replies:
    8
    Views:
    526
    Nick Keighley
    Aug 26, 2010
  5. AzamSharp
    Replies:
    2
    Views:
    157
Loading...

Share This Page