Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

create custom namedtuple type with extra features

I'd like to create my own type of build-in namedtuple that has some extra features. Let's say we create a class:

from collections import namedtuple
MyClass = namedtuple('MyClass', 'field1 field2')

It`s immutable, readable and simple. Now I can create instances of MyClass:

myobj = MyClass(field1 = 1, field2 = 3.0)
print(myobj.field1, myobj.field2)

My extra requirement is when instance is created I'd like to check if field1 is int type and field2 is float. For example if user try to create MyClass instance:

obj = MyClass(field1 = 1, field2 = 3.0) # instantiates ok
obj1 = MyClass(field1 = 'sometext', field2 = 3.0) # raises TypeError

I tried to make a customized namedtuple that can validate datatypes (MyClass should be immutable) something like.:

MyClass = modifiednamedtuple('MyClass', 'field1 field2', (int, float) )

but got stuck :(. namedtuple is function (cannot be a baseclass for modifiednamedtuple), my experiments with metaclasses failed.

Any tips or suggestions?

ok, I came up with a solution that might be not "clean" or pythonic. It works except that my objects are not immutable. How to make them immutable? Any suggestions how to make it more clean and redable?

Here is my code.:

def typespecificnamedtuple(name, *attr_definitions):

    def init(self, *args, **kwargs):
        valid_types = dict(attr_definitions) # tuples2dict
        for attr_name, value in kwargs.items():
            valid_type = valid_types[attr_name]
            if not isinstance(value, valid_type):
                raise TypeError('Cannot instantiate class '+ self.__name__+
                    '. Inproper datatype for '+ attr_name + '=' + str(value)+ 
                        ', expected '+str(valid_type) )
            setattr(self, attr_name, value)


    class_dict = {'__init__' : init, '__name__' : name}
    for attr_def in attr_definitions:
        class_dict[attr_def[0]] = attr_def[1] # attr_def is ('name', <type int>)

    customType = type(name, (object, ), class_dict )
    return customType

if __name__ == '__main__':
    MyClass = typespecificnamedtuple('MyClass', ('value', int), ('value2', float)  )
    mc = MyClass(value = 1, value2 = 3.0)
    mc.something = 1    # this assigment is possible :( how to make immutable?
    print(mc.__name__, mc.value, mc.value2, mc.something)
    mc1 = MyClass(value = 1, value2 = 'sometext')   # TypeError exception is raised

and console output.:

MyClass 1 3.0 1
Traceback (most recent call last):
  File "/home/pawel/workspace/prices/prices.py", line 89, in <module>
    mc1 = MyClass(value = 1, value2 = 'sometext')   # TypeError exception is raised
  File "/home/pawel/workspace/prices/prices.py", line 70, in init
    ', expected '+str(valid_type) )
TypeError: Cannot instantiate class MyClass. Inproper datatype for value2=sometext, expected <class 'float'>
like image 915
Maciejski Pawel Avatar asked Feb 09 '17 20:02

Maciejski Pawel


People also ask

Can you modify a Namedtuple?

Since a named tuple is a tuple, and tuples are immutable, it is impossible to change the value of a field. In this case, we have to use another private method _replace() to replace values of the field. The _replace() method will return a new named tuple.

How do I change attributes in Namedtuple?

Namedtuples are immutable objects, so you cannot change the attribute values.

Can Namedtuple have methods?

index() , namedtuple classes also provide three additional methods and two attributes. To prevent name conflicts with custom fields, the names of these attributes and methods start with an underscore. In this section, you'll learn about these methods and attributes and how they work.

Can Namedtuple be pickled?

@Antimony: pickle handles namedtuple classes just fine; classes defined in a function local namespace not so much.


1 Answers

namedtuple isn't a class, as you note; it's a function. But it's a function that returns a class. Thus, you can use the result of the namedtuple call as a parent class.

Since it is immutable, a namedtuple is initialized in __new__ rather in in __init__.

So something like this, perhaps:

MyTuple = namedtuple('MyTuple', 'field1 field2')

class MyClass(MyTuple):
    def __new__(cls, field1, field2):
        if not isinstance(field1, int):
            raise TypeError("field1 must be integer")
        # accept int or float for field2 and convert int to float
        if not isinstance(field1, (int, float)):
            raise TypeError("field2 must be float")
        return MyTuple.__new__(cls, field1, float(field2))
like image 167
kindall Avatar answered Sep 18 '22 16:09

kindall