1

I was learning the python. And when comes to the collection module in official library, I found a code snipet of the NamedTuple like:

for i, name in enumerate(field_names):
    template += "        %s = _property(_itemgetter(%d), doc='Alias for field number %d')\n" % (name, i, i)

And it's one part of the code that generated by the NamedTuple. The code generated is listed below:

name = property(itemgetter(0), doc='Alias for field number 0')
age = property(itemgetter(1), doc='Alias for field number 1')

And here is my question:

Itemgetter(0) is a function which needs an object as arguments. But property will not pass any arguments to the itemgetter. So how does this work?

Thank you!

This is the whole code that property is used:

class Person(tuple):
    'Person(name, age)' 

    __slots__ = () 

    _fields = ('name', 'age') 

    def __new__(_cls, name, age):
        'Create new instance of Person(name, age)'
        print sys._getframe().f_code.co_name

        return _tuple.__new__(_cls, (name, age)) 

    @classmethod
    def _make(cls, iterable, new=tuple.__new__, len=len):
        'Make a new Person object from a sequence or iterable'
        print sys._getframe().f_code.co_name

        result = new(cls, iterable)
        if len(result) != 2:
            raise TypeError('Expected 2 arguments, got %d' % len(result))
        return result 

    def __repr__(self):
        'Return a nicely formatted representation string'
        print sys._getframe().f_code.co_name

        return 'Person(name=%r, age=%r)' % self 

    def _asdict(self):
        'Return a new OrderedDict which maps field names to their values'
        print sys._getframe().f_code.co_name

        return OrderedDict(zip(self._fields, self)) 

    def _replace(_self, **kwds):
        'Return a new Person object replacing specified fields with new values'
        print sys._getframe().f_code.co_name

        result = _self._make(map(kwds.pop, ('name', 'age'), _self))
        if kwds:
            raise ValueError('Got unexpected field names: %r' % kwds.keys())
        return result 

    def __getnewargs__(self):
        'Return self as a plain tuple.  Used by copy and pickle.'
        print sys._getframe().f_code.co_name

        return tuple(self) 

    name = property(itemgetter(0), doc='Alias for field number 0')
    age = property(itemgetter(1), doc='Alias for field number 1')

1 Answers1

4

itemgetter is not a function, it's a class whose instances are callable (cf the FineManual). The property instance will call it with the current object as argument (that's what properties are for).

Let's summarize... assuming this:

point = tuple(1, 2)
getx = itemgetter(0)

passing point to getx() will return point[0] (actually, point.__getitem__[0] for which point[0] is syntactic sugar)

Now if we subclass tuple and add a property:

class Point(tuple):
    @property
    def x(self):
        return self[0]

The @decorator syntax is actually syntactic sugar for :

class Point(tuple):
    def x(self):
        return self[0]
    x = property(fget=x)

so the function x becomes the fget attribute of the property instance, and the name x in the class statement's namespace is rebound to this property instance.

Now let's create a Point instance:

point = Point(1, 2)

then when evaluating point.x, the attributes lookup rules will find the "x" property object on Point (actually on point.__class__), notice that it has a __get__() method, and according to the descriptor protocol will return the result of Point.x.__get__(point, Point.__class__). Since property.__get__(obj, cls) is mainly implemented as return self.fget(obj), this will return the result of the x function called with point as self param. IOW:

point.x

is equivalent to

Point.x.__get__(point, point.__class__)

which is equivalent to

Point.x.fget(point)

which is equivalent to (NB : here 'x' refers the x function that has been passed as fget argument to property, not to Point.x

x(point)

which is equivalent to

point[0]

And since itemgetter(0)(point) is equivalent to point[0], one can see how x = property(itemgetter(0)) works.

bruno desthuilliers
  • 75,974
  • 6
  • 88
  • 118
  • Thanks Bruno. I attached the code that generated by NamedTuple. Do you mean that the whole Person object will be pasted to the itemgetter(0) or itemgetter(1)function as arguments? If so, which item will be considered as item 0 or item 1? Because I think it should pass the _fields as the argument. Thank you. – Zhang Yunjie Sep 18 '13 at 22:51
  • Hi, is there anyone who can kindly answer my questions? Thanks! – Zhang Yunjie Sep 22 '13 at 12:02
  • Nothing will be "pasted" and once again, `itemgetter` is NOT a function. You first need to understand Python's object model and attribute lookup rules and specially the descriptor protocol (which provides the support for computed attributes, including methods and properties), and no one is going to explain the whole thing here since it's already well documented. – bruno desthuilliers Sep 23 '13 at 07:28
  • Yes, but itemgetter(0) will return a callable object which needs an iterable object as its parameters, right? But usually, the callable passed to property as fget argument is a function with no parameter. But in this case, the callable returned by itemgetter(0) - let's call it f, needs a iterable item. So who will do this and how? In Python document, it defines property equvilent to: – Zhang Yunjie Sep 23 '13 at 15:07
  • def itemgetter(*items): if len(items) == 1: item = items[0] def g(obj): return obj[item] else: def g(obj): return tuple(obj[item] for item in items) return g – Zhang Yunjie Sep 23 '13 at 15:08
  • The function g will take an obj as arguments. And in Property class, the __get__ function be defined as def __get__(self, obj, objtype=None). But we usually call the property as C.x, which does not pass any arguments as obj. So how does it work? – Zhang Yunjie Sep 23 '13 at 15:10
  • I don't know if I looked at the wrong documentation, but itemgetter does seem to be a function on the operator module: "The operator module exports a set of efficient functions corresponding to the intrinsic operators of Python". Anyway, that aside, while there's good documentation on itemgetter or property, they don't make the construct property(itemgetter(0)) any obvious. And google points to this question, which in my opinion deserves an answer that actually explains the concept. – Nagev Sep 08 '17 at 11:14
  • @Nagev once you understand `property` and `itemgetter` (which, I insist, is a class, not a function), the "construct" is obvious. – bruno desthuilliers Sep 08 '17 at 11:42
  • @Nagev edited my answer - but you will still have to understand `property` (and globally the descriptor protocol) etc to fully understand it, which is why I didn't bother going to such depth - if you understand Python's object model all of this is obvious, else it's confusing at best. – bruno desthuilliers Sep 08 '17 at 12:08
  • That's correct, itemgetter is indeed a class, confirmed by looking at the library code. The documentation should be updated. – Nagev Sep 08 '17 at 18:35