Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]
Groups > comp.lang.python > #110972 > unrolled thread
| Started by | Lawrence D’Oliveiro <lawrencedo99@gmail.com> |
|---|---|
| First post | 2016-07-02 21:31 -0700 |
| Last post | 2016-07-05 08:52 -0600 |
| Articles | 14 — 6 participants |
Back to article view | Back to comp.lang.python
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
| From | Lawrence D’Oliveiro <lawrencedo99@gmail.com> |
|---|---|
| Date | 2016-07-02 21:31 -0700 |
| Subject | Making 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]
| From | dieter <dieter@handshake.de> |
|---|---|
| Date | 2016-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]
| From | Lawrence D’Oliveiro <lawrencedo99@gmail.com> |
|---|---|
| Date | 2016-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]
| From | Michael Selik <michael.selik@gmail.com> |
|---|---|
| Date | 2016-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]
| From | Lawrence D’Oliveiro <lawrencedo99@gmail.com> |
|---|---|
| Date | 2016-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]
| From | dieter <dieter@handshake.de> |
|---|---|
| Date | 2016-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]
| From | Steven D'Aprano <steve+comp.lang.python@pearwood.info> |
|---|---|
| Date | 2016-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]
| From | Ian Kelly <ian.g.kelly@gmail.com> |
|---|---|
| Date | 2016-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]
| From | Steven D'Aprano <steve@pearwood.info> |
|---|---|
| Date | 2016-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]
| From | Ian Kelly <ian.g.kelly@gmail.com> |
|---|---|
| Date | 2016-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]
| From | Steven D'Aprano <steve@pearwood.info> |
|---|---|
| Date | 2016-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]
| From | Lawrence D’Oliveiro <lawrencedo99@gmail.com> |
|---|---|
| Date | 2016-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]
| From | Lawrence D’Oliveiro <lawrencedo99@gmail.com> |
|---|---|
| Date | 2016-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]
| From | Ian Kelly <ian.g.kelly@gmail.com> |
|---|---|
| Date | 2016-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