<jupyter_start><jupyter_text>Working With the Pythonnet Library IntroductionIn this tutorial, we are going begin our exploration of the Python for .NET (`pythonnet`) package, a powerful tool that allows us to access the CLR from Python. To fully understand this topic, we will have to give you some background and help you understand why it was developed.Additionally, throughout this process, I want you to keep a couple of ideas in mind:1. How can we use programs and objects written in other lanaguages?2. If we could use programs from other langauges, what sort of rules would we need to define?3. How would the process of taking one object, written in one language, and using it in another language? What is Pythonnet?Coming straight from the source, the `pythonnet` library is defined as follows:*Python for .NET (pythonnet) is a package that gives Python programmers nearly seamless integration with the .NET 4.0+ Common Language Runtime (CLR) on Windows and Mono runtime on Linux and OSX. Python for .NET provides a powerful application scripting tool for .NET developers. Using this package you can script .NET applications or build entire applications in Python, using .NET services and components written in any language that targets the CLR (C, VB.NET, F, C++/CLI).*In a concise summary, the `pythonnet` package gives us access to the CLR environment on Windows. Having access to this environment allows us to access the .Net framework and it's components like window forms, and the excel interop library.Hopefully, after reading that definition you started to ask some more questions. Well, let's start going through some of your questions. What is the Common Language Runtime (CLR)?Againg, coming straight from the source, `CLR` is as defined as follows:*The .NET Framework provides a run-time environment called the common language runtime, which runs the code and provides services that make the development process easier.Compilers and tools expose the common language runtime's functionality and enable you to write code that benefits from this managed execution environment. Code that you develop with a language compiler that targets the runtime is called managed code; it benefits from features such as cross-language integration, cross-language exception handling, enhanced security, versioning and deployment support, a simplified model for component interaction, and debugging and profiling services.*In essence, CLR helps manage the execution of .NET programs, regardless of the .NET language they were written in. Additionally, CLR implements the Virtual Execution System which is defined under the Common Langauge Infastructure (CLI). What is the Common Language Infastructure (CLI)?CLI can be defined as follows:*The Common Language Infrastructure (CLI) is an open specification (technical standard) developed by Microsoft and standardized by ISO and Ecma that describes executable code and a runtime environment that allows multiple high-level languages to be used on different computer platforms without being rewritten for specific architectures. This implies it is platform agnostic.*The CLI defines four specifications that make the CLR possible:1. The Common Type System (CTS)    - A set of data types and operations that are shared by all CTS-compliant programming languages.2. The Metadata    - Information about program structure is language-agnostic, so that it can be referenced between languages and tools, making it easy to work with code written in a language the developer is not using.3. The Common Language Specification (CLS)    - A set of base rules to which any language targeting the CLI should conform in order to interoperate with other CLS-compliant languages. The CLS rules define a subset of the Common Type System.4. The Virtual Execution System (VES)    - The VES loads and executes CLI-compatible programs, using the metadata to combine separately generated pieces of code at runtime.    All compatible languages compile to Common Intermediate Language (CIL), which is an intermediate language that is abstracted from the platform hardware. When the code is executed, the platform-specific VES will compile the CIL to the machine language according to the specific hardware and operating system. What are the benefits of the Common Lanaguage Runtime Environment?1. Performance improvements.2. The ability to easily use components developed in other languages.3. Extensible types provided by a class library.4. Language features such as inheritance, interfaces, and overloading for object-oriented programming.5. Support for explicit free threading that allows creation of multithreaded, scalable applications.6. Support for structured exception handling.7. Support for custom attributes.8. Garbage collection.9. Use of delegates instead of function pointers for increased type safety and security. For more information about delegates, see Common Type System. How will we use Pythonnet in this tutorial?In this tutorial, we will keep things simple and work with some of the built in system libraries found in the .Net Framework. More specifically, we will work with the `System.Forms` library and go over common things we may need to do in the library.With all of that being said, let's get coding!*** Installing PythonnetTo install `pythonnet` on your system, run the following command:`pip install pythonnet`After that you can import the libary by writing the following:<jupyter_code># import the CLR library
import clr<jupyter_output><empty_output><jupyter_text>Importing Modules and Loading AssembliesAfter you've imported the `clr` library you can begin to import CLR Namespaces and Types. For CLR namespaces, importing them is just like importing a normal python package.`from System import String`or`from System.Collections import *`If the assembly you wish to import from is not currently loaded, no worries simply use the `AddReference` method and load the assembly that way. Here is an example of loading an assembly:`import clr``clr.AddReference('System.Windows.Forms')`Once, you've added the reference you can begin to import Types just like up above.`from System.Windows.Forms import Form`This will load the `Form` Type into Python.Now that we know how to import modules and load assemblies we can move on to the next section, Generics. GenericsBecause we are now able to access the world of .Net we get access to a new arsenal of tools that we can use. One of those tools are called `Generics`. A `Generic`, in simple terms, allow for the idea of something called Type Parameters. A Type Parameter is just a way of building a class or defining a method in such a way that it delays the specification of one's type until the class is instantiated or the method is called by the client code.Generic classes and methods combine reusability, type safety and efficiency in a way that their non-generic counterparts cannot.`Pythonnet` supports generic types. A generic type must be bound to create a concrete type before it can be instantiated. Generic types also support the subscript syntax to create bound types.Let's walk through an example and create a `Dictionary` generic.<jupyter_code># add a reference to the System.Collections Assembly
clr.AddReference('System.Collections')

# generics are usually used with collections, so let's import the 'Dictionary' Generic
from System.Collections.Generic import Dictionary

# let's also import the different data types we need.
from System import *

'''
    EXAMPLE ONE:
    ------------
    For the Dictionary Generic, we can specify what Type we want the Key and Value to be.
    This helps maintain a little more control over the process. In the first example,
    I'm creating a Dictionary Generic that requires the Key to be of Type String and 
    the Value to be of Type String.
'''
generic_dict_1 = Dictionary[String, String]()


'''
    EXAMPLE TWO:
    ------------
    In the second example, I'm creating a Dictionary Generic that requires the Key to 
    be of Type String and the Value to be of Type Int32.
'''
generic_dict_2 = Dictionary[String, Int32]()


'''
    EXAMPLE THREE:
    --------------
    In the third example, I'm creating a Dictionary Generic that requires the Key to 
    be of Type String and the Value to be of Type Type(Object).
'''
generic_dict_3 = Dictionary[String, Type]()


# let's add a value to our Dictionary Generic 2
generic_dict_1.Add("myKey1","myValue1")
generic_dict_1.Add("myKey2","myValue2")

# finally, let's grab a value.
print('For "myKey1", the value is {}'.format(generic_dict_1["myKey1"]))

# let's add a value to our Dictionary Generic 2
generic_dict_2.Add("myKey1", 100)
generic_dict_2.Add("myKey2", 200)

# finally, let's grab a value.
print('For "myKey1", the value is {}'.format(generic_dict_2["myKey1"]))

# here is the neat thing about generics. Even though I'm passing through a string it'll be converted to int.
generic_dict_2.Add("myKey3", "200")

print('For "myKey3", the value is {}'.format(generic_dict_2["myKey3"]))
print('For "myKey3", the type of the value is {}'.format(type(generic_dict_2["myKey3"])))<jupyter_output>For "myKey1", the value is myValue1
For "myKey1", the value is 100
For "myKey3", the value is 200
For "myKey3", the type of the value is <class 'int'><jupyter_text>Fields & PropertiesTime to introduce another tool, `Fields`. A `Field` is a variable of any type that is declared directly in a class or struct. `Fields` are members of their containing type. Generally, you should use fields only for variables that have private or protected accessibility. Data that your class exposes to client code should be provided through methods, properties and indexers. Fields typically store the data that must be accessible to more than one class method and must be stored for longer than the lifetime of any single method.At this point, we should know what a property is so I won't go over that. You can `get` and `set` fields and properties of CLR objects just as if they were regular attributes.<jupyter_code># import the Environment Type
from System import Environment

# the Envrionment Type has a property called `MachineName`
my_machine_name = Environment.MachineName

# print out my Machine Name
print('My Machine is Called: {}'.format(my_machine_name))


'''
    The Environment Type also has an attribute called `ExitCode` which is just the ExitCode of a process.
''' 

# let's change it to 1 to indicate the process has not completed successfully.
Environment.ExitCode = 1

# let's change it back to 0, to indicate the process has completed successfully.
Environment.ExitCode = 0<jupyter_output>My Machine is Called: DESKTOP-MOA8O6M<jupyter_text>ArraysYou can store multiple variables of the same type in an array data structure. You declare an array by specifying the type of its elements.Let's walkthrough declaring `Arrays` in `Pythonnet`<jupyter_code># import the Array Type
from System import Array

'''
    Single-Dimension Array.
'''

# declare a single-dimension array, that will only contain numbers, and contains ten elements that are all 0.
array_1 = Array.CreateInstance(int,10)

# print your newly created array.
print("Here is my Array: {}".format(list(array_1)))

# reassign value
array_1[0] = 100

# grab value from element 1, since we are 0-based.
print("Here is the value of my first element: {}".format(array_1[0]))

# here is an alternative way to declaring a single 
array_2 = Array[int](range(10))

# print your newly created array.
print("Here is my Array: {}".format(list(array_2)))


'''
    Multi-Dimension Array.

'''

# declare a multi dimension arrary, that will contain only numbers, and be 2 by 3.
multi_array_1 = Array.CreateInstance(int, 2, 3)

# assign a value to row 0, column 1
multi_array_1[0, 1] = 100

# assign a value to row 0, column 1
multi_array_1[1, 1] = 100

# assign a value to row 0, column 2
multi_array_1[0, 2] = 300

# print your newly created array.
print("Here is my multi-dimensional Array: {}".format(list(multi_array_1)))

# here is an alternative method.
multi_array_2 = Array[Array[int]]( ( (1, 2), (3, 4) ) )

# print your newly created array.
print("Here is my multi-dimensional Array: {}".format([list(array) for array in list(multi_array_2)]))<jupyter_output>Here is my Array: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
Here is the value of my first element: 100
Here is my Array: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Here is my multi-dimensional Array: [0, 100, 300, 0, 100, 0]
Here is my multi-dimensional Array: [[1, 2], [3, 4]]<jupyter_text>CollectionsManaged arrays and managed objects that implement the IEnumerable interface can be iterated over using the standard iteration Python idioms.<jupyter_code># grab the current domain, this belongs under the 'System' namespace.
domain = AppDomain.CurrentDomain

# get all the assemblies in the domain, this returns a collection that we can loop through.
for assembly in domain.GetAssemblies():
    
    # grab the assembly name, using the GetName() method.
    assembly_name = assembly.GetName()
    
    print('-'*10)
    print(assembly_name)<jupyter_output>----------
mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
----------
clrmodule, Version=2.4.0.0, Culture=neutral, PublicKeyToken=null
----------
Python.Runtime, Version=2.4.0.0, Culture=neutral, PublicKeyToken=null
----------
System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
----------
System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
----------
__CodeGenerator_Assembly, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
----------
e__NativeCall_Assembly, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
----------
System.Configuration, Version=4.0.0.0, Culture=neutral, PublicKeyToken=<KEY>
----------
System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
----------
System.Collections, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a<jupyter_text>Using IndexersAt this point, we've used indexers multiple times, but just to be consistent let's dedicate a section to it. If a managed object implements one or more indexers, you can call the indexer using standard Python indexing syntax.<jupyter_code># key method
print("Using the key method, I got the value: {}".format(generic_dict_2['myKey1']))

# index method
print("Using the index method, I got the value: {}".format(array_1[0]))

# index method, multi.
print("Using the index method multi, I got the value: {}".format(multi_array_1[0,2]))<jupyter_output>Using the key method, I got the value: 100
Using the index method, I got the value: 100
Using the index method multi, I got the value: 300
{'fraction_non_alphanumeric': 0.07591480065537957, 'fraction_numerical': 0.019456581103222282, 'mean_word_length': 4.684516880093131, 'pattern_counts': {'":': 0, '<': 22, '<?xml version=': 0, '>': 22, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 10, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '6', 'dir': 'jupyter-structured-clean-dedup', 'id': '214189', 'license': 'MIT', 'n_tokens_mistral': 3986, 'n_tokens_neox': 3740, 'n_words': 1988, 'path': 'python/python-pythonnet/Pythonnet 2 - Fundamentals.ipynb', 'repo_name': 'nsethtyler/sigma_coding_youtube', 'size': '14659'}