2

Let's say I have a class like this:

class Foo(namedtuple('Foo', ['f1'])):
    def f1(self):
        print('Default f1')

    def __new__(cls, f1=f1):
        return super().__new__(cls, f1)

And let's say I create a Foo object later on and choose to override the method definition for f1, like so:

def my_f1():
    print("My f1")

foo = Foo(f1=my_f1)

If I then try:

foo.f1()

I get:

Default f1

I'm trying to get "My f1" to print, of course. I also want f1 to be optional. What's going on here? Is it possible to define a default implementation for a method in a namedtuple and then override it in new?

LateCoder
  • 2,163
  • 4
  • 25
  • 44
  • 1
    It seems to me you want a namedtuple with a default value for a field. In this case, the default value is a callable, but it doesn't make much difference: http://stackoverflow.com/questions/11351032/named-tuple-and-optional-keyword-arguments – Andrea Corbellini Sep 25 '15 at 15:21
  • @AndreaCorbellini: it makes a huge difference because the property handling the attribute is clobbered by the method. – Martijn Pieters Sep 25 '15 at 15:50
  • @MartijnPieters: it makes a difference only if you want to declare `f1` inside `class Foo`. But this overly complicates things (see your own answer for the details :P). Declaring `f1` outside `class Foo` and setting it as a default argument makes things much easier. – Andrea Corbellini Sep 25 '15 at 16:31
  • @AndreaCorbellini: but the OP is expecting `foo.f1()` to work, from the `f1` argument when creating the instance. It doesn't matter if `self[0]` is a default `None` or a default external function.. – Martijn Pieters Sep 25 '15 at 16:33
  • @MartijnPieters: I'm not sure I've understood your last comment. BTW, I posted an answer. – Andrea Corbellini Sep 25 '15 at 16:38
  • @AndreaCorbellini: right, setting a function as a default works too, just different. – Martijn Pieters Sep 25 '15 at 16:49

2 Answers2

5

You can't have both a method and a slot use the same name. Named tuples use slots, and these are implemented as property objects in the same namespace as methods. By defining a method f1 you clobbered the f1 property:

>>> from collections import namedtuple
>>> namedtuple('Foo', ['f1']).f1
<property object at 0x1069764c8>
>>> class Foo(namedtuple('Foo', ['f1'])):
...     def f1(self):
...         print('Default f1')
... 
>>> Foo.f1
<unbound method Foo.f1>

The property object simply returns self[0]. So either access the f1 value by position:

class Foo(namedtuple('Foo', ['f1'])):
    def f1(self):
        if self[0]:
            return self[0]()
        print('Default f1')

or give your attribute a different name, and have the f1 method delegate to that:

class Foo(namedtuple('Foo', ['f1_callable'])):
    def f1(self):
        if self.f1_callable:
            return self.f1_callable()
        print('Default f1')

Demo:

>>> def my_f1():
...     print("My f1")
... 
>>> class Foo(namedtuple('Foo', ['f1'])):
...     def f1(self):
...         if self[0]:
...             return self[0]()
...         print('Default f1')
... 
>>> foo = Foo(my_f1)
>>> foo.f1()
My f1
>>> foo = Foo(None)
>>> foo.f1()
Default f1
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
0

Avoid declaring f1 inside class Foo. Instead, declare it at module level, and use it as a default argument for your namedtuple:

def f1():
    print('Default f1')

Foo = namedtuple('Foo', 'f1')
Foo.__new__.__defaults__ = (f1,)

Usage:

>>> foo = Foo()
>>> foo.f1()
Default f1

>>> def other_f1():
...     print('Another f1')
>>> foo = Foo(f1=other_f1)
>>> foo.f1()
Another f1
Community
  • 1
  • 1
Andrea Corbellini
  • 17,339
  • 3
  • 53
  • 69