Can you use LINQ types and extension methods in IronPython?

47

Solution 1

IronPython 2.7 finally bridges this gap with the clr.ImportExtensions method which adds the extension methods from a namespace to the target types e.g.

>& 'C:\Program Files\IronPython 2.7\ipy.exe'
IronPython 2.7 (2.7.0.40) on .NET 4.0.30319.225
Type "help", "copyright", "credits" or "license" for more information.
>>> import clr
>>> clr.AddReference("System.Core")
>>> from System.Collections.Generic import List
>>> dir (List)
['Add', 'AddRange', 'AsReadOnly', 'BinarySearch', 'Capacity', 'Clear', 'Contains', 'ConvertAll', 'CopyTo', 'Count', 'Enu
merator', 'Equals', 'Exists', 'Find', 'FindAll', 'FindIndex', 'FindLast', 'FindLastIndex', 'ForEach', 'GetEnumerator', '
GetHashCode', 'GetRange', 'GetType', 'IndexOf', 'Insert', 'InsertRange', 'IsReadOnly', 'IsSynchronized', 'Item', 'LastIn
dexOf', 'MemberwiseClone', 'ReferenceEquals', 'Remove', 'RemoveAll', 'RemoveAt', 'RemoveRange', 'Reverse', 'Sort', 'Sync
Root', 'ToArray', 'ToString', 'TrimExcess', 'TrueForAll', '__add__', '__class__', '__contains__', '__delattr__', '__doc_
_', '__format__', '__getattribute__', '__getitem__', '__hash__', '__init__', '__iter__', '__len__', '__new__', '__reduce
__', '__reduce_ex__', '__repr__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__']
>>> import System
>>> clr.ImportExtensions(System.Linq)
>>> dir (List)
['Add', 'AddRange', 'Aggregate', 'All', 'Any', 'AsEnumerable', 'AsParallel', 'AsQueryable', 'AsReadOnly', 'Average', 'Bi
narySearch', 'Capacity', 'Cast', 'Clear', 'Concat', 'Contains', 'ConvertAll', 'CopyTo', 'Count', 'DefaultIfEmpty', 'Dist
inct', 'ElementAt', 'ElementAtOrDefault', 'Enumerator', 'Equals', 'Except', 'Exists', 'Find', 'FindAll', 'FindIndex', 'F
indLast', 'FindLastIndex', 'First', 'FirstOrDefault', 'ForEach', 'GetEnumerator', 'GetHashCode', 'GetRange', 'GetType',
'GroupBy', 'GroupJoin', 'IndexOf', 'Insert', 'InsertRange', 'Intersect', 'IsReadOnly', 'IsSynchronized', 'Item', 'Join',
 'Last', 'LastIndexOf', 'LastOrDefault', 'LongCount', 'Max', 'MemberwiseClone', 'Min', 'OfType', 'OrderBy', 'OrderByDesc
ending', 'ReferenceEquals', 'Remove', 'RemoveAll', 'RemoveAt', 'RemoveRange', 'Reverse', 'Select', 'SelectMany', 'Sequen
ceEqual', 'Single', 'SingleOrDefault', 'Skip', 'SkipWhile', 'Sort', 'Sum', 'SyncRoot', 'Take', 'TakeWhile', 'ToArray', '
ToDictionary', 'ToList', 'ToLookup', 'ToString', 'TrimExcess', 'TrueForAll', 'Union', 'Where', 'Zip', '__add__', '__clas
s__', '__contains__', '__delattr__', '__doc__', '__format__', '__getattribute__', '__getitem__', '__hash__', '__init__',
 '__iter__', '__len__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setitem__', '__sizeof__'
, '__str__', '__subclasshook__']
>>>

which brings it into line with IronRuby 1.1's using_clr_extensions method.

Solution 2

Some of the things you'd do with LINQ can be done with list comprehensions:

[myFunc(i) for i in numbers if i > 3]

Or you can use map, reduce, and filter:

map(myFunc, filter(lambda x: x > 3, numbers))

But list comprehensions are much more "Pythonic" than using the functional programming constructs. For reducing things, consider using "".join or sum. And you can check the truth value of entire iterables by using any and all.

Just remember these translations:

Select -> map
Where -> filter
Aggregate -> reduce

And you'll be well on your way!

Solution 3

In IronPython 2.7.1 you have clr.ImportExtensions for this use case.

import clr
clr.AddReference("System.Core")
import System
clr.ImportExtensions(System.Linq)

# will print 3 and 4 :)
[2, 3, 4].Where(lambda x: x != 2).ToList().ForEach(System.Console.WriteLine)

A little background: IronPython 2.7 initially introduced this feature, but there was an issue which stopped it from being really usable.

Solution 4

I described a C# wrapper class around the LINQ extension methods to achieve a syntax similar to C#'s 'chained extension method' syntax in IronPython.

The idea is to have a kind of decorator class around IEnumerable that simply calls the extension methods. Probably this wrapper class can be written just as well in IronPython, but I'm not as fluent in Python yet :-)

public class ToLinq<T> : IEnumerable<T>
{
    private readonly IEnumerable<T> _wrapped;

    public ToLinq(IEnumerable<T> wrapped)
    {
       _wrapped = wrapped;
    }

    public ToLinq<T> Where(Func<T, bool> predicate)
    {
        return new ToLinq<T>(_wrapped.Where(predicate));
    }

    // ... similar methods for other operators like Select, Count, Any, ...
}

This allows for a syntax similar to this:

johns = ToLinq[Customer](customers)\
          .Where(lambda c: c.Name.StartsWith("John"))\
          .Select(lambda c: c.Name)

Disclaimer: This is something I tried as a learning exercise. I haven't used this in a real-world project.

Share:
47
Pak Hang Leung
Author by

Pak Hang Leung

Updated on June 03, 2022

Comments

  • Pak Hang Leung
    Pak Hang Leung about 2 years

    Now I have a list of dicts like this:

    test_dict = [{'Id': 3, 'Name': 'Marcus', 'Email': '[email protected]'}, {'Id': 4, 'Name': 'Sally', 'Email': '[email protected]'}]
    

    What I want to do is to first convert into a tuple and write the record into table in database. The outcome that I want to achieve is like:

    [(3, 'Marcus', '[email protected]'), (4,'Sally', '[email protected]')]
    

    I used the following code to extract the values:

    list_test = []
    
    for x in test_dict:
        data = x.values()
        list_test.append(data)
    
    print(list_test)
    

    And I got the following:

    [dict_values([3, 'Marcus', '[email protected]']), dict_values([4, 'Sally', '[email protected]'])]
    

    Before I though it might be an default value and I tried to get the element e.g 3 in this case with print(list_test[0][1]), it returns error:

    TypeError: 'dict_values' object is not subscriptable
    

    Why this happens? And if I want to achieve the aim, how should I write? Thanks!

    • Paul M.
      Paul M. almost 4 years
      You want tuples = [tuple(d.values()) for d in test_dict], where tuples will be a list of tuples.
  • Vishal Singh
    Vishal Singh almost 4 years
    will tuple(x.values()) maintain the order the op wants?
  • RoadRunner
    RoadRunner almost 4 years
    @VishalSingh Yes, python 3.6+ preserves dictionary insertion order, and since values() is just a view of the dictionary values, nothing is changed when you cast tuple. Documentation for reference.
  • Pak Hang Leung
    Pak Hang Leung almost 4 years
    @RoadRunner Thanks and it works! But actually why the code that I wrote returned the strange value?
  • Pak Hang Leung
    Pak Hang Leung almost 4 years
    Thank you! So that means, the error that I made was because I was extracting the index from the dicts itself, instead of the values inside the dicts?
  • pari
    pari almost 4 years
    Yes, by first indexing, so list_test[0] we are getting dictionary objects, then for extracting the values out of this object using indexing, u need to convert it to a list, and then u r able to extract values by another indexing: list(list_test[0])[0]