Anonymous Functions (MATLAB) vs Lambdas (Python) Anonymous Functions in MATLAB is closure while Lambdas in Python are not

Lambdas in Python does not play by the same rules as anonymous functions in MATLAB

  • MATLAB takes a snapshot of (capture) the workspace variables involved in the anonymous function AT the time the anonymous function handle is created, thus the captured values will live on afterwards (by definition a proper closure).
  • Lambda is Python is NOT closure! The local variables involved in the expression are simply read on-the-fly (aka, the last state) when the functor is executed.

It’s kind of a mixed love-and-hate situation for both. Either design choice will be confusing for some use cases. I was at first thrown off by MATLAB’s closure, then after I get used to it (how else are you going to curry?), Python’s Lambda’s non-closure tripped me. Even in the official FAQ, it address the surprise that people are not getting what they expected creating lambdas in a for-loop.

To enable capture in Python, you assign the value you wanted to capture to a lambda argument (intermediary), then use the intermediary in the expression.

lambda: ser.close()      # does not capture 'ser'
lambda s=ser: s.close()  # 'ser' is captured by s. NOTE: still not closure!

I usually keep the usage of nested functions to the minimum, even in MATLAB, because effectively it’s kind of a compromised ‘global’ between nested levels, or a little bit like protected classes in C++. It breaks encapsulation (intentionally) for functions in your inner circle (nest).

It’s often useful for coding up GUI in MATLAB quick because you need to share access to the UI controls within the same group. For GUI that gets more complicated, I actually avoided nested functions altogether and used *appdata() to share UI object handles.

Functors of nested functions are closures in both MATLAB and Python! Only Lambdas in Python behave slightly differently.

54 total views, no views today

Handling resources that needs to be cleaned up in Python and MATLAB Files, sockets, ports, etc.

Using try/catch to handle resource (files, ports, etc) cleanup is out of fashion in both MATLAB and Python. In MATLAB, it uses a very slick idea based on closures to let you sneak your cleanup function (as a function object) into an onCleanup object that will be executed (in destructor) when that object is cleaned up by being out of scope.

Python does not provide the same mechanism. Instead, it relies on the resource class (like file IO or PySerial) to implement as a Context Manager (has __enter__) and provide the cleanup in the manager’s __exit__ method. Then you use the with keyword with the returned resource object put after as keyword, like this:

with File('test.txt', 'w') as f:
    f.write('SPFCCsMfT!')

The body of with-block will not run (and therefore object f won’t be created) if the with-statement throws an exception. Unfortunately, it’s a fill-or-kill (or try/finally) instead of try/catch. So if the resource failed to open, resource object f is simply not created. No other clue is generated. This is what I hate about the with-statement. There are two ways to kind of get around it, but they are not reliable and might cause other bugs if you don’t keep track of the variable names in the local context:

  1. Check for the existence of the resource object
    if 'f' in dir(): del f   # Avoid name conflicts
    with File() as f
        print("Done");
    if not 'f' in dir():
        print('Cannot create file');
  2. Use the body code to indicate that the with-block is executed
    isSuccess = false     # Signaling
    with File() as f
        isSuccess = true
        print("Done')
    if not isSuccess
        print("Cannot create file");
    
    
  3. Back to the old way
    try:
        f = File();
    except:
        print("Cannot open file")
    else:
        cleanup_obj = onCleanup(lambda x = f: x.custom_cleanup())
        # run core code that uses resource f
    

Actually there’s another mess here. In PySerial, creating the Serial object with a wrong port string will throw an exception as well, which with-as statements cannot handle. Therefore you’ll need to do both:

try:
    ser = serial.Serial(dev_str)
except:
    print(dev_str + " not accessible (either the wrong port of something else opened it)");
else:
    with ser:
        # meat

If your resource initializer does not have context manager built in, and you want a quick-and-dirty solution (given your cleanup is a one-liner). Use my library (lang.py) that recreates onCleanup():

"""
@author: [2019-04-23] Hoi Wong 
"""
class onCleanup:
    '''make sure you 'capture' the lambdas by initializing an intermediate running variable
       e.g. lambda s=ser: s.close()
       lambda: ser.close() will NOT work as ser is not 'captured''''
    def __init__(self, functor):
        self.task = functor;
    def __del__(self):
        self.task()

Then you can use the old way without nesting try/except:

try:
    f = File()
except:
    print("Cannot open file")
else:
    cleanup_obj = onCleanup(lambda x = f: x.custom_cleanup())
    # run core code that uses resource f

Check with the provider of your resource initializer to see if context manager is already implemented. Use onCleanup() only when you don’t have this facility and you don’t want to build a whole context manager (even with decorators) for a one-liner cleanup.

47 total views, no views today