inheritance and default arguments

Bengt Richter bokr at oz.net
Sun Aug 11 18:21:48 EDT 2002


On Sun, 11 Aug 2002 15:47:55 GMT, Andrew Koenig <ark at research.att.com> wrote:

>I want a method in a class hierarchy to have a default argument.  For
>example, I might want the method to deal with a designated substring
>of a string that I pass as an argument, and I want the beginning and
>end of the substring to be 0 and len(s) as defaults.
>
>I can't do this:
>
>        class Base(object):
>                def f(self, s, begin=0, end=len(s)):
>                        ...
>
>        class Derived(object):
>                def f(self, s, begin=0, end=len(s)):
>                        ...
>
>because the default argument is evaluated at the wrong time.
>If I fix this problem in the obvious way:
>
>        class Base(object):
>                def f(self, s, begin=0, end=None):
>                        if end == None:
>                                end = len(s)
>                        ...
>
>        class Derived(base):
>                def f(self, s, begin=0, end=None):
>                        if end == None:
>                                end = len(s)
>                        ...
>
>I must now repeat not only the default arguments, but also the
>corresponding tests, in each derived class.
>
>I can avoid that repetition by splitting the function into two:
>One deals with the default-argument processing (and is inherited
>by every derived class), the other does the actual work (and is
>overridden as needed in the derived classes):
>
>        class Base(object):
>                def f(self, s, begin=0, end=None):
>                        if end == None:
>                                end = len(s)
>                        return self.f_aux(s, begin, end)
>                def f_aux(self, s, begin, end):
>                        ...
>
>        class Derived(object):
>                def f_aux(self, s, begin, end):
>                        ...
>
>This isn't too bad, but I have the feeling that there may be a more
>Pythonic way of doing the same thing.
>
>Any suggestions?

I presume your methods really need access to s and the substring limits,
otherwise you would be passing the substring itself? (Unless you anticpate
large substrings and want to avoid duplication (which maybe python could do
for us for really large slices?)).

Depending on what f does, you might want to consider packaging its access
to (s,begin,end) in a single class instance of its own and then passing
that instance as an argument instead. In this example it might be an indirect
substring class, e.g., (with some silly extra access mechanisms for example purposes),

 >>> class ISS(object):
 ...     from types import SliceType
 ...     def __init__(self, s, begin, end=None):
 ...         self.s = s
 ...         self.begin = begin
 ...         if end is None: self.end = len(s)
 ...         else: self.end = end
 ...     # convenience string value return via instance()
 ...     def __call__(self): return self.s[self.begin:self.end]
 ...     # perhaps weird access by indexing relative to begin & end ?
 ...     def __getitem__(self, i):
 ...         if isinstance(i, int): return self.s[i+ self.begin] # single char of s relative to begin
 ...         elif isinstance(i, tuple): return self.s[self.begin+i[0]:self.end+i[1]] # ss[-1,1]
 ...         elif isinstance(i, self.SliceType): raise NotImplementedError, 'left as exercise ;-)'
 ...         else: raise IndexError,'Unsupported index type'
 ...     def __len__(self): return self.end-self.begin
 ...
 >>> ss=ISS('0123456789',4,7)
 >>> ss()
 '456'
 >>> ss[-1,1]
 '34567'
 >>> ss[1:]
 Traceback (most recent call last):
   File "<stdin>", line 1, in ?
   File "<stdin>", line 14, in __getitem__
 NotImplementedError: left as exercise ;-)
 >>>
 >>> class Base(object):
 ...         def do_something_with_substring(self, s): print 'substring was "%s"'%s
 ...         def do_something_with_char_before(self, c): print 'char was "%s"' %c
 ...         def do_something_with_ss_with_pre_post_context(self, ppc): print 'pre/ss/post="%s"'%ppc
 ...         def mess_with_s_itself(self, s): print 's itself: "%s"'%s
 ...         def f(self, ss):
 ...             self.do_something_with_substring(ss())
 ...             self.do_something_with_char_before(ss[-1])
 ...             self.do_something_with_ss_with_pre_post_context(ss[-2,2])
 ...             self.mess_with_s_itself(ss.s)
 ...
 >>> b = Base()
 >>> b.f(ss)
 substring was "456"
 char was "3"
 pre/ss/post="2345678"
 s itself: "0123456789"
 >>> b.f(ISS('abcdefghijkl',5))
 substring was "fghijkl"
 char was "e"
 pre/ss/post="defghijkl"
 s itself: "abcdefghijkl"

 >>> class Derived(object):
 ...         def f(self, ss):
 ...             print 'Derived f using %s:\nwhich has __dict__: %s' %(ss, ss.__dict__)
 ...             print '"%s" at %s %s the first instance in "%s"' % (
 ...                 ss(), ss.begin, ['is not', 'is'][ss.s.find(ss())==ss.begin], ss.s
 ...             )
 ...
 >>> d=Derived()
 >>> d.f(ss)
 Derived f using <__main__.ISS object at 0x007D3840>:
 which has __dict__: {'s': '0123456789', 'end': 7, 'begin': 4}
 "456" at 4 is the first instance in "0123456789"
 >>> d.f(ISS('01234560123456',9,11))
 Derived f using <__main__.ISS object at 0x007D6C90>:
 which has __dict__: {'s': '01234560123456', 'end': 11, 'begin': 9}
 "23" at 9 is not the first instance in "01234560123456"


Of course, there's overhead in this (which might be lessened somewhat with __slots__=['s','begin','end']).
You'll have to judge the tradeoffs. Or if you want opinions, you'll have to reveal a little more
about the Base and Derived classes and how much of what their methods do to what and when ;-)

Regards,
Bengt Richter



More information about the Python-list mailing list