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


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

Making Classes Subclassable

Started byLawrence D’Oliveiro <lawrencedo99@gmail.com>
First post2016-07-02 21:31 -0700
Last post2016-07-05 08:52 -0600
Articles 14 — 6 participants

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


Contents

  Making Classes Subclassable Lawrence D’Oliveiro <lawrencedo99@gmail.com> - 2016-07-02 21:31 -0700
    Re: Making Classes Subclassable dieter <dieter@handshake.de> - 2016-07-04 09:57 +0200
      Re: Making Classes Subclassable Lawrence D’Oliveiro <lawrencedo99@gmail.com> - 2016-07-04 01:34 -0700
        Re: Making Classes Subclassable Michael Selik <michael.selik@gmail.com> - 2016-07-05 05:26 +0000
          Re: Making Classes Subclassable Lawrence D’Oliveiro <lawrencedo99@gmail.com> - 2016-07-05 00:08 -0700
        Re: Making Classes Subclassable dieter <dieter@handshake.de> - 2016-07-05 09:55 +0200
        Re: Making Classes Subclassable Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2016-07-05 18:31 +1000
          Re: Making Classes Subclassable Ian Kelly <ian.g.kelly@gmail.com> - 2016-07-05 08:41 -0600
            Re: Making Classes Subclassable Steven D'Aprano <steve@pearwood.info> - 2016-07-06 01:33 +1000
        Re: Making Classes Subclassable Ian Kelly <ian.g.kelly@gmail.com> - 2016-07-05 09:02 -0600
          Re: Making Classes Subclassable Steven D'Aprano <steve@pearwood.info> - 2016-07-06 01:41 +1000
          Re: Making Classes Subclassable Lawrence D’Oliveiro <lawrencedo99@gmail.com> - 2016-07-05 19:31 -0700
      Re: Making Classes Subclassable Lawrence D’Oliveiro <lawrencedo99@gmail.com> - 2016-07-05 20:04 -0700
    Re: Making Classes Subclassable Ian Kelly <ian.g.kelly@gmail.com> - 2016-07-05 08:52 -0600

#110972 — Making Classes Subclassable

FromLawrence D’Oliveiro <lawrencedo99@gmail.com>
Date2016-07-02 21:31 -0700
SubjectMaking Classes Subclassable
Message-ID<ca1ee22e-9769-4e76-81f3-b9d0dcb55212@googlegroups.com>
Some of the classes in Qahirah, my Cairo binding <https://github.com/ldo/qahirah> I found handy to reuse elsewhere, for example in my binding for Pixman <https://github.com/ldo/python_pixman>. Subclassing is easy, but then you need to ensure that operations inherited from the superclass return instances of the right class. This means that superclass methods must never refer directly to the class by name for constructing new objects; they need to obtain the current class by more indirect means.

(“isinstance” checks on the superclass name are fine, since they will succeed on subclasses as well.)

For example, consider the qahirah.Matrix class. Here is how I define the multiplication operator:

    def __mul__(m1, m2) :
        "returns concatenation with another Matrix, or mapping of a Vector."
        if isinstance(m2, Matrix) :
            result = m1.__class__ \
              (
                xx = m1.xx * m2.xx + m1.xy * m2.yx,
                yx = m1.yx * m2.xx + m1.yy * m2.yx,
                xy = m1.xx * m2.xy + m1.xy * m2.yy,
                yy = m1.yx * m2.xy + m1.yy * m2.yy,
                x0 = m1.xx * m2.x0 + m1.xy * m2.y0 + m1.x0,
                y0 = m1.yx * m2.x0 + m1.yy * m2.y0 + m1.y0,
              )
        elif isinstance(m2, Vector) :
            result = m2.__class__ \
              (
                x = m2.x * m1.xx + m2.y * m1.xy + m1.x0,
                y = m2.x * m1.yx + m2.y * m1.yy + m1.y0
              )
        else :
            result = NotImplemented
        #end if
        return \
            result
    #end __mul__

The idea behind this is that you can do “matrix * matrix” to do matrix multiplication, or “matrix * vector” to transform a Vector by a Matrix.

In Pixman, the corresponding class names are Transform instead of Matrix, and Point instead of Vector (following the usual pixman terminology). But the inherited multiplication operation still works on them.

For another example, consider the qahirah.Vector.from_tuple method, which allows easy passing of a simple pair of (x, y) coordinates wherever a Vector is wanted, with only a little extra work:

    @classmethod
    def from_tuple(celf, v) :
        "converts a tuple of 2 numbers to a Vector. Can be used to ensure that" \
        " v is a Vector."
        if not isinstance(v, celf) :
            v = celf(*v)
        #end if
        return \
            v
    #end from_tuple

The key thing here is to avoid staticmethods and use classmethods instead, so you get passed the class object and can use it to construct new instances.

[toc] | [next] | [standalone]


#111049

Fromdieter <dieter@handshake.de>
Date2016-07-04 09:57 +0200
Message-ID<mailman.59.1467619073.2295.python-list@python.org>
In reply to#110972
Lawrence D’Oliveiro <lawrencedo99@gmail.com> writes:

> Some of the classes in Qahirah, my Cairo binding <https://github.com/ldo/qahirah> I found handy to reuse elsewhere, for example in my binding for Pixman <https://github.com/ldo/python_pixman>. Subclassing is easy, but then you need to ensure that operations inherited from the superclass return instances of the right class. This means that superclass methods must never refer directly to the class by name for constructing new objects; they need to obtain the current class by more indirect means.

--> "type(obj)" or "obj.__class__" (there are small differences)
give you the type/class of "obj".

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


#111069

FromLawrence D’Oliveiro <lawrencedo99@gmail.com>
Date2016-07-04 01:34 -0700
Message-ID<337f8071-ef4a-4ac5-867b-a5320c684730@googlegroups.com>
In reply to#111049
On Monday, July 4, 2016 at 7:58:07 PM UTC+12, dieter wrote:
> --> "type(obj)" or "obj.__class__" (there are small differences)
> give you the type/class of "obj".

When would it not be the same?

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


#111100

FromMichael Selik <michael.selik@gmail.com>
Date2016-07-05 05:26 +0000
Message-ID<mailman.87.1467696408.2295.python-list@python.org>
In reply to#111069
On Mon, Jul 4, 2016, 4:36 AM Lawrence D’Oliveiro <lawrencedo99@gmail.com>
wrote:

> On Monday, July 4, 2016 at 7:58:07 PM UTC+12, dieter wrote:
> > --> "type(obj)" or "obj.__class__" (there are small differences)
> > give you the type/class of "obj".
>
> When would it not be the same?
>

Old-style classes.

>

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


#111107

FromLawrence D’Oliveiro <lawrencedo99@gmail.com>
Date2016-07-05 00:08 -0700
Message-ID<ab5b8e85-3c64-40e8-ad23-4723f3bb464a@googlegroups.com>
In reply to#111100
On Tuesday, July 5, 2016 at 5:27:01 PM UTC+12, Michael Selik wrote:
> On Mon, Jul 4, 2016, 4:36 AM Lawrence D’Oliveiro wrote:
> 
>> On Monday, July 4, 2016 at 7:58:07 PM UTC+12, dieter wrote:
>>>
>>> --> "type(obj)" or "obj.__class__" (there are small differences)
>>> give you the type/class of "obj".
>>
>> When would it not be the same?
> 
> Old-style classes.

Which don’t exist any more.

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


#111108

Fromdieter <dieter@handshake.de>
Date2016-07-05 09:55 +0200
Message-ID<mailman.89.1467705362.2295.python-list@python.org>
In reply to#111069
Lawrence D’Oliveiro <lawrencedo99@gmail.com> writes:

> On Monday, July 4, 2016 at 7:58:07 PM UTC+12, dieter wrote:
>> --> "type(obj)" or "obj.__class__" (there are small differences)
>> give you the type/class of "obj".
>
> When would it not be the same?

Special proxy objects, i.e. objects that try to behave as much
as possible like another object.

For those objects, you may either want the class of the proxy itself
(--> "type(obj)") or the class of the proxied object (--> "obj.__class__").


Moreover, you might have special descriptors installed which may
affect how "obj.__class__" is resolved.

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


#111111

FromSteven D'Aprano <steve+comp.lang.python@pearwood.info>
Date2016-07-05 18:31 +1000
Message-ID<577b707f$0$2917$c3e8da3$76491128@news.astraweb.com>
In reply to#111069
On Monday 04 July 2016 18:34, Lawrence D’Oliveiro wrote:

> On Monday, July 4, 2016 at 7:58:07 PM UTC+12, dieter wrote:
>> --> "type(obj)" or "obj.__class__" (there are small differences)
>> give you the type/class of "obj".
> 
> When would it not be the same?


class X(object):
    def __getattribute__(self, name):
        if __name__ == '__class__':
            return int
        return super().__getattribute__(name)



