pythonnet / pythonnet

Python for .NET is a package that gives Python programmers nearly seamless integration with the .NET Common Language Runtime (CLR) and provides a powerful application scripting tool for .NET developers.
http://pythonnet.github.io
MIT License
4.48k stars 694 forks source link

Using F# (FSHARP) successfully #112

Open aolney opened 8 years ago

aolney commented 8 years ago

I've used F# to do some of the nice dynamic interop with pythonnet rather than the c# dynamic keyword approach. Not sure if it is truly helpful over c# dynamic, but it may be a bit more concise and interesting.

Here is some code that shows the functionality. The key is to use fsprojects/FSharp.Interop.Dynamic@318f50b234bc8248c78451a0fac0d783b493663e (actually whatever is currently available in nuget). This helped me get closer to the goal behavior of fsprojects/FSharp.Interop.PythonProvider@f3cdabf0f4e198fe5d02bd36d68aacb424c7cbe4 but of course without the type provider mojo (meaning user perceived behavior rather than implementation behavior). I used pythonnet from tonyroberts/pythonnet@21270ebe858267dc11db74c57181ce5c61ee5a29 , CPython3.4, the matching NLTK distribution, VS2015, Windows 10.

As described below the most significant complaint is that you can't use F# primitive operators like (+) on numpy arrays without an error. The workaround is to use the corresponding numpy function as demonstrated.

Also if a complicated call is made like nltk?corpus?brown?tagged_words(Py.kw("categories", "news") ) the debugger seems to time out the expression evaluation (requiring print statements). Though in general the expression evaluation on the dynamic types returned is not very helpful, so this isn't a major drawback.

open Python.Runtime
open FSharp.Interop.Dynamic
open System.Collections.Generic

[<EntryPoint>]
let main argv = 
    //set up for garbage collection?
    use gil = Py.GIL()

    //-----
    //NUMPY
    //import numpy
    let np = Py.Import("numpy")

    //call a numpy function dynamically
    let sinResult = np?sin(5)

    //make a python list the hard way
    let list = new Python.Runtime.PyList()
    list.Append( new PyFloat(4.0) )
    list.Append( new PyFloat(5.0) )

    //run the python list through np.array dynamically
    let a = np?array( list )
    let sumA = np?sum(a) 

    //again, but use a keyword to change the type
    let b = np?array( list, Py.kw("dtype", np?int32 ) )
    let sumAB = np?add(a,b)

    let SeqToPyFloat ( aSeq : float seq ) =
        let list = new Python.Runtime.PyList()
        aSeq |> Seq.iter( fun x -> list.Append( new PyFloat(x)))
        list

    //Worth making some convenience functions (see below for why)
    let a2 = np?array( [|1.0;2.0;3.0|] |> SeqToPyFloat )

    //--------------------
    //Problematic cases: these run but don't give good results
    //make a np.array from a generic list
    let list2 = [|1;2;3|] |> ResizeArray
    let c = np?array( list2 )
    printfn "%A" c //gives type not value in debugger

    //make a np.array from an array
    let d = np?array( [|1;2;3|] )
    printfn "%A" d //gives type not value in debugger

    //use a np.array in a function
    let sumD = np?sum(d)  //gives type not value in debugger
    //let sumCD = np?add(d,d) // this will crash

    //can't use primitive f# operators on the np.arrays without throwing an exception; seems 
    //to work in c# https://github.com/tonyroberts/pythonnet //develop branch
    //let e = d + 1

    //-----
    //NLTK
    //import nltk
    let nltk = Py.Import("nltk")
    let sentence = "I am happy"
    let tokens = nltk?word_tokenize(sentence)
    let tags = nltk?pos_tag(tokens)

    let taggedWords = nltk?corpus?brown?tagged_words()
    let taggedWordsNews = nltk?corpus?brown?tagged_words(Py.kw("categories", "news") )
    printfn "%A" taggedWordsNews

    let tlp = nltk?sem?logic?LogicParser(Py.kw("type_check",true))
    let parsed = tlp?parse("walk(angus)")
    printfn "%A" parsed?argument

    0 // return an integer exit code
den-run-ai commented 8 years ago

This seems a better fit for pythonnet mailing list or a wiki page, since I do not see any issue, is there? I also copied your answer here:

http://stackoverflow.com/a/34207705/2230844

Folks from Deedle that is written in F# use pythonnet extensively.

aolney commented 8 years ago

Not issue in the sense of bug but perhaps in the broader sense https://guides.github.com/features/issues/

I'm not sure if there is an appropriate label that could be applied to this issue. It is not an enhancement per se but might be useful in future planning as another case study in dynamic.

Thanks for the Deedle/pythonnet connection

royalstream commented 8 years ago

The main problem I have with F# is not being able to use the standard arithmetic operators. This is a limitation of the language, of course, not of Python.NET.

I resorted to this for the time being but needless to say it's not an optimal solution:

let (?+) a b = a?__add__(b)

The only other solution I can think of at this moment is forking pythonnet to define the respective static methods (e.g. op_Addition) in PyObject (so they become accessible from F#) and maybe using a custom ? operator that always returns a PyObject (as opposed to a raw obj)

den-run-ai commented 8 years ago

Another way would be to submit a pull request to pythonnet with f# specific code enabled using compilation symbol (like it is done now for various platforms) or even runtime detection. Of course basic tests would be welcome.

BTW, why F# uses special arithmetic operators?

On Friday, March 18, 2016, Steven notifications@github.com wrote:

The main problem I have with F# is not being able to use the standard arithmetic operators. This is a limitation of the language, of course, not of Python.NET.

I resorted to this for the time being but needless to say it's not an optimal solution:

let (?+) a b = a?add(b)

The only other solution I can think of at this moment is forking pythonnet to define the respective static methods (e.g. _opAddition) in PyObject (so they become accessible from F#) and maybe using a custom ? operator that always returns a PyObject (as opposed to a raw obj)

— You are receiving this because you commented. Reply to this email directly or view it on GitHub https://github.com/pythonnet/pythonnet/issues/112#issuecomment-198602887

royalstream commented 8 years ago

It's actually the other way around: DynamicObject has direct support from the C# compiler, so the dot operator gets mapped to DynamicObject.TryInvokeMember/TryGetMember/etc and the arithmetic operators get mapped to DynamicObject.TryBinaryOperation/TryUnaryOperation/etc F# has no such thing which is why you need to import the third party FSharp.Interop.Dynamic library so you can use the ? operator and for the time being there's no solution regarding the arithmetic operators.

den-run-ai commented 7 years ago

@matthid did you encounter the issues above in your F# code with pythonnet? If yes, how did you workaround this?

matthid commented 7 years ago

I think @royalstream has said just about everything. There is no particular issue either on this (pythonnet) or the F# side. There is no (and probably wont be) dynamic support by the F# compiler.

I like the suggestion to add the operators at PyObject (eg op_addition).

matthid commented 7 years ago

I should note here that I immediately typed/casted/converted all results to static types. Therefore I did never notice this issue.

https://github.com/matthid/googleMusicAllAccess/blob/develop/src/GMusicApi/GMusicApi.fs#L176

https://github.com/matthid/googleMusicAllAccess/blob/develop/src/GMusicApi/PythonInterop.fs#L742

And I build a computation expression to not forget to gather and release the GIL: https://github.com/matthid/googleMusicAllAccess/blob/develop/src/GMusicApi/PythonInterop.fs#L20

More details: https://yaaf.de/blog/post/2016-05-28/Having%20Fun%20with%20Computation%20Expressions

den-run-ai commented 7 years ago

@matthid thank you for the feedback, I included a link to your blog post in wiki:

https://github.com/pythonnet/pythonnet/wiki

BTW, congratulations on being featured in Microsoft weekly news!

https://blogs.msdn.microsoft.com/dotnet/2016/05/31/the-week-in-net-5312016/

den-run-ai commented 7 years ago

@aolney I found your blog post as well (Using NLTK from F#) and linked it in wiki.

dsyme commented 7 years ago

As described below the most significant complaint is that you can't use F# primitive operators like (+) on numpy arrays without an error. The workaround is to use the corresponding numpy function as demonstrated.

It would make sense to track this with an F# language suggestion. But the techniques you are using today make sense.

jbtule commented 6 years ago

FSharp.Interop.Dynamic has operators now for dynamic objects. Version 4.0 (current beta package on nuget. )

open FSharp.Interop.Dynamic.Operators

https://github.com/fsprojects/FSharp.Interop.Dynamic/blob/1c7ce0551da8d864c174eb981cf52406ac506659/Tests/Library1.fs#L194-L229

den-run-ai commented 6 years ago

some F# related issues: https://github.com/pythonnet/pythonnet/issues/574