Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > comp.lang.python > #84510 > unrolled thread

Delegation in Python

Started byBrian Gladman <noone@nowhere.net>
First post2015-01-24 22:57 +0000
Last post2015-01-25 07:43 +0000
Articles 16 — 5 participants

Back to article view | Back to comp.lang.python


Contents

  Delegation in Python Brian Gladman <noone@nowhere.net> - 2015-01-24 22:57 +0000
    Re: Delegation in Python Chris Angelico <rosuav@gmail.com> - 2015-01-25 10:22 +1100
      Re: Delegation in Python Brian Gladman <noone@nowhere.net> - 2015-01-24 23:38 +0000
        Re: Delegation in Python Chris Angelico <rosuav@gmail.com> - 2015-01-25 10:43 +1100
          Re: Delegation in Python Brian Gladman <noone@nowhere.net> - 2015-01-25 00:18 +0000
            Re: Delegation in Python Chris Angelico <rosuav@gmail.com> - 2015-01-25 11:28 +1100
              Re: Delegation in Python Brian Gladman <noone@nowhere.net> - 2015-01-25 07:49 +0000
                Re: Delegation in Python Chris Angelico <rosuav@gmail.com> - 2015-01-25 19:07 +1100
        Re: Delegation in Python Gary Herron <gherron@digipen.edu> - 2015-01-24 15:47 -0800
          Re: Delegation in Python Brian Gladman <noone@nowhere.net> - 2015-01-24 23:58 +0000
    Re: Delegation in Python Gary Herron <gherron@digipen.edu> - 2015-01-24 15:41 -0800
      Re: Delegation in Python Brian Gladman <noone@nowhere.net> - 2015-01-24 23:52 +0000
    Re: Delegation in Python Mark Lawrence <breamoreboy@yahoo.co.uk> - 2015-01-24 23:59 +0000
    Re: Delegation in Python Chris Angelico <rosuav@gmail.com> - 2015-01-25 11:07 +1100
    Re: Delegation in Python Terry Reedy <tjreedy@udel.edu> - 2015-01-24 20:31 -0500
      Re: Delegation in Python Brian Gladman <noone@nowhere.net> - 2015-01-25 07:43 +0000

#84510 — Delegation in Python

FromBrian Gladman <noone@nowhere.net>
Date2015-01-24 22:57 +0000
SubjectDelegation in Python
Message-ID<FeCdnXdsTpunvlnJnZ2dnUVZ8judnZ2d@brightview.co.uk>
I would appreciate advice on how to set up delgation in Python.

I am continuously implementing a function to test whether a Python
Fraction is an integer so I wanted to define a new class, based on
Fraction, that includes this new method.

But I am not clear on how to delegate from my new class to the existing
Fraction class.  This is what I have:

--------------------------
class RF(Fraction):

  def __new__(self, x, y):
    super().__new__(self, x, y)

  def is_integer(self):
    return self.numerator % self.denominator == 0

  def __getattr__(self, attr):
    return getattr(self, attr)
--------------------------

which doesn't work.

Any advice on how to do this would be much appreciated.

   Brian

[toc] | [next] | [standalone]


#84514

FromChris Angelico <rosuav@gmail.com>
Date2015-01-25 10:22 +1100
Message-ID<mailman.18106.1422141738.18130.python-list@python.org>
In reply to#84510
On Sun, Jan 25, 2015 at 9:57 AM, Brian Gladman <noone@nowhere.net> wrote:
> But I am not clear on how to delegate from my new class to the existing
> Fraction class.  This is what I have:
>
> --------------------------
> class RF(Fraction):
>
>   def __new__(self, x, y):
>     super().__new__(self, x, y)
>
>   def is_integer(self):
>     return self.numerator % self.denominator == 0
>
>   def __getattr__(self, attr):
>     return getattr(self, attr)

If you just drop everything but your new method, it should work just fine.

class RF(Fraction):
    def is_integer(self):
       return self.numerator % self.denominator == 0

However, this doesn't ensure that operations on RFs will return more
RFs - they'll often return Fractions instead. There's no easy fix for
that, sorry.

ChrisA

[toc] | [prev] | [next] | [standalone]


#84516

FromBrian Gladman <noone@nowhere.net>
Date2015-01-24 23:38 +0000
Message-ID<5ZKdnd6rbdZ3sVnJnZ2dnUVZ8mCdnZ2d@brightview.co.uk>
In reply to#84514
On 24/01/2015 23:22, Chris Angelico wrote:
> class RF(Fraction):
>     def is_integer(self):
>        return self.numerator % self.denominator == 0

Thanks for your help on this.  I must admit that nowhere in a lot of
searching did I find that delegation is achieved by doing nothing!

   Brian


[toc] | [prev] | [next] | [standalone]


#84519

FromChris Angelico <rosuav@gmail.com>
Date2015-01-25 10:43 +1100
Message-ID<mailman.18109.1422143020.18130.python-list@python.org>
In reply to#84516
On Sun, Jan 25, 2015 at 10:38 AM, Brian Gladman <noone@nowhere.net> wrote:
> On 24/01/2015 23:22, Chris Angelico wrote:
>> class RF(Fraction):
>>     def is_integer(self):
>>        return self.numerator % self.denominator == 0
>
> Thanks for your help on this.  I must admit that nowhere in a lot of
> searching did I find that delegation is achieved by doing nothing!

Hehe :) If you want a technical look at it, what you have is the
fundamental nature of subclassing: you derive from another class, and
your class is identical to that with certain exceptions (in your case,
an additional method). Python's way of handling that is called the
Method Resolution Order or MRO, and you can find info on it on the
web, eg:

http://python-history.blogspot.com/2010/06/method-resolution-order.html

Enjoy!

ChrisA

[toc] | [prev] | [next] | [standalone]


#84530

FromBrian Gladman <noone@nowhere.net>
Date2015-01-25 00:18 +0000
Message-ID<K8KdncuoKPL1q1nJnZ2dnUVZ8h2dnZ2d@brightview.co.uk>
In reply to#84519
On 24/01/2015 23:43, Chris Angelico wrote:
> On Sun, Jan 25, 2015 at 10:38 AM, Brian Gladman <noone@nowhere.net> wrote:
>> On 24/01/2015 23:22, Chris Angelico wrote:
>>> class RF(Fraction):
>>>     def is_integer(self):
>>>        return self.numerator % self.denominator == 0
>>
>> Thanks for your help on this.  I must admit that nowhere in a lot of
>> searching did I find that delegation is achieved by doing nothing!
> 
> Hehe :) If you want a technical look at it, what you have is the
> fundamental nature of subclassing: you derive from another class, and
> your class is identical to that with certain exceptions (in your case,
> an additional method). Python's way of handling that is called the
> Method Resolution Order or MRO, and you can find info on it on the
> web, eg:
> 
> http://python-history.blogspot.com/2010/06/method-resolution-order.html

Thanks for the further background.

Is there a way of doing delegation rather than sub-classing?

That is, can I create a class (say RF) that passes some of its methods
to Fraction for implementation but always returns an RF?

   Brian

[toc] | [prev] | [next] | [standalone]


#84531

FromChris Angelico <rosuav@gmail.com>
Date2015-01-25 11:28 +1100
Message-ID<mailman.18116.1422145722.18130.python-list@python.org>
In reply to#84530
On Sun, Jan 25, 2015 at 11:18 AM, Brian Gladman <noone@nowhere.net> wrote:
> Is there a way of doing delegation rather than sub-classing?
>
> That is, can I create a class (say RF) that passes some of its methods
> to Fraction for implementation but always returns an RF?

Hmm. The key here is that you want more than just delegation; you want
to transform every return value. That's not going to be easy. It would
be easiest and cleanest to skip the whole thing, and have a separate
function for what you're doing here - not a method, a stand-alone
function.

def is_integer(fr):
    return fr.numerator % fr.denominator == 0

Next best would be the monkey-patching option. Delegation with
transformation is a lot of effort for what you want.

ChrisA

[toc] | [prev] | [next] | [standalone]


#84559

FromBrian Gladman <noone@nowhere.net>
Date2015-01-25 07:49 +0000
Message-ID<KYednbizIP5rAlnJnZ2dnUVZ7sidnZ2d@brightview.co.uk>
In reply to#84531
On 25/01/2015 00:28, Chris Angelico wrote:
> On Sun, Jan 25, 2015 at 11:18 AM, Brian Gladman <noone@nowhere.net> wrote:
>> Is there a way of doing delegation rather than sub-classing?
>>
>> That is, can I create a class (say RF) that passes some of its methods
>> to Fraction for implementation but always returns an RF?
> 
> Hmm. The key here is that you want more than just delegation; you want
> to transform every return value. That's not going to be easy. It would
> be easiest and cleanest to skip the whole thing, and have a separate
> function for what you're doing here - not a method, a stand-alone
> function.
> 
> def is_integer(fr):
>     return fr.numerator % fr.denominator == 0
> 
> Next best would be the monkey-patching option. Delegation with
> transformation is a lot of effort for what you want.

Thanks, a part of this was a wish to understand how to map what I can do
in other languages into Python.  I felt that it might just be possible
in Python to avoid having to wrap all the methods of the base class in
the derived class.  But it seems that __getattr__ etc are not quite as
magic as I hoped.

Thanks again for your help.

   Brian

[toc] | [prev] | [next] | [standalone]


#84560

FromChris Angelico <rosuav@gmail.com>
Date2015-01-25 19:07 +1100
Message-ID<mailman.18130.1422173260.18130.python-list@python.org>
In reply to#84559
On Sun, Jan 25, 2015 at 6:49 PM, Brian Gladman <noone@nowhere.net> wrote:
> Thanks, a part of this was a wish to understand how to map what I can do
> in other languages into Python.  I felt that it might just be possible
> in Python to avoid having to wrap all the methods of the base class in
> the derived class.  But it seems that __getattr__ etc are not quite as
> magic as I hoped.

They do exactly what they're documented to, nothing more and nothing
less :) It's certainly possible to use them to hook into missing
attributes, for instance:

>>> class RF:
    def __init__(self, *args, **kw):
        self._frac = Fraction(*args, **kw)
    def __getattr__(self, attr):
        return getattr(self._frac, attr)
    def __repr__(self):
        return "RF(%d, %d)" % (self._frac.numerator, self._frac.denominator)
    def is_integer(self):
        return self._frac.denominator==1

>>> RF(1,4).numerator
1

But it doesn't work for everything:
>>> RF(1,4)*2
Traceback (most recent call last):
  File "<pyshell#37>", line 1, in <module>
    RF(1,4)*2
TypeError: unsupported operand type(s) for *: 'RF' and 'int'

The only solution would be to go through every operation that you care
about, and manually hook them. Something like this:

    def __mul__(self, other):
        result = self._frac * other
        if isinstance(result, Fraction):
            return RF(result.numerator, result.denominator)
        return result

>>> RF(1,4)*2
RF(1, 2)

And do that for every other operation, method, etc. Tedious, but can
be effective if you need it.

ChrisA

[toc] | [prev] | [next] | [standalone]


#84521

FromGary Herron <gherron@digipen.edu>
Date2015-01-24 15:47 -0800
Message-ID<mailman.18111.1422143281.18130.python-list@python.org>
In reply to#84516
On 01/24/2015 03:38 PM, Brian Gladman wrote:
> On 24/01/2015 23:22, Chris Angelico wrote:
>> class RF(Fraction):
>>      def is_integer(self):
>>         return self.numerator % self.denominator == 0
> Thanks for your help on this.  I must admit that nowhere in a lot of
> searching did I find that delegation is achieved by doing nothing!
>
>     Brian

That's *not* "doing nothing".   And it's not even really "delegation".  
It's just sub-classing Fraction to add one new method and inherit all 
other methods.

Gary Herron


-- 
Dr. Gary Herron
Department of Computer Science
DigiPen Institute of Technology
(425) 895-4418

[toc] | [prev] | [next] | [standalone]


#84525

FromBrian Gladman <noone@nowhere.net>
Date2015-01-24 23:58 +0000
Message-ID<2didnRFXOrIwrFnJnZ2dnUVZ8iednZ2d@brightview.co.uk>
In reply to#84521
On 24/01/2015 23:47, Gary Herron wrote:
> On 01/24/2015 03:38 PM, Brian Gladman wrote:
>> On 24/01/2015 23:22, Chris Angelico wrote:
>>> class RF(Fraction):
>>>      def is_integer(self):
>>>         return self.numerator % self.denominator == 0
>> Thanks for your help on this.  I must admit that nowhere in a lot of
>> searching did I find that delegation is achieved by doing nothing!
>>
>>     Brian
> 
> That's *not* "doing nothing".   And it's not even really "delegation". 
> It's just sub-classing Fraction to add one new method and inherit all
> other methods.

I realised that - I was being whimsical.

I really did want true delgation so that I get an RF returned even when
I delegate to Fractions.

   Brian

[toc] | [prev] | [next] | [standalone]


#84518

FromGary Herron <gherron@digipen.edu>
Date2015-01-24 15:41 -0800
Message-ID<mailman.18108.1422142883.18130.python-list@python.org>
In reply to#84510
On 01/24/2015 03:22 PM, Chris Angelico wrote:
> On Sun, Jan 25, 2015 at 9:57 AM, Brian Gladman <noone@nowhere.net> wrote:
>> But I am not clear on how to delegate from my new class to the existing
>> Fraction class.  This is what I have:
>>
>> --------------------------
>> class RF(Fraction):
>>
>>    def __new__(self, x, y):
>>      super().__new__(self, x, y)
>>
>>    def is_integer(self):
>>      return self.numerator % self.denominator == 0
>>
>>    def __getattr__(self, attr):
>>      return getattr(self, attr)
> If you just drop everything but your new method, it should work just fine.
>
> class RF(Fraction):
>      def is_integer(self):
>         return self.numerator % self.denominator == 0
>
> However, this doesn't ensure that operations on RFs will return more
> RFs - they'll often return Fractions instead. There's no easy fix for
> that, sorry.
>
> ChrisA

You can always "monkey-path" the Fraction class on the fly to add a new 
method to it.  I think most would consider this a bad idea, but it does 
work.
Try this:

 >>> from fractions import Fraction
 >>> def is_integer(self):
...     return self.numerator % self.denominator == 0
...
 >>> Fraction.is_integer = is_integer # Monkey-patch Fraction
 >>>
 >>> Fraction(1,2).is_integer()
False
 >>> Fraction(2,1).is_integer()
True


Gary Herron


-- 
Dr. Gary Herron
Department of Computer Science
DigiPen Institute of Technology
(425) 895-4418

[toc] | [prev] | [next] | [standalone]


#84522

FromBrian Gladman <noone@nowhere.net>
Date2015-01-24 23:52 +0000
Message-ID<UridnbK49sm5rVnJnZ2dnUVZ7radnZ2d@brightview.co.uk>
In reply to#84518
On 24/01/2015 23:41, Gary Herron wrote:

[snip]>
> You can always "monkey-path" the Fraction class on the fly to add a new
> method to it.  I think most would consider this a bad idea, but it does
> work.
> Try this:
> 
>>>> from fractions import Fraction
>>>> def is_integer(self):
> ...     return self.numerator % self.denominator == 0
> ...
>>>> Fraction.is_integer = is_integer # Monkey-patch Fraction
>>>>
>>>> Fraction(1,2).is_integer()
> False
>>>> Fraction(2,1).is_integer()
> True

Thanks Gary.  As Chris says, the method he suggests is problematic since
anything delegated returns a Fraction, not an RF.  Patching Fraction
looks nicer to use even if it is frowned on.

   Brian

[toc] | [prev] | [next] | [standalone]


#84526

FromMark Lawrence <breamoreboy@yahoo.co.uk>
Date2015-01-24 23:59 +0000
Message-ID<mailman.18113.1422144008.18130.python-list@python.org>
In reply to#84510
On 24/01/2015 23:41, Gary Herron wrote:
> On 01/24/2015 03:22 PM, Chris Angelico wrote:
>> On Sun, Jan 25, 2015 at 9:57 AM, Brian Gladman <noone@nowhere.net> wrote:
>>> But I am not clear on how to delegate from my new class to the existing
>>> Fraction class.  This is what I have:
>>>
>>> --------------------------
>>> class RF(Fraction):
>>>
>>>    def __new__(self, x, y):
>>>      super().__new__(self, x, y)
>>>
>>>    def is_integer(self):
>>>      return self.numerator % self.denominator == 0
>>>
>>>    def __getattr__(self, attr):
>>>      return getattr(self, attr)
>> If you just drop everything but your new method, it should work just
>> fine.
>>
>> class RF(Fraction):
>>      def is_integer(self):
>>         return self.numerator % self.denominator == 0
>>
>> However, this doesn't ensure that operations on RFs will return more
>> RFs - they'll often return Fractions instead. There's no easy fix for
>> that, sorry.
>>
>> ChrisA
>
> You can always "monkey-path" the Fraction class on the fly to add a new
> method to it.  I think most would consider this a bad idea, but it does
> work.
> Try this:
>
>  >>> from fractions import Fraction
>  >>> def is_integer(self):
> ...     return self.numerator % self.denominator == 0
> ...
>  >>> Fraction.is_integer = is_integer # Monkey-patch Fraction
>  >>>
>  >>> Fraction(1,2).is_integer()
> False
>  >>> Fraction(2,1).is_integer()
> True
>
>
> Gary Herron
>

As regards this being a bad idea I'd suggest the latest score is 
Practicality 1 Purity 0 :)

-- 
My fellow Pythonistas, ask not what our language can do for you, ask
what you can do for our language.

Mark Lawrence

[toc] | [prev] | [next] | [standalone]


#84528

FromChris Angelico <rosuav@gmail.com>
Date2015-01-25 11:07 +1100
Message-ID<mailman.18115.1422144482.18130.python-list@python.org>
In reply to#84510
On Sun, Jan 25, 2015 at 10:59 AM, Mark Lawrence <breamoreboy@yahoo.co.uk> wrote:
>> You can always "monkey-path" the Fraction class on the fly to add a new
>> method to it.  I think most would consider this a bad idea, but it does
>> work.
>
> As regards this being a bad idea I'd suggest the latest score is
> Practicality 1 Purity 0 :)

Indeed. But there's a huge difference between simple and complex
projects. I had a bit of a nightmare trying to figure out what was
going on with a project's logging... it looked like the Python logging
module, but there was another argument being processed, courtesy of
some monkey-patching. Try to keep these changes to small projects,
where it's easier to keep everything in your head; or for a temporary
bit of debugging, where you just want to do this temporarily and then
undo it again when you find the bug. Otherwise, it gets confusing for
the subsequent reader.

ChrisA

[toc] | [prev] | [next] | [standalone]


#84537

FromTerry Reedy <tjreedy@udel.edu>
Date2015-01-24 20:31 -0500
Message-ID<mailman.18120.1422149503.18130.python-list@python.org>
In reply to#84510
On 1/24/2015 5:57 PM, Brian Gladman wrote:
> I would appreciate advice on how to set up delgation in Python.
>
> I am continuously implementing a function to test whether a Python
> Fraction is an integer

Since Fractions are reduced to lowest terms,
 >>> from fractions import Fraction as F
 >>> F(4, 2)
Fraction(2, 1)
 >>> F(12, 3)
Fraction(4, 1)

the test is that the denominator is 1

 >>> F(12,3).denominator == 1
True
 >>> F(12,5).denominator == 1
False

it may not be worth the bother to define a function,

def fint(F): return F.denominator == 1

>so I wanted to define a new class, based on
> Fraction, that includes this new method.

but if you do, there is little need to make it a method.  You are not 
overriding an existing method or adding a new special method.  The math 
module has float and int functions that have *not* been turned into 
methods.  Ditto for cmath.  I would just leave the function a function. 
  Python is not Java.

-- 
Terry Jan Reedy

[toc] | [prev] | [next] | [standalone]


#84558

FromBrian Gladman <noone@nowhere.net>
Date2015-01-25 07:43 +0000
Message-ID<_YmdnTx6rZsjA1nJnZ2dnUVZ8hOdnZ2d@brightview.co.uk>
In reply to#84537
On 25/01/2015 01:31, Terry Reedy wrote:
> On 1/24/2015 5:57 PM, Brian Gladman wrote:
>> I would appreciate advice on how to set up delgation in Python.
>>
>> I am continuously implementing a function to test whether a Python
>> Fraction is an integer
> 
> Since Fractions are reduced to lowest terms,
>>>> from fractions import Fraction as F
>>>> F(4, 2)
> Fraction(2, 1)
>>>> F(12, 3)
> Fraction(4, 1)
> 
> the test is that the denominator is 1

Thanks Terry, I should have realised this but didn't.

>>>> F(12,3).denominator == 1
> True
>>>> F(12,5).denominator == 1
> False
> 
> it may not be worth the bother to define a function,
> 
> def fint(F): return F.denominator == 1
> 
>> so I wanted to define a new class, based on
>> Fraction, that includes this new method.
> 
> but if you do, there is little need to make it a method.  You are not
> overriding an existing method or adding a new special method.  The math
> module has float and int functions that have *not* been turned into
> methods.  Ditto for cmath.  I would just leave the function a function.

Yes, it seems so.

   Brian




[toc] | [prev] | [standalone]


Back to top | Article view | comp.lang.python


csiph-web