Skip to content

Instantly share code, notes, and snippets.

@grantjenks
Created December 20, 2016 19:17
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save grantjenks/a06da0db18826be1176c31c95a6ee572 to your computer and use it in GitHub Desktop.
Save grantjenks/a06da0db18826be1176c31c95a6ee572 to your computer and use it in GitHub Desktop.
Performance Comparison of namedtuple, namedlist, and recordclass
import sys
class Record(object):
__slots__ = ()
def __init__(self, *args):
assert len(self.__slots__) == len(args)
for field, value in zip(self.__slots__, args):
setattr(self, field, value)
def __getitem__(self, index):
return getattr(self, self.__slots__[index])
def __len__(self):
return len(self.__slots__)
def __eq__(self, that):
if not isinstance(that, type(self)):
return NotImplemented
return all(item == iota for item, iota in zip(self, that))
def __repr__(self):
args = ', '.join(repr(item) for item in self)
return '%s(%s)' % (type(self).__name__, args)
def __getstate__(self):
return tuple(self)
def __setstate__(self, state):
self.__init__(*state)
from collections import namedtuple
from namedlist import namedlist
from recordclass import recordclass
NTTest = namedtuple('NTTest', 'a b c d e f')
NLTest = namedlist('NLTest', 'a b c d e f')
RCTest = recordclass('RCTest', 'a b c d e f')
class RETest(Record):
__slots__ = 'a', 'b', 'c', 'd', 'e', 'f'
nttest = NTTest(1, 2, 3, 4, 5, 6)
nltest = NLTest(1, 2, 3, 4, 5, 6)
rctest = RCTest(1, 2, 3, 4, 5, 6)
retest = RETest(1, 2, 3, 4, 5, 6)
print 'Attribute Access'
print 'namedtuple'
%timeit nttest.c
print 'namedlist'
%timeit nltest.c
print 'recordclass'
%timeit rctest.c
print 'Record'
%timeit retest.c
print 'Object Size'
print 'namedtuple'
print sys.getsizeof(nttest)
print 'namedlist'
print sys.getsizeof(nltest)
print 'recordclass'
print sys.getsizeof(rctest)
print 'Record'
print sys.getsizeof(retest)
@grantjenks
Copy link
Author

Output:

Attribute Access
namedtuple
The slowest run took 16.59 times longer than the fastest. This could mean that an intermediate result is being cached.
10000000 loops, best of 3: 115 ns per loop
namedlist
10000000 loops, best of 3: 41.5 ns per loop
recordclass
10000000 loops, best of 3: 49 ns per loop
Record
10000000 loops, best of 3: 41.7 ns per loop
Object Size
namedtuple
104
namedlist
96
recordclass
104
Record
96

My advice is to prefer the Record recipe above. Thirty lines of code and it pretty prints, pickles, copies, and unpacks. You can also easily customize to your liking.

@C0oo1D
Copy link

C0oo1D commented May 5, 2019

#python 3.7
import sys
from lib import exec_time  # based on get min time of timeit.Timer and partial().repeat()

from collections import namedtuple
from namedlist import namedlist
from recordclass import recordclass, structclass


class Record(object):
    __slots__ = ()

    def __init__(self, *args):
        assert len(self.__slots__) == len(args)
        for field, value in zip(self.__slots__, args):
            setattr(self, field, value)

    def __getitem__(self, index):
        return getattr(self, self.__slots__[index])

    def __len__(self):
        return len(self.__slots__)

    def __eq__(self, that):
        if not isinstance(that, type(self)):
            return NotImplemented
        return all(item == iota for item, iota in zip(self, that))

    def __repr__(self):
        args = ', '.join(repr(item) for item in self)
        return '%s(%s)' % (type(self).__name__, args)

    def __getstate__(self):
        return tuple(self)

    def __setstate__(self, state):
        self.__init__(*state)


params_count = 6
fields = tuple(hex(_)[1:] for _ in range(params_count))
args = tuple(range(params_count))

NTTest = namedtuple('NTTest', fields)
NLTest = namedlist('NLTest', fields)
RCTest = recordclass('RCTest', fields)
SCTest = structclass('SCTest', fields)


class RETest(Record):
    __slots__ = fields


print('\nCreate:')
print(f'\tnamedtuple: {exec_time(NTTest, *args)}')
print(f'\tnamedlist: {exec_time(NLTest, *args)}')
print(f'\trecordclass: {exec_time(RCTest, *args)}')
print(f'\tstructclass: {exec_time(SCTest, *args)}')
print(f'\tRecord: {exec_time(RETest, *args)}')


nttest = NTTest(*args)
nltest = NLTest(*args)
rctest = RCTest(*args)
sctest = SCTest(*args)
retest = RETest(*args)

print('\nAccess:')
print(f'\tnamedtuple: {exec_time(lambda: nttest.x1)}')
print(f'\tnamedlist: {exec_time(lambda: nltest.x1)}')
print(f'\trecordclass: {exec_time(lambda: rctest.x1)}')
print(f'\tstructclass: {exec_time(lambda: sctest.x1)}')
print(f'\tRecord: {exec_time(lambda: retest.x1)}')

print('\nSize:')
print(f'\tnamedtuple: {sys.getsizeof(nttest)}')
print(f'\tnamedlist: {sys.getsizeof(nltest)}')
print(f'\trecordclass: {sys.getsizeof(rctest)}')
print(f'\tstructclass: {sys.getsizeof(sctest)}')
print(f'\tRecord: {sys.getsizeof(retest)}')

Create:
namedtuple: 496 ns
namedlist: 4.07 µs
recordclass: 422 ns
structclass: 509 ns
Record: 1.94 µs

Access: (lambda used, can be not so valid)
namedtuple: 217 ns
namedlist: 186 ns
recordclass: 180 ns
structclass: 186 ns
Record: 183 ns

Size:
namedtuple: 52
namedlist: 48
recordclass: 36
structclass: 32
Record: 48

@C0oo1D
Copy link

C0oo1D commented May 6, 2019

print('\nAccess:')
print(f'\tnamedtuple: {exec_time(lambda: (nttest.x0, nttest.x1, nttest.x2, nttest.x3, nttest.x4, nttest.x5))}')
print(f'\tnamedlist: {exec_time(lambda: (nltest.x0, nltest.x1, nltest.x2, nltest.x3, nltest.x4, nltest.x5))}')
print(f'\trecordclass: {exec_time(lambda: (rctest.x0, rctest.x1, rctest.x2, rctest.x3, rctest.x4, rctest.x5))}')
print(f'\tstructclass: {exec_time(lambda: (sctest.x0, sctest.x1, sctest.x2, sctest.x3, sctest.x4, sctest.x5))}')
print(f'\tRecord: {exec_time(lambda: (retest.x0, retest.x1, retest.x2, retest.x3, retest.x4, retest.x5))}')

Access:
namedtuple: 621 ns
namedlist: 496 ns
recordclass: 434 ns
structclass: 481 ns
Record: 465 ns

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment