swift icon indicating copy to clipboard operation
swift copied to clipboard

Pass function/closure to Python

Open tanmayb123 opened this issue 5 years ago • 16 comments

I'm not sure how difficult this would be, or even if it's possible -- could we pass functions/closures from Swift to Python?

Something like this:

test.py:

def f(x, y):
    return x(y + 1)

if __name__ == "__main__":
    print(f(lambda z: z**2, 5)) # Should print 6^2

test.swift

import Python

let sys = Python.import("sys")
sys.path.append(".")
let test = Python.import("test")

print(test.f({ z in pow(z, 2) }, 5)) // I wish this were possible!

tanmayb123 avatar Jun 29 '19 05:06 tanmayb123

There doesn't exist a "ExpressibleByClosureLiteral" to enable implicit conversion of the { z in pow(z, 2) } expression into a PythonObject.

More critically, translating Swift expressions/statements within a lambda into Python expressions/statements (e.g. turning { z in pow(z, w) } into Python C API calls to build up a similar Python function object) requires non-trivial machinery.

Possible solutions are:

  • Language virtualization: turning native language constructs into customizable entry points.
  • Adding a new function convention: e.g. adding @convention(python), function conversion triggers translation of Swift AST to Python.

But both are very heavyweight.

dan-zheng avatar Jun 29 '19 06:06 dan-zheng

Interesting - thanks for sharing!

On a similar note, just like how you can call Python code from Swift, is there a way to call Swift from Python? If so, how difficult would it be to get that running? I see this: https://gist.github.com/jiaaro/e111f0f64d0cdb8aca38 but it's not working for me, for some reason.

Currently, with the Python->Swift interop (which is absolutely amazing btw), there are a few limitations around interacting with an existing Python codebase. I think going both ways could open up a lot more potential.

tanmayb123 avatar Jun 29 '19 06:06 tanmayb123

Calling Swift from Python is a direction we've thought about! That direction is interesting because it allows large Python codebases to incrementally add Swift code, using features like differentiable programming. This seems more useful than calling numpy from Swift, for example.

One approach is to declare and export Swift @_cdecl functions and import them in Python. Python supports importing C-compatible functions. @_cdecl functions are severely constrained (small set of supported parameter/result types, must be top-level), but they can be wrapped in Swift (and in Python, after being imported) to be more friendly.

dan-zheng avatar Jun 29 '19 06:06 dan-zheng

I do not think passing Swift closures needs anything heavyweight like virtualization or type system changes. All we need is to get its function pointer and maintain the lifetime of the closure context using a PythonObject.

rxwei avatar Jun 30 '19 20:06 rxwei

This would be super useful!

cgarciae avatar Jul 14 '19 17:07 cgarciae

I do not think passing Swift closures needs anything heavyweight like virtualization or type system changes. All we need is to get its function pointer and maintain the lifetime of the closure context using a PythonObject.

I do something like that to call Swift from C++ so I'm pretty sure it can be done relatively easily.

eaplatanios avatar Jul 29 '19 22:07 eaplatanios

Any progress on that front? That would enable some pretty cool things (eg: I'd love to build an ETL tool in swift that wraps over Airflow, but it's not possible unless I can pass closures that can be called from python).

biellls avatar Oct 18 '19 09:10 biellls

Many python libraries have async function versions with callbacks. Not sure if it is possible to use that from swift. Any idea how to bridge it?

avnerbarr avatar Feb 03 '20 20:02 avnerbarr

I'm currently trying to maximize a swift function. It seems that scipy.optimize.minimize is a good way to do so, but I would need to be able to pass the Swift function to the Python library.

As hinted at in this thread, is this currently not possible? Does anyone have any other suggestions?

aepryus avatar Feb 04 '20 05:02 aepryus

Any progress in that matter?

graboosky avatar Feb 16 '20 09:02 graboosky

It would be easy to make this work:

print(test.f(PythonObject({ z in pow(z, 2) }), 5))

if such cases were common enough to make that syntactically burdensome, one could create an operator to sugar it, but I don't think we're at that point yet:

print(test.f({ z in pow(z, 2) }^, 5))

Amendment: you would of course need to specify types in the closure signature, because there's nothing available to deduce them from:

print(test.f(PythonObject({ (z: Float) in pow(z, 2) }), 5))
print(test.f({ (z: Float) in pow(z, 2) }^, 5))

dabrahams avatar Mar 04 '20 21:03 dabrahams

we are talking about closures back to the swift code, not lambdas in python right?

avnerbarr avatar Mar 05 '20 10:03 avnerbarr

@dabrahams but how would you make closure responsive to 'PythonConvertible'?

graboosky avatar Mar 05 '20 10:03 graboosky

@graboosky without language extensions to make non-nominal types such as functions and tuples able to (implicitly) conform to protocols, you wouldn't. So instead you'd pay the syntactic price of explicit wrapping in PythonObject or the use of an operator.

dabrahams avatar Mar 06 '20 05:03 dabrahams

I see. At this moment did someone experiment with C? I like the idea of passing C function pointers, I did callback with primitive parameters between 'swift <-> C'. I would like to see how to do that for 'swift <-> C <-> Python'. Unfortunately, Python is magic for me:) Gist welcome:)

graboosky avatar Mar 06 '20 11:03 graboosky

I'm afraid I don't understand your question. Regardless, I don't know about all the experiments that have been done so I'm probably not the best person to answer it.

Sorry, Dave

On Fri, Mar 6, 2020 at 3:58 AM Patryk Grabowski [email protected] wrote:

I see. At this moment did someone experiment with C? I like the idea of passing C function pointers, I did callback with primitive parameters between 'swift <-> C'. I would like to see how to do that for 'swift <-> C <-> Python'. Unfortunately, Python is magic for me:) Gist welcome:)

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/tensorflow/swift/issues/211?email_source=notifications&email_token=AAAKYINYQJX24QCVPGATLDTRGDQNVA5CNFSM4H4JWDL2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEOBDSZY#issuecomment-595736935, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAAKYIJDKO5PP2YYCR7HBV3RGDQNVANCNFSM4H4JWDLQ .

-- -Dave

dabrahams avatar Mar 12 '20 23:03 dabrahams