Depuis l’arrivée du moteur Dynamo PythonNet3, il est désormais
possible d’exploiter les méthodes LINQ (Language Integrated Query) avec des versions récentes de Python3 sur DynamoBIM.
Les méthodes d’extension de System.Linq
en .NET permettent d'effectuer des opérations de requêtage et de
manipulation de collections.
Elles s'appliquent principalement aux collections qui implémentent
IEnumerable<T>
et
IQueryable<T>
, et incluent des fonctionnalités comme le filtrage, le tri, la
transformation et l'agrégation des données.
La différence la plus notable avec IronPython, c'est qu'il faudra être vigilant lorsqu'une méthode d'extension LINQ a plusieurs variantes ( Overloads ).
Voyons quelques exemples :
-
Exemple avec les méthodes
Where
etSelect
import sys
import clr
import System
from System.Collections.Generic import List, IList
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *
clr.AddReference('RevitAPI')
import Autodesk
from Autodesk.Revit.DB import *
import Autodesk.Revit.DB as DB
clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Persistence import DocumentManager
doc = DocumentManager.Instance.CurrentDBDocument
clr.AddReference("System.Core")
clr.ImportExtensions(System.Linq)
#
lst_int = List[System.Int32](list(range(10)))
resultA = lst_int.Where[System.Int32](System.Func[System.Int32, System.Boolean](lambda x : x % 2 == 0))\
.Select[System.Int32, System.Int32](System.Func[System.Int32, System.Int32](lambda x : x ** 2))
OUT = resultA
.
-
Les lambdas Python doivent être transformées (cast) en délégués
.NET de type
System.Func<T>
pour être compatibles avec les méthodes d’extension LINQ.System.Func[System.Int32, System.Int32](lambda x : x ** 2)
Func<T> est un type de délégué prédéfini pour une méthode qui renvoie une valeur du type T.
-
PythonNET prend en charge les types génériques. Un type
générique peut être spécifié en lui attribuant des types avant de
pouvoir être instancié.
.Select[System.Int32, System.Int32]()
-
Si vous créez des nouvelles instances par la suite dans votre
code via
NewFamilyInstance
je vous recommande fortement de fixer votre "collector" en le transformant enList<T>
avec la méthode d'extensionToList()
(pour éviter que de nouveaux éléments viennent s'ajouter à ce dernier)
-
La classe
FilteredElementCollector
implémente l'interfaceIEnumerable<T>
, de ce fait la méthodeToElements()
n'est pas nécessaire.
- Dans certains cas, vous pouvez vous passer de spécifier le Type lorsque les méthodes ont le même Type de paramètre (exemple FirstOrDefault )
-
Exemples avec les méthodes
FirstOrDefault
,GroupBy
import sys
import clr
import System
from System.Collections.Generic import List, IList
clr.AddReference("System.Reflection")
from System.Reflection import Assembly
clr.AddReference('RevitAPI')
import Autodesk
from Autodesk.Revit.DB import *
import Autodesk.Revit.DB as DB
clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Persistence import DocumentManager
doc = DocumentManager.Instance.CurrentDBDocument
clr.AddReference("System.Core")
clr.ImportExtensions(System.Linq)
#
resultB = FilteredElementCollector(doc).OfClass(FamilySymbol).WhereElementIsElementType()\
.Where(System.Func[DB.Element, System.Boolean](lambda e_type : "E1" in e_type.Name))
resultC = FilteredElementCollector(doc).OfClass(FamilySymbol).WhereElementIsElementType()\
.FirstOrDefault(System.Func[DB.Element, System.Boolean](lambda e_type : "E1" in e_type.Name))
resultD = FilteredElementCollector(doc).OfClass(FamilySymbol).WhereElementIsElementType()\
.FirstOrDefault(System.Func[DB.Element, System.Boolean](lambda e_type : "E12" in e_type.Name))
resultGroup = FilteredElementCollector(doc).OfClass(FamilySymbol).WhereElementIsElementType()\
.GroupBy[DB.Element, System.String](System.Func[DB.Element, System.String](lambda e_type : e_type.Name))\
.Select(System.Func[System.Object, System.Object](lambda x : x.ToList()))
assemblies = System.AppDomain.CurrentDomain.GetAssemblies()
assemblies_name = assemblies\
.OrderBy[Assembly, System.String](System.Func[Assembly, System.String](lambda x : x.GetName().Name))\
.Select[Assembly, System.String](System.Func[Assembly, System.Int32, System.String](lambda asm, index : f"{index + 1} : {asm.GetName().Name}"))
print(assemblies_name.GetType())
OUT = resultB.ToList(), resultC, resultD, resultGroup.ToList(), assemblies_name
-
Exemple pour convertir un
IEnumerable[IGrouping[TKey, TElement]]
enDictionary[TKey, TValue]
import clr
import System
from System.Collections.Generic import List
clr.AddReference('RevitAPI')
import Autodesk
import Autodesk.Revit.DB as DB
from Autodesk.Revit.DB import *
clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Persistence import DocumentManager
doc = DocumentManager.Instance.CurrentDBDocument
clr.AddReference("System.Core")
clr.ImportExtensions(System.Linq)
fecMultiCat = FilteredElementCollector(doc).OfClass(FamilySymbol)
#
groupList=[]
#
dict_group = fecMultiCat.OrderBy[DB.Element, System.String](System.Func[DB.Element, System.String](lambda e_type : e_type.FamilyName))\
.GroupBy[DB.Element, System.String](System.Func[DB.Element, System.String](lambda x : x.Category.Name))\
.ToDictionary[System.Object, System.Object, System.Object](
System.Func[System.Object, System.Object](lambda g : g.Key),
System.Func[System.Object, System.Object](lambda g : g.ToList())
)
# convert to python dict
pydict = dict(dict_group)
OUT = dict_group
...- Note concernant les DataTables
Les méthodes d'extension des DataTables ne sont pas prises en charge, une solution consiste à utiliser les méthodes statiques.
...
# Load the Python Standard and DesignScript Libraries
import sys
import clr
import System
from System.Collections.Generic import List, IList
clr.AddReference('RevitAPI')
import Autodesk
from Autodesk.Revit.DB import *
import Autodesk.Revit.DB as DB
clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Persistence import DocumentManager
doc = DocumentManager.Instance.CurrentDBDocument
clr.AddReference("System.Core")
clr.ImportExtensions(System.Linq)
#
clr.AddReference('System.Data')
from System.Data import *
clr.AddReference('System.Data.Common')
from System.Data import DataTableExtensions
class Test():
def __init__(self):
self.i= 89
dt = DataTable("test")
dt.Columns.Add('Name', System.String)
dt.Columns.Add('Number', System.String)
dt.Columns.Add('Level', System.String)
dt.Columns.Add('Area', System.Double)
dt.Columns.Add('object', System.Object)
dt.Rows.Add("A", "1", "L1", 23.9, Test())
dt.Rows.Add("B", "2", "L1", 43.9, Test())
dt.Rows.Add("C", "3", "L2", 33.9, Test())
# syntax IronPython
# newdt = List[DataRow](dt.AsEnumerable().Where(lambda r : r['Level'] == "L1")).CopyToDataTable()
newRows = List[DataRow]([r for r in dt.Rows])\
.Where(System.Func[DataRow, System.Boolean](lambda r : r['Level'] == "L1"))
newdt = DataTableExtensions.CopyToDataTable[DataRow](newRows)
OUT = newdt.GetType().ToString(), [[r['Area'], r["object"]] for r in newdt.Rows]
- Test de performances entre PythonNet et IronPython
Test sur 30000 points
Environnement | Méthode | Nombre de points | Moyenne temps (µs) | Nombre de tests | Version Runtime |
---|---|---|---|---|---|
IronPython2 | Where | 30 000 | 600 | 20 | 4.8 |
IronPython3 | Where | 30 000 | 450 | 20 | 4.8 |
PythonNet3 | Where | 30 000 | 350 | 20 | 8.0.10 |
Can we use Plinq somehow with PythonNET3? Thank you!
RépondreSupprimerI mean AsParallel().
SupprimerHi, parallel multi-threading with AsParallal() do not work with PythonNet, try multiprocessing with concurrent.futures instead.
SupprimerAfter checking, it works, but I'll do a few more tests to see if parallelization is taken into account.
SupprimerGood news! I could test performance on big rvt model, if you share the code. Thanks!
SupprimerI confirm that the AsParallel() method works, but the parallelization of the methods is not taken into account (limitation due to Python/PythonNet) that said the Linq have very good performance under Net Core without multiprocessing
SupprimerCe commentaire a été supprimé par l'auteur.
RépondreSupprimer