-- 
Steve

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


#111125

FromIan Kelly <ian.g.kelly@gmail.com>
Date2016-07-05 08:41 -0600
Message-ID<mailman.99.1467729753.2295.python-list@python.org>
In reply to#111111
On Tue, Jul 5, 2016 at 2:31 AM, Steven D'Aprano
<steve+comp.lang.python@pearwood.info> wrote:
> On Monday 04 July 2016 18:34, Lawrence D’Oliveiro wrote:
>
>> On Monday, July 4, 2016 at 7:58:07 PM UTC+12, dieter wrote:
>>> --> "type(obj)" or "obj.__class__" (there are small differences)
>>> give you the type/class of "obj".
>>
>> When would it not be the same?
>
>
> class X(object):
>     def __getattribute__(self, name):
>         if __name__ == '__class__':
>             return int
>         return super().__getattribute__(name)

Did you actually test that?

py> class X(object):
...     def __getattribute__(self, name):
...         if __name__ == '__class__':
...             return int
...         return super().__getattribute__(name)
...
py> x = X()
py> x.__class__
<class '__main__.X'>

Certain attributes like __class__ and __dict__ are special.

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


#111130

FromSteven D'Aprano <steve@pearwood.info>
Date2016-07-06 01:33 +1000
Message-ID<577bd33a$0$1609$c3e8da3$5496439d@news.astraweb.com>
In reply to#111125
On Wed, 6 Jul 2016 12:41 am, Ian Kelly wrote:

> On Tue, Jul 5, 2016 at 2:31 AM, Steven D'Aprano
> <steve+comp.lang.python@pearwood.info> wrote:
>> On Monday 04 July 2016 18:34, Lawrence D’Oliveiro wrote:
>>
>>> On Monday, July 4, 2016 at 7:58:07 PM UTC+12, dieter wrote:
>>>> --> "type(obj)" or "obj.__class__" (there are small differences)
>>>> give you the type/class of "obj".
>>>
>>> When would it not be the same?
>>
>>
>> class X(object):
>>     def __getattribute__(self, name):
>>         if __name__ == '__class__':
>>             return int
>>         return super().__getattribute__(name)
> 
> Did you actually test that?

Er, no, because if I had, I would have discovered the silly typo: __name__
instead of name, which screws up the whole thing...

Trying again:

py> class X(object):
...     def __getattribute__(self, name):
...         if name == '__class__':  # NOTE SPELLING
...             return int
...         return super().__getattribute__(name)
...
py> x = X()
py> x.__class__
<class 'int'>


[...]
> Certain attributes like __class__ and __dict__ are special.

Indeed they are, but __class__ is not *that* special :-)




-- 
Steven
“Cheer up,” they said, “things could be worse.” So I cheered up, and sure
enough, things got worse.

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


#111128

FromIan Kelly <ian.g.kelly@gmail.com>
Date2016-07-05 09:02 -0600
Message-ID<mailman.101.1467730990.2295.python-list@python.org>
In reply to#111069
On Mon, Jul 4, 2016 at 2:34 AM, Lawrence D’Oliveiro
<lawrencedo99@gmail.com> wrote:
> On Monday, July 4, 2016 at 7:58:07 PM UTC+12, dieter wrote:
>> --> "type(obj)" or "obj.__class__" (there are small differences)
>> give you the type/class of "obj".
>
> When would it not be the same?

I think the only remaining difference in Python 3 is that
obj.__class__ is assignable and type(obj) is not. For most uses,
type(obj) would be preferred though, in the same way that len(obj) is
preferable to obj.__len__().

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


#111132

FromSteven D'Aprano <steve@pearwood.info>
Date2016-07-06 01:41 +1000
Message-ID<577bd53e$0$1602$c3e8da3$5496439d@news.astraweb.com>
In reply to#111128
On Wed, 6 Jul 2016 01:02 am, Ian Kelly wrote:

> On Mon, Jul 4, 2016 at 2:34 AM, Lawrence D’Oliveiro
> <lawrencedo99@gmail.com> wrote:
>> On Monday, July 4, 2016 at 7:58:07 PM UTC+12, dieter wrote:
>>> --> "type(obj)" or "obj.__class__" (there are small differences)
>>> give you the type/class of "obj".
>>
>> When would it not be the same?
> 
> I think the only remaining difference in Python 3 is that
> obj.__class__ is assignable and type(obj) is not. For most uses,
> type(obj) would be preferred though, in the same way that len(obj) is
> preferable to obj.__len__().


Assigning to __class__ will change the result of type(obj).


py> class A(object):
...     pass
...
py> class B(object):
...     pass
...
py> a = A()
py> type(a)
<class '__main__.A'>
py> a.__class__ = B
py> type(a)
<class '__main__.B'>


This is a deliberate design feature! Provided your classes are designed to
be compatible, you can safely change an instance of class A into an
instance of class B, and then have a.method() correctly call B.method.

(Objective C makes *extensive* use of this technique in Cocoa's standard
library functions, but entirely behind the scenes. Apple's semi-official
stance on this is that they are allowed to do it because the know what
they're doing, but anyone else who tries it is a muppet.)

I don't know what this technique is called, but I'm going to call it
adoption (as opposed to adaption) -- your A instance gets adopted by class
B, after which it behaves like any other B instance.

Again, I stress that A and B must be compatible (and written in Python, you
can't do it with classes written in C), otherwise their methods won't find
the attributes they expect.



-- 
Steven
“Cheer up,” they said, “things could be worse.” So I cheered up, and sure
enough, things got worse.

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


#111162

FromLawrence D’Oliveiro <lawrencedo99@gmail.com>
Date2016-07-05 19:31 -0700
Message-ID<998c5088-027a-478b-993d-99fef00edf13@googlegroups.com>
In reply to#111128
On Wednesday, July 6, 2016 at 3:03:26 AM UTC+12, Ian wrote:
>
> On Mon, Jul 4, 2016 at 2:34 AM, Lawrence D’Oliveiro wrote:
>>
>> On Monday, July 4, 2016 at 7:58:07 PM UTC+12, dieter wrote:
>>> --> "type(obj)" or "obj.__class__" (there are small differences)
>>> give you the type/class of "obj".
>>
>> When would it not be the same?
> 
> I think the only remaining difference in Python 3 is that
> obj.__class__ is assignable and type(obj) is not. For most uses,
> type(obj) would be preferred though, in the same way that len(obj) is
> preferable to obj.__len__().

OK, I think that makes sense.

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


#111165

FromLawrence D’Oliveiro <lawrencedo99@gmail.com>
Date2016-07-05 20:04 -0700
Message-ID<f1fd265f-5e13-4ecc-870e-b8f5141fe6fd@googlegroups.com>
In reply to#111049
On Monday, July 4, 2016 at 7:58:07 PM UTC+12, dieter wrote:
>
> Lawrence D’Oliveiro writes:
> 
> > Some of the classes in Qahirah, my Cairo binding
> <https://github.com/ldo/qahirah> I found handy to reuse elsewhere, for
> example in my binding for Pixman <https://github.com/ldo/python_pixman>.
> Subclassing is easy, but then you need to ensure that operations inherited
> from the superclass return instances of the right class. This means that
> superclass methods must never refer directly to the class by name for
> constructing new objects; they need to obtain the current class by more
> indirect means.
> 
> --> "type(obj)" or "obj.__class__" (there are small differences)
> give you the type/class of "obj".

OK, both updated.

Funny how you find other bugs when revisiting code after a while. :)

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


#111127

FromIan Kelly <ian.g.kelly@gmail.com>
Date2016-07-05 08:52 -0600
Message-ID<mailman.100.1467730382.2295.python-list@python.org>
In reply to#110972
On Mon, Jul 4, 2016 at 1:57 AM, dieter <dieter@handshake.de> wrote:
> Lawrence D’Oliveiro <lawrencedo99@gmail.com> writes:
>
>> Some of the classes in Qahirah, my Cairo binding <https://github.com/ldo/qahirah> I found handy to reuse elsewhere, for example in my binding for Pixman <https://github.com/ldo/python_pixman>. Subclassing is easy, but then you need to ensure that operations inherited from the superclass return instances of the right class. This means that superclass methods must never refer directly to the class by name for constructing new objects; they need to obtain the current class by more indirect means.
>
> --> "type(obj)" or "obj.__class__" (there are small differences)
> give you the type/class of "obj".

Another option is a classmethod factory:

class X:
    @classmethod
    def new(cls, *args, **kwargs):
        return cls(*args, **kwargs)

    def method(self):
        return self.new()

class Y(X): pass

Y().method() will return a new Y instance. There's not a lot of reason
to prefer this over type(self)(), but by overriding the classmethod
you can have subclasses that use different signatures in the
constructor but still support the same signature in the factory for
compatibility with the base class. You can also make the classmethod
private, if you wish.

[toc] | [prev] | [standalone]


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


csiph-web