Click here to Skip to main content
Click here to Skip to main content

Tagged as

Python’s namedtuple… for .Net!

, 20 Jan 2014
Rate this:
Please Sign up or sign in to vote.
One of the things I have seen people applaud about Python is its “namedtuple” class. If you could describe this in terms of .Net’s Tuple (and ) it would be to basically say that it’s the same as Tuple, but if instead of “Item1” on Tuple you got “Name” or “Age” or whatever […]CodeProject-->

One of the things I have seen people applaud about Python is its "namedtuple" class. If you could describe this in terms of .Net’s Tuple<T> (and <T1, T2, etc>) it would be to basically say that it’s the same as Tuple<T>, but if instead of "Item1" on Tuple<T> you got "Name" or "Age" or whatever other meaningful name for the property (instead of ‘Item1’). And, of course, if you weren’t limited to at most 8 items on the Tuple object.

That being said, let’s have a look at how namedtuple works in practice:

C:\Users\Brandon>python
Python 2.7.6 (default, Nov 10 2013, 19:24:18) [MSC v.1500 32 bit (Intel)] on win
32
Type "help", "copyright", "credits" or "license" for more information.
>>> from collections import namedtuple
>>> PersonClass = namedtuple(‘person’, ‘name, age, gender’)
>>> PersonClass
<class ‘__main__.person’>
>>> p = PersonClass(name=’brandon’, age=32, gender=’m')
>>> p
person(name=’brandon’, age=32, gender=’m')
>>> p.name
‘brandon’
>>>

Let’s go over what you’re seeing in detail as it relates to what you may, or may not know, about languages and .Net in particular:

The call to ‘namedtuple’ creates a new class in the type system called ‘person’ with properties ‘name’, ‘age’, and ‘gender’ on it. The return value from this call is assigned to ‘PersonClass’ variable which effectively becomes an alias for the new class within the type system. When you want to use this newly created class, you reference it with ‘PersonClass’ – a usual practice in this area is to assign the return value to the same name as the class you created. In other words ‘person’ instead of ‘PersonClass’. I did this here to illustrate the difference.

After you have an alias to this new type, you get to use it just like you would any other type. I create a new instance of this type assigned to variable ‘p’ by simply using the constructor that is created for this type. The default constructor for types created by ‘namedtuple’ is one that takes a value for each of the properties you gave the new type. So in this example, it’s a constructor that takes values for ‘name’, ‘age’, and ‘gender’. If you try to create an instance without specifying them all, you’ll get an error.
After creating my new instance with its values, you get to reference those values by the names you assigned to the properties when you created the new class in the type system with the call to ‘namedtuple’.

Indeed it is pretty slick. So… let’s see what we can do with .Net shall we?

My first thought here was that Dynamic objects in general somewhat "solve" this issue. What they don’t provide, however, is the "definition" and the ability to add properties on the fly. At least, not at first glance.

I knew about ExpandoObject from a few years back, and decided to see what it could do for me. If you haven’t seen this before, it’s pretty mind blowing when you first see what you can do with it. Here’s a quick example:

   1: dynamic coolStuff = new System.Dynamic.ExpandoObject();
   2: coolStuff.name = "Brandon";
   3: coolStuff.age = 32;
   4: coolStuff.gender = 'M';
   5:
   6: Console.WriteLine("name: " + coolStuff.name);

And the output:

name: Brandon

"Whoa, what?" Yeah – the beauty of .Net’s DLR (Dynamic Language Runtime). It’s pretty awesome. ExpandoObject is a special type that says "if you assign something to a property and I don’t have it already, I’ll just create the property for it on myself and let you get at it later". It’s a property bag on steroids. The biggest key to it, though, is that you have to cast it to a ‘dynamic’ object in order to really use it.

So this is pretty close to namedtuple, but not quite there. We want the properties there for usage at creation time, and we’d also like the other niceties that Python provides like _make, _replace, and _fields to do some special things.

Unfortunately, ExpandoObject can’t be inherited from (sealed type) but there is another type that the DLR provides that lets you do these cool dynamic typing things; DynamicObject.

This type is one that hooks in to what the DLR does at runtime and, more importantly, lets you react to them. In the case of namedtuple, what you’re reacting to are the Get/Set calls used in property fetching and assignment.

But let’s roll back a second. At face value what *really* are we playing with here? It’s a Dictionary. More specifically, Dictionary<string, object>. It’s a collection of values that you can look up by a string. The only difference is you get to do it in good old-fashioned OO ways, by using property calls. That being said, if you were to create a class that used DynamicObject to react to type system calls, what would you back it with? A Dictionary.

Let’s get down to brass tax and see how this can be done:

   1: public class NamedTuple : DynamicObject
   2: {
   3:     private readonly IDictionary<string, object> _contained = new Dictionary<string, object>();
   4:
   5:     /// <summary>
   6:     /// Provides the implementation for operations that get member values. Classes derived from the <see cref="T:System.Dynamic.DynamicObject" /> class can override this method to specify dynamic behavior for operations such as getting a value for a property.
   7:     /// </summary>
   8:     /// <param name="binder">Provides information about the object that called the dynamic operation. The binder.Name property provides the name of the member on which the dynamic operation is performed. For example, for the Console.WriteLine(sampleObject.SampleProperty) statement, where sampleObject is an instance of the class derived from the <see cref="T:System.Dynamic.DynamicObject" /> class, binder.Name returns "SampleProperty". The binder.IgnoreCase property specifies whether the member name is case-sensitive.</param>
   9:     /// <param name="result">The result of the get operation. For example, if the method is called for a property, you can assign the property value to <paramref name="result" />.</param>
  10:     /// <returns>
  11:     /// true if the operation is successful; otherwise, false. If this method returns false, the run-time binder of the language determines the behavior. (In most cases, a run-time exception is thrown.)
  12:     /// </returns>
  13:     public override bool TryGetMember(GetMemberBinder binder, out object result)
  14:     {
  15:         var contAsDict = (IDictionary<string, object>)_contained;
  16:         return contAsDict.TryGetValue(binder.Name, out result);
  17:     }
  18:
  19:     /// <summary>
  20:     /// Provides the implementation for operations that set member values. Classes derived from the <see cref="T:System.Dynamic.DynamicObject" /> class can override this method to specify dynamic behavior for operations such as setting a value for a property.
  21:     /// </summary>
  22:     /// <param name="binder">Provides information about the object that called the dynamic operation. The binder.Name property provides the name of the member to which the value is being assigned. For example, for the statement sampleObject.SampleProperty = "Test", where sampleObject is an instance of the class derived from the <see cref="T:System.Dynamic.DynamicObject" /> class, binder.Name returns "SampleProperty". The binder.IgnoreCase property specifies whether the member name is case-sensitive.</param>
  23:     /// <param name="value">The value to set to the member. For example, for sampleObject.SampleProperty = "Test", where sampleObject is an instance of the class derived from the <see cref="T:System.Dynamic.DynamicObject" /> class, the <paramref name="value" /> is "Test".</param>
  24:     /// <returns>
  25:     /// true if the operation is successful; otherwise, false. If this method returns false, the run-time binder of the language determines the behavior. (In most cases, a language-specific run-time exception is thrown.)
  26:     /// </returns>
  27:     public override bool TrySetMember(SetMemberBinder binder, object value)
  28:     {
  29:         var contAsDict = (IDictionary<string, object>)_contained;
  30:         try
  31:         {
  32:             contAsDict[binder.Name] = value;
  33:             return true;
  34:         }
  35:         catch
  36:         {
  37:             return false;
  38:         }
  39:     }
  40: }

And boom. By inheriting from DynamicObject we’re letting .Net know that we intend on intercepting the DLR’s calls while it’s resolving the code at run time. Specifically the Get and Set Member calls.

And how do we use this gem?

dynamic nt = new NamedTuple();
nt.name = "Brandon";

I know, it looks a lot like ExpandoObject. But the important thing to know here is that I can’t change how ExpandoObject works, is constructed, other functionality on it, nothing. But I can change my new NamedTuple. When the ‘name’ property is assigned, the code that’s executed is the code in TrySetMember above. You can see that all that does is store the value in my underlying dictionary. Similarly when it’s retrieved, TryGetMember is executed and the value’s pulled back out.
Interestingly enough, if you look at the documentation of Python’s namedtuple, you’ll see it provides methods to convert a namedtuple in to a dictionary – it’s now obvious to see why.

So let’s look at providing the exact functionality of Python’s variant in to ours. The first thing you see is that there’s a nice constructor to specify the property names. Easy enough:

/// <summary>
/// Initializes a new instance of the <see cref="NamedTuple"/> class.
/// </summary>
/// <param name="propertyNames">The property names.</param>
public NamedTuple(IEnumerable<string> propertyNames)
{
    foreach (var property in propertyNames)
    {
        _contained.Add(property, null);
    }
}

And let’s go one step further and give our users to populate the names and the values just for convenience sake:

/// <summary>
/// Initializes a new instance of the <see cref="NamedTuple"/> class.
/// </summary>
/// <param name="propertiesAndValues">The properties and values.</param>
public NamedTuple(IDictionary<string, object> propertiesAndValues)
{
    _contained = propertiesAndValues;
}

Now the equivalent to Python’s _make:

/// <summary>
/// Makes the tuple by populating the fields with the given contents
/// in the order they are specified and the order the fields were added
/// to this instance
/// </summary>
/// <param name="contents">The contents with which to populate the fields</param>
public void Make(IEnumerable<object> contents)
{
    for (int i = 0; i < contents.Count(); i++)
    {
        _contained[_contained.Keys.ElementAt(i)] = contents.ElementAt(i);
    }
}
[TestMethod]
public void Make()
{
    dynamic t = new NamedTuple(new[] { "one", "two", "three" });
    t.Make(new object[] { 3, 2, 1 });

    Assert.AreEqual(3, t.one);
    Assert.AreEqual(2, t.two);
    Assert.AreEqual(1, t.three);
}

_fields:

/// <summary>
/// Gets the fields aka Property names that can be used with this instance
/// </summary>
public ICollection<string> Fields
{
    get
    {
        return _contained.Keys;
    }
}
[TestMethod]
public void Fields()
{
    var t = new NamedTuple(new[] { "one", "two", "three" });
    
    Assert.IsTrue(t.Fields.Contains("one"));
        Assert.IsTrue(t.Fields.Contains("two"));
            Assert.IsTrue(t.Fields.Contains("three"));
            }

_replace:

/// <summary>
/// Replaces the specified field with the given value
/// </summary>
/// <param name="field">The field of which to replace the value</param>
/// <param name="value">The new value.</param>
/// <returns>A new instance of NamedTuple with the <paramref name="field"/>'s value replaced with <paramref name="value"/></returns>
/// <exception cref="System.ArgumentException">Field not found in tuple;field</exception>
public NamedTuple Replace(string field, object value)
{
    if (!_contained.ContainsKey(field))
    {
        throw new ArgumentException("Field not found in tuple", "field");
    }

    var newDict = new Dictionary<string, object>(_contained);
    newDict[field] = value;

    return new NamedTuple(newDict);
}

The important note about _replace is that you get a new instance back. Same as Python.

[TestMethod]
public void Replace()
{
    dynamic t = new NamedTuple(new[] { "one", "two", "three" });
    t.Make(new object[] { 3, 2, 1 });
        
    dynamic newT = t.Replace("one", 4);
    Assert.AreEqual(4, newT.one);
    Assert.AreNotEqual(t.one, newT.one);
}

Since we can see how similar ExpandoObject and NamedTuple are, let’s give our users an easy way to convert an ExpandoObject to our NamedTuple:

/// <summary>
/// Converts an <seealso cref="ExpandoObject"/> to a NamedTuple
/// </summary>
/// <param name="eo">The expando object.</param>
/// <returns></returns>
public static implicit operator NamedTuple(ExpandoObject eo)
{
    return new NamedTuple((IDictionary<string, object>)eo);
}

And since we know Python provides the ability to take a NamedTuple instance and give the user back a Dictionary:

/// <summary>
/// Converts this instance to a <seealso cref="Dictionary{string,object}"/>
/// </summary>
/// <param name="me">The NamedTuple instance to convert.</param>
/// <returns></returns>
public static implicit operator Dictionary<string, object>(NamedTuple me)
{
    return (Dictionary<string, object>)me._contained;
}
[TestMethod]
public void AsDict()
{
    var t = new NamedTuple(new[] { "one", "two", "three" });
    t.Make(new object[] { 3, 2, 1 });

    var d = (Dictionary<string, object>)t;
    Assert.IsInstanceOfType(d, typeof(Dictionary<string, object>));

    var dyn = (dynamic)t;
    Assert.AreEqual(dyn.one, d["one"]);
    Assert.AreEqual(dyn.two, d["two"]);
    Assert.AreEqual(dyn.three, d["three"]);
}

And there you have it. The full class is available on NuGet as ‘namedtuple’.

The only couple of things I didn’t implement here are a few of the more funky constructors that Python’s has – I didn’t see them as too valuable past what we have here, but if you find you would like them or make changes to this class to add them don’t hesitate to contact me so I can update the NuGet package with your changes!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

BC3Tech
Software Developer (Senior)
United States United States
No Biography provided
Follow on   Twitter   Google+

Comments and Discussions

 
GeneralUseful! PinprofessionalBrisingr Aerowing22-Jan-14 13:32 
GeneralRe: Useful! PinmemberBC3Tech31-Mar-14 10:12 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140827.1 | Last Updated 20 Jan 2014
Article Copyright 2014 by BC3Tech
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid