[PYTHON] Try to make something like C # LINQ

Overview

Can the idea of LINQ in C # be useful in Maya Python? I tried to find out.

motivation

――I want to operate the list more easily and easily. --If you can write something similar to C #, the switching cost should go down.

What is LINQ

I will leave the explanation about LINQ to other sites (reference sites are summarized at the bottom of the page) Roughly speaking, it is "* a function to filter and process values in a SQL statement-like way for lists etc. *". The code that actually uses LINQ is as follows.

LinqSample.cs


var hoge = new int[] { 0, 1, 2, 3, 4, 5 };
var hogehoge = hoge.where(n => n > 3).select(n => n * n);
foreach (var x in hogehoge)
    Debug.WriteLine(x);

>>> 16
>>> 25

If you write the above code in Python's ** list comprehension **, it will be as follows.

list_comprehension_sample.py


hoge = [0, 1, 2, 3, 4, 5]
hogehoge = [x * x for x in hoge if x > 3]
for x in hogehoge:
    print x

>>> 16
>>> 25

If you write it like this, I think it would be nice if there was a list comprehension notation. (Sasuga Python) Furthermore, since slices can be used in Python, there are few problems with list operations.

LINQ features such as ** method chain ** and ** lazy evaluation **, I think the big advantage is that it is easy to understand and write while simply shortening the code. Writing complex operations with Python list comprehensions and slices can significantly reduce readability.

Based on this situation, I thought about how to reproduce the good parts of LINQ.

** First prototype **

First of all, I tried to make only the interface like that to check the atmosphere. The first trial is to prepare the same method as LINQ and bring only the single effect closer.

iterator_v1.py


class GeneralIterator(object):
    """A class that wraps list operations using LINQ method names

    <usage>
    selection = GeneralIterator(cmds.ls(sl=True))
    selection.count()
    for x in selection.generator(): print x
    selection.last()
    selection.first()
    selection.at(3)
    selection.distinct()
    selection.skip(3)
    selection.take(3)
    selection.all(lambda x: x.startswith('mesh_'))
    selection.any(lambda x: x.startswith('skel_'))
    selection.contains("grp_")
    selection.union(["group1", "group2"])
    selection.reverse()
    selection.select(lambda x: cmds.getAttr(x + '.tx'))
    selection.where(lambda x: x.endswith('_offset'))
    """

    def __init__(self, list=None):
        self.set_list(list)

    def set_list(self, list):
        self.__list = list

    def is_empty(self):
        return self.__list is None or len(self.__list) == 0

    def print_items(self):
        for x in self.generator():
            print x

    def count(self):
        if self.is_empty():
            return 0
        return len(self.__list)

    def generator(self):
        for x in self.__list:
            yield x
    
    def first(self, default=None):
        if self.is_empty():
            return default
        return self.__list[0]

    def last(self, default=None):
        if self.is_empty():
            return default
        return self.__list[-1]

    def at(self, index, default=None):
        if index <= self.count():
            return self.__list[index]
        return default

    def distinct(self):
        return list(set(self.__list))

    def skip(self, count):
        if count < self.count():
            return self.__list[count:]
        
    def take(self, count):
        if count <= self.count():
            return self.__list[:count]

    def all(self, func):
        for x in self.generator():
            if not func(x):
                return False
        return True

    def any(self, func):
        for x in self.generator():
            if func(x):
                return True
        return False

    def contains(self, obj):
        for x in self.generator():
            if x == obj:
                return True
        return False

    def union(self, list):
        return self.__list + list

    def reverse(self):
        return list(reversed(self.__list))

    def select(self, func):
        return [func(x) for x in self.__list]

    def where(self, func):
        return [x for x in self.__list if func(x)]

** 2nd prototype **

As a next step, I want a method chain. I would like to see delayed execution. After reading the implementation method of LINQ, I thought that I could make something similar by using closures, and I tried the second one.

iterator_v2.py


class EnumerableIterator(object):
    """List manipulation class that tried method chain and lazy execution like LINQ
    
        [usage]
        hoge = EnumerableIterator(range(10))
        for x in hoge.where(lambda x: x > 7).select(lambda x: x * x): print x
    """

    def __init__(self, list=None, func=None):
        self._set_list(list)
        self.func = func

    def _set_list(self, list):
        self.__list = list

    def __execute_func(self):
        if self.func is None:
            return self.__list
        return self.func(self.__list)
        
    def __iter__(self):
        for x in self.__execute_func():
            yield x

    def to_list(self):
        return self.__execute_func()

    def count(self):
        return len(self.__execute_func())

    def __is_empty(self, list):
        return list is None or len(list) == 0

    def first(self, default=None):
        result = self.__execute_func()
        if self.__is_empty(result):
            return default
        return result[0]

    def last(self, default=None):
        result = self.__execute_func()
        if self.__is_empty(result):
            return default
        return result[-1]

    def at(self, index, default=None):
        result = self.__execute_func()
        if self.__is_empty(result):
            return default
        if index <= len(result):
            return list[index]
        return default

    def distinct(self):
        return list(set(self.__execute_func()))

    def skip(self, count):
        result = self.__execute_func()
        return result[count:]
        
    def take(self, count):
        result = self.__execute_func()
        return result[:count]

    def all(self, func):
        for x in self:
            if not func(x):
                return False
        return True

    def any(self, func):
        for x in self:
            if func(x):
                return True
        return False

    def contains(self, obj):
        for x in self:
            if x == obj:
                return True
        return False

    def union(self, list):
        return self.__execute_func() + list

    def reverse(self):
        return list(reversed(self.__execute_func()))

    def where(self, func):
        def action(list):
            result = list
            if self.func is not None:
                result = self.func(list)
            return [x for x in result if func(x)]
        return EnumerableIterator(self.__list, action)

    def select(self, func):
        def action(list):
            result = list
            if self.func is not None:
                result = self.func(list)
            return [func(x) for x in result]
        return EnumerableIterator(self.__list, action)

** Use the second prototype **

If nothing is done, the content will not be related to Maya, so I will try using it in Maya.

iterator_v2.py


class EnumerableSelection(EnumerableIterator):
    """Object selection iterator

    [usage]
    selection = Selection()

    for x in selection.where(lambda x: x.endswith('_offset')).select(lambda x: cmds.getAttr(x + '.tx')):
        print x

    print selection \
        .where(lambda  x: x.endswith('Group')) \
        .select(lambda x: cmds.getAttr(x + '.tx')) \
        .where(lambda  x: x > 0.1) \
        .first()
    """

    def __init__(self, flat=True):
        super(EnumerableSelection, self).__init__()
        self.__flat = flat
        self.update()

    def update(self):
        self._set_list(cmds.ls(sl=True, fl=self.__flat))

** Impression **

There are also itertools and more-itertools, but I prefer LINQ as the writing style. I feel that if I can understand and use the language specifications more, I will be able to write in Pythonic style. I would like to continue studying while waiting for advice and advice from various fields.

** Reference site **

-I made a list of LINQ extension methods and almost all the samples. --Go to the horizon -LINQ mechanism & correct basic knowledge of lazy evaluation- @ IT

Recommended Posts

Try to make something like C # LINQ
Try to make a Python module in C language
Something like 40-32 / 2 = 4!
Try to make a kernel of Jupyter
Do you make something like a rocket?
Try something like Python for-else in Ruby
Try to make your own AWS-SDK with bash
Would you like to make a Twitter resume?
AI beginners try to make professional student bots
Try to make a "cryptanalysis" cipher with Python
Try to make a dihedral group with Python
I want to make C ++ code from Python code!
Try to make client FTP fastest with Pythonista
I tried to make something like a chatbot with the Seq2Seq model of TensorFlow
Try using Junos' On-box Python #Bonus 1 Something like ChatOps / Commit and post to Slack
Try to make a command standby tool with python
Try to make RESTful API with MVC using Flask 1.0.2
Machine learning beginners try to make a decision tree
Try to implement yolact
Try to make it using GUI and PyQt in Python
Try to make PC setting change software using TKinter (beginner)
I want to do something like sort uniq in Python
When I try to make Apache SSL, it doesn't start.
[Introduction to Tensorflow] Understand Tensorflow properly and try to make a model
Try to make Qiita's Word Cloud from your browser history