Is there a consensus on how to check a polymorphic instance?

M

Mike Meng

hi all,
I'm a newbie Python programmer with a C++ brain inside. I have a
lightweight framework in which I design a base class and expect user to
extend. In other part of the framework, I heavily use the instance of
this base class (or its children class). How can I ensure the instance
IS-A base class instance, since Python is a fully dynamic typing
language?

I searched and found several different ways to do this:

1. In `Text Processing in Python', David Mertz suggests using `hasattr'
to check abilities instead of type checking.

2. In ActiveState's Python Cookbook site, Alex Martelli suggested a
safe and accurate `look before you leap' technique on
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52291

3. In this group, some others suggest using isinstance() builtin
function to check instance.

Now my questions:
1. Is my design speaks native Pythonish? Such design is native in C++
and Java OO style, but I'm not sure about this in Python.

2. As I state in title, is there a consensus on how to check a
polymorphic instance? I find all the three ways above is a little bit
tedious compared with C++ and Java, in which compile-time type checking
ensures the interface protocol, any better ideas in Python?
Any comments will be appreciated.
 
D

Dan Perl

As you say yourself, many people in this group will probably recommend
solution #3. I would agree with them.

You should keep in mind that Alex's recipe (solution #2) was published in
2001 (before version 2.2 which introduced big changes in terms of OO) so I'm
not sure he would recommend that solution anymore. I'll leave it to him to
give his final judgement.

I haven't read Mertz's book and I don't know in what context he recommends
solution #1. Personally I see it useful in situations where you want to
check whether the class has a method like next() for example, so you want to
see whether the object is iterable. But if you implement a base class with
an attribute x1 it may be risky to just check objects for having that
attribute. The object may have an attribute with that name but its role may
be completely different than what you expect (e.g., your class may use x1
for a list, but your object is an instance of a class that uses x1 for a
string). Same thing with methods.

Go with isinstance(). That's what it's for.

Dan
 
S

Steven Bethard

Mike said:
I'm a newbie Python programmer with a C++ brain inside. I have a
lightweight framework in which I design a base class and expect user to
extend. In other part of the framework, I heavily use the instance of
this base class (or its children class). How can I ensure the instance
IS-A base class instance, since Python is a fully dynamic typing
language?

The short answer is that if you want to ensure an instance is-a subclass
of the base class, you should use isinstance:
.... def f(self):
.... raise NotImplementedError
........ def f(self):
.... return self.__class__
........ def f(self):
.... return self.__class__
....False

However it's often not necessary to go this route. Consider:
.... pass
........ try:
.... f = c.f
.... except AttributeError:
.... raise TypeError('argument to use_c must have an f method')
.... return f()
....Traceback (most recent call last):
File "<interactive input>", line 1, in ?
File "<interactive input>", line 5, in use_c
TypeError: argument to use_c must have an f method

Is it really necessary that the classes passed to your equivalent of
'use_c' are actually subclasses of your equivalent to 'HasF'? Or is it
just necessary that they support the appropriate methods? If it's the
second, I would just test for the appropriate methods and catch the
AttributeErrors (and possibly the TypeError that's thrown if 'f' isn't
callable).

If you can give a little more detail on your particular example, I (and
others) can probably give you more helpful suggestions...

Steve
 
M

Mike Meng

Thank you Dan,
Solution #1 is concluded from 'Text Processing in Python', section
1.1.3, `Pythonic Polymorphisim', around there, here is an example he
provides:

def toDOM(xml_src = None):
from xml.dom import minidom
if hasattr(xml_src, 'documentElement'):
return xml_src # it's already a DOM object
elif hasattr(xml_src, 'read'):
# it is something that knows how to read data
return minidom.parseString(xml_src.read())
elif type(xml_src) in (StringType, UnicodeType):
# it is a filename of an XML document
xml = open(xml_src).read()
return minidom.parseString(xml)
else:
raise ValueError, "Must be initialized with " + \
"filename, file-like object, or DOM object"
What's your opinion?
 
S

Steven Bethard

Mike said:
1. In `Text Processing in Python', David Mertz suggests using `hasattr'
to check abilities instead of type checking.

hasattr can give you problems if the attribute you're checking for is
calculated on the fly since hasattr basically does the same thing
getattr does:
.... def time():
.... def fget(self):
.... time.sleep(5)
.... return time.time()
.... return dict(fget=fget)
.... time = property(**time())
....1101191279.3729999

If you run the code above, you'll get the 5 second pause both during the
hasattr call and the c.time access. If any object you use calculates
attribute values on the fly, calling hasattr before directly accessing
the attribute will cause the value to be calculated twice.

Steve
 
M

Mike Meng

hi Steve,
Your example is helpful. Thank you.

Here is my problem. My project needs to parse some user input
string according to specific rules. The rules can be defined with a
string, a CSV file, an XML file, and other forms. Followed the standard
OO style, I designed a dictionary-like base class to abstract those
different sources and provides other parts a uniform interface to query
those rules. I put some hard code into the base class and expect user
to inherit from it.

Any good suggestion?

Mike
 
S

Steven Bethard

Mike said:
Here is my problem. My project needs to parse some user input
string according to specific rules. The rules can be defined with a
string, a CSV file, an XML file, and other forms. Followed the standard
OO style, I designed a dictionary-like base class to abstract those
different sources and provides other parts a uniform interface to query
those rules. I put some hard code into the base class and expect user
to inherit from it.

The big question, I guess, is what do you want to happen if a user did
not inherit from your base class, but still provides all the appropriate
functionality? In a dynamically-typed language like Python the answer
is usually that the user's class should still be considered valid,
though of course there are exceptions to this rule.

For example, say I write the following function:
.... for rule in rules:
.... print rule.name
....

Do I really care if the 'rules' object inherits from, say, the Rules
class? Probably not. I care that it is iterable, and that the items
that it contains have a name attribute. Here's a few different ways I
could write an object that conforms to this interface:
.... def __init__(self, name):
.... self.name = name
....
>>> process_rules([Rule(s) for s in 'abc'])
a
b
c
.... def __init__(self, names):
.... self.names = names
.... def __iter__(self):
.... for name in self.names:
.... yield Rule(name)
....
.... def __init__(self, names):
.... self.names = names
.... self.index = -1
.... def __iter__(self):
.... return iter([self]*len(self.names))
.... def __getattr__(self, attr):
.... if attr == 'name':
.... self.index += 1
.... return self.names[self.index]
.... raise AttributeError
....a
b
c

Of course, if you write your code like NastyRules, you should be drug
out into the street and shot ;) but the point is that there are a
variety of ways that a class could support the given interface and still
do everything you've asked it to. If you'd like to give your users the
option to implement the interface in whatever way seems most
appropriate, you shouldn't be testing isinstance:
>>> isinstance([Rule(s) for s in 'abc'], list) True
>>> isinstance(Rules('abc'), list) False
>>> isinstance(NastyRules('abc'), list)
False

In the example above, if you test isinstance, you disallow your user
from writing either of the other two implementations.

Note that you can still give useful error messages if you catch the
appropriate exceptions:
.... try:
.... rules = iter(rules)
.... except TypeError:
.... raise TypeError('process_rules argument must '
.... 'support iterator protocol')
.... for rule in rules:
.... try:
.... print rule.name
.... except AttributeError:
.... raise TypeError('process_rules argument must '
.... 'produce objects with name attribute')
....
>>> process_rules([Rule(s) for s in 'abc'])
a
b
c a
b
c a
b
c
Traceback (most recent call last):
File "<interactive input>", line 1, in ?
Traceback (most recent call last):
File "<interactive input>", line 1, in ?
File "<interactive input>", line 11, in process_rules
TypeError: process_rules argument must produce objects with name attribute

So again, the real question here is: If a user creates a class that
appropriately implements the interface, but doesn't inherit from your
base class, should their code fail?

Steve
 
D

Dan Perl

I was referring to a different kind of context in my posting. Not exactly
the code implementation but rather how is the code being used. Are you the
only one using this code and can you be confident that the function toDOM
will be called only with objects that it is designed to handle? Or are you
implementing this function as part of a module that will be used by
hundreds/thousands of users over the next ten years?

In other words, how much do you trust the test that if hasattr(xml_src,
'documentElement') is true then "it's already a DOM object"? What if a user
who downloads your code off the internet 2 years from now calls toDOM with
an object that he implemented and that has an attribute 'documentElement'
but that object behaves in a way totally different from what you expect for
a DOM object? Even you may make such a mistake if you use this code again
only one year from now, after forgetting how you implemented it.

OTOH, you may not have much of a choice but use the hasattr test in this
case if documentElement is an attribute that is commonly used by all the DOM
parser implementations and you want to be able to switch parsers without
changing your code. I am using the assumption that not all the parsers
inherit from a common base class or else you could use isinstance with that
class. I am not familiar enough with XML parsers so I don't know if
documentElement is such an attribute.

The other hasattr test in the example, for a 'read' attribute, is such a
case. 'read' is a commonly used attribute name for methods that read data.
It doesn't matter whether it is a file, or a stream, or a queue, or
whatever, as long as it is readable and read() returns a string. Mind you,
this is still an incomplete test because it does not guarantee the string to
be XML. But it is probably the best choice compared to testing for all the
possible sources of XML text that you might use.

I see actually two issues here. One is of checking inputs and that comes
down to safety vs. flexibility. You make a test very strict or very
flexible depending on many criteria. You have to consider your users,
future changes, etc.

The other issue is one of design. How do you design a set of classes that
need to follow a common interface but implement that interface in different
ways? You can create a base class and subclass all the other classes from
this one. But thanks to python's dynamic typing, if the base class is
abstract and all its attributes are overridden in the subclasses, you may as
well just forget about inheritance and just implement all the subclasses to
follow an interface protocol. That's what iterators and generators are.

So, getting back to our topic, if you use inheritance to implement your
classes or you have only one class, then use isinstance. If you have a set
of classes that do not have a common base class and instead just implement
an interface protocol, then use hasattr. Your question was about
polymorphic instances. That implies inheritance, but you may want to
consider also protocol implementation and that is what other posters are
suggesting.

I think that an interesting question here is whether inheritance or protocol
implementation is the preferred way in python. I'm not sure what the answer
is to that question. One advantage I see in inheritance is to allow
stronger checks like isinstance vs. hasattr. I come from a very
conservative background in software development and strong checks are deeply
rooted in my mind. But it looks to me that python is not about that or else
it wouldn't have dynamic typing.

Dan
 
D

Dan Perl

Steven Bethard said:
The big question, I guess, is what do you want to happen if a user did not
inherit from your base class, but still provides all the appropriate
functionality? In a dynamically-typed language like Python the answer is
usually that the user's class should still be considered valid, though of
course there are exceptions to this rule.

I have a question here, as we are discussing now protocol interfaces vs.
inheritance. Is using a class that implements a protocol without inheriting
from a base class still "polymorphism"? There are probably many definitions
for polymorphism and probably all those definitions can be interpreted in
such a way that they accept also protocols. But what I would like to hear
is what is the general opinion of people who use python. I am biased
because I come from a C++ and Java background and I am still used to a
certain practical meaning for "polymorphism". But it's beginning to dawn on
me that it is only a bias and polymorphism does apply also to python
protocol interfaces. Is that generally accepted?

Dan
 
S

Steven Bethard

Dan said:
I have a question here, as we are discussing now protocol interfaces vs.
inheritance. Is using a class that implements a protocol without inheriting
from a base class still "polymorphism"?

Some good definitions:

http://en.wikipedia.org/wiki/Polymorphism_(computer_science))

There are probably two types of polymorphism relevant to the current
discussion: parametric polymorphism and subtyping polymorphism. In
parametric polymorphism, a function is written so as to work with
objects of different types. Subtyping polymorphism is a subtype of
parametric polymorphism that restricts the polymorphism to only objects
that inherit from a specified base class.

In its most basic use, Python is inherently parametrically polymorphic
-- all functions are written so as to work with different types.
Consider a few simple functions:
.... return 'f(%s)' % x
........ return 2**x
....

Both of these functions work perfectly well with a wide range of objects:
>>> f(1), f(2.0), f('abc'), f([1,2,3]) ('f(1)', 'f(2.0)', 'f(abc)', 'f([1, 2, 3])')
>>> g(4), g(5.0), g(decimal.Decimal(6))
(16, 32.0, Decimal("64"))

I didn't have to do anything special to make them work this way. Any
object that supports __str__ can be passed to f, e.g.:

.... def __str__(self):
.... return 'str'
....'f(str)'

and any object that supports __rpow__ (or __coerce__, or some other way
to be raised to a power) can be passed to g, e.g.:
.... def __rpow__(self, other):
.... return other**other
....4

So both functions f and g are parametrically polymorphic. If you wanted
to artifically restrict f or g to support only subtype polymorphism, you
could do this with isinstance. In most cases, I would probably advise
against this -- if you're using isinstance to restrict your functions to
only subtype polymorphism, you're reducing the usefulness of your code.

This is not to say that there are never any times when you want to use
isinstance. Here's a good example from my code for a Bunch class[1]:

class Bunch(object):
...
def update(self, *args, **kwds):
if len(args) == 1:
other, = args
if isinstance(other, self.__class__):
other = other.__dict__
try:
self.__dict__.update(other)
except TypeError:
raise TypeError('cannot update Bunch with %s' %
type(other).__name__)
elif len(args) != 0:
raise TypeError('expected 1 argument, got %i' % len(args))
self.__dict__.update(kwds)

In the code here, we use isinstance to check on whether or not to use
the argument's __dict__. Now I could have written the isinstance
section as:

try:
other = other.__dict__
except AttributeError:
pass

and then the function would 'work' with any object that had a __dict__
attribute. But since other classes are not guaranteed to use __dict__
in the same way that Bunch does (e.g. they're not guaranteed to have all
attributes in their __dict__s), this would give some potentially
confusing results:
.... def x():
.... def get(self):
.... return 5
.... return dict(fget=get)
.... x = property(**x())
....Traceback (most recent call last):
File "<interactive input>", line 1, in ?
AttributeError: 'Bunch' object has no attribute 'x'

The point here is that I'm using isinstance here because I *know* there
are objects out there that, unlike Bunch objects, may have 'attributes'
not stored in their __dict__, and I *know* they might get passed to
Bunch.update. Since I can't appropriately support their use, I want the
user to be notified of this (with the TypeError).

Of course, I'm still making heavy use of the more general parametric
polymorphism because even if the object doesn't inherit from Bunch, I
still pass it to dict.update, which works with any type that supports
the mapping protocol (and a few other types besides). So if, in the
future dict.update gains support for some new type, my code doesn't
artifically restrict the type of parameter that can be passed to my
function.

Hope this was helpful (or at least vaguely intelligible) ;)

STeve


[1] Thanks to Peter Otten for the implementation suggestion
 
D

Donn Cave

I have a question here, as we are discussing now protocol interfaces vs.
inheritance. Is using a class that implements a protocol without inheriting
from a base class still "polymorphism"? There are probably many definitions
for polymorphism and probably all those definitions can be interpreted in
such a way that they accept also protocols. But what I would like to hear
is what is the general opinion of people who use python. I am biased
because I come from a C++ and Java background and I am still used to a
certain practical meaning for "polymorphism". But it's beginning to dawn on
me that it is only a bias and polymorphism does apply also to python
protocol interfaces. Is that generally accepted?

Not only are there many definitions of polymorphism, there are
several kinds of polymorphism, each with many definitions. You
could spend some time with a web search engine and get lots of
reading material on this, and I think you would find that a good
deal of it is coming from a point of view that kind of tends to
ignore object oriented programming altogether. Check it out.
If you want to stay with the more immediately relevant OOP
thinking on this, search for polymorphism and smalltalk.

Note that C++ and Java need two mechanisms in support of something
approximating parametric polymorphism. The subtype polymorphism
I think you're thinking of is the classic OO device, but it doesn't
support really generic functions and types. The template system
has been bolted onto these languages (I gather Java has one now)
for this purpose. With these two mechanisms, they more or less
support parametric polymorphism, in a statically typed, compiled
language. Python is not statically typed, of course, so where
the literature about parametric polymorphism focuses on types
you have to decide for yourself how important that is in principle.
In the end it of course doesn't really matter what you call it.

Donn Cave, (e-mail address removed)
 
D

Donn Cave

Steven Bethard said:
This is not to say that there are never any times when you want to use
isinstance. Here's a good example from my code for a Bunch class[1]:

class Bunch(object):
...
def update(self, *args, **kwds):
if len(args) == 1:
other, = args
if isinstance(other, self.__class__):
other = other.__dict__
try:
self.__dict__.update(other)
except TypeError:
raise TypeError('cannot update Bunch with %s' %
type(other).__name__)
elif len(args) != 0:
raise TypeError('expected 1 argument, got %i' % len(args))
self.__dict__.update(kwds)

In the code here, we use isinstance to check on whether or not to use
the argument's __dict__. ....

The point here is that I'm using isinstance here because I *know* there
are objects out there that, unlike Bunch objects, may have 'attributes'
not stored in their __dict__, and I *know* they might get passed to
Bunch.update. Since I can't appropriately support their use, I want the
user to be notified of this (with the TypeError).

Of course, I'm still making heavy use of the more general parametric
polymorphism because even if the object doesn't inherit from Bunch, I
still pass it to dict.update, which works with any type that supports
the mapping protocol (and a few other types besides). So if, in the
future dict.update gains support for some new type, my code doesn't
artifically restrict the type of parameter that can be passed to my
function.

Hope this was helpful (or at least vaguely intelligible) ;)

I don't really see that isinstance() was necessary in principle
here. Not that it makes any difference to me, but it looks like
you created this problem by defining these semantics in Bunch
but failing to assign any identifiable attribute - we could in
principle make another unrelated class "quack like a Bunch",
but we'd have no way to inform your update function of this.

Donn Cave, (e-mail address removed)
 
D

Dan Perl

Donn Cave said:
Steven Bethard said:
This is not to say that there are never any times when you want to use
isinstance. Here's a good example from my code for a Bunch class[1]:

class Bunch(object):
...
def update(self, *args, **kwds):
if len(args) == 1:
other, = args
if isinstance(other, self.__class__):
other = other.__dict__
try:
self.__dict__.update(other)
except TypeError:
raise TypeError('cannot update Bunch with %s' %
type(other).__name__)
elif len(args) != 0:
raise TypeError('expected 1 argument, got %i' % len(args))
self.__dict__.update(kwds)

In the code here, we use isinstance to check on whether or not to use
the argument's __dict__. ...

The point here is that I'm using isinstance here because I *know* there
are objects out there that, unlike Bunch objects, may have 'attributes'
not stored in their __dict__, and I *know* they might get passed to
Bunch.update. Since I can't appropriately support their use, I want the
user to be notified of this (with the TypeError).

Of course, I'm still making heavy use of the more general parametric
polymorphism because even if the object doesn't inherit from Bunch, I
still pass it to dict.update, which works with any type that supports
the mapping protocol (and a few other types besides). So if, in the
future dict.update gains support for some new type, my code doesn't
artifically restrict the type of parameter that can be passed to my
function.

Hope this was helpful (or at least vaguely intelligible) ;)

I don't really see that isinstance() was necessary in principle
here. Not that it makes any difference to me, but it looks like
you created this problem by defining these semantics in Bunch
but failing to assign any identifiable attribute - we could in
principle make another unrelated class "quack like a Bunch",
but we'd have no way to inform your update function of this.

I'm not sure what you mean by an "identifiable attribute", but I think we
are going back to an issue I pointed out in another posting in this thread.
It makes a huge difference whether your code is something that only you are
using and changing during the next few weeks or it's an off-the-shelf
library that will be maintained and changed for years from now and will be
used by many clients. What is "identifiable" to you (or even to someone in
the next cubicle) for the next few weeks may not be identifiable a few years
from now or to someone who downloaded your module from the web.

I hope this was "at least vaguely intelligible"!

Dan
 
D

Dan Perl

Donn Cave said:
Not only are there many definitions of polymorphism, there are
several kinds of polymorphism, each with many definitions. You
could spend some time with a web search engine and get lots of
reading material on this, and I think you would find that a good
deal of it is coming from a point of view that kind of tends to
ignore object oriented programming altogether. Check it out.
If you want to stay with the more immediately relevant OOP
thinking on this, search for polymorphism and smalltalk.

Note that C++ and Java need two mechanisms in support of something
approximating parametric polymorphism. The subtype polymorphism
I think you're thinking of is the classic OO device, but it doesn't
support really generic functions and types. The template system
has been bolted onto these languages (I gather Java has one now)
for this purpose. With these two mechanisms, they more or less
support parametric polymorphism, in a statically typed, compiled
language. Python is not statically typed, of course, so where
the literature about parametric polymorphism focuses on types
you have to decide for yourself how important that is in principle.
In the end it of course doesn't really matter what you call it.

Yes, you are right, subtype polymorphism is what I had in mind and I was
implicitly questioning on the acceptance of 'polymorphism' as one type or
the other, even though I was not aware of the formal separation between the
two types. Having learned of polymorphism years ago in the C++ and Java
context where it was used only the sense of subtype polymorphism, it was
still the way I interpreted the term in the subject line of this thread.
From now on I will keep in mind the more general meaning of parametric
polymorphism.

Dan
 
S

Steven Bethard

Donn said:
Steven Bethard wrote:
...
The point here is that I'm using isinstance here because I *know* there
are objects out there that, unlike Bunch objects, may have 'attributes'
not stored in their __dict__, and I *know* they might get passed to
Bunch.update. Since I can't appropriately support their use, I want the
user to be notified of this (with the TypeError).
[snip]

I don't really see that isinstance() was necessary in principle
here. Not that it makes any difference to me, but it looks like
you created this problem by defining these semantics in Bunch
but failing to assign any identifiable attribute

Yup. The reason I "needed" isinstance was that my implementation works
directly with __dict__ instead of creating some __dict__-like attribute
and writing the appropriate code in __getattr__ and __setattr__ to use
this __dict__-like attribute instead. I actually thought about using a
__dict__-like attribute instead of __dict__ for exactly this reason (so
that I could just check for the attribute instead of testing
isinstance), but it seemed like a lot of overhead for not much gain (at
least, for the intended uses of Bunch -- see the "generic object -
moving toward PEP" and "generic object implementation" threads.)

Steve
 
C

Carlos Ribeiro

hi Steve,
Your example is helpful. Thank you.

Here is my problem. My project needs to parse some user input
string according to specific rules. The rules can be defined with a
string, a CSV file, an XML file, and other forms. Followed the standard
OO style, I designed a dictionary-like base class to abstract those
different sources and provides other parts a uniform interface to query
those rules. I put some hard code into the base class and expect user
to inherit from it.

Your base class defines a "protocol" (loosely defined as a set of
methods, associated behavior & usage pattern). It's more than an
"interface", in strict C++/Java speak, because a protocol defines the
interaction between objects in a dynamic fashion.

In Python, you can use isinstance() to implement a safe, but somewhat
rigid, solution to your problem. Using hasattr() is another option,
that doesn't restrict you from using only derived classes; however,
it's more confusing, because you may mistake a partial implementation
of the protocol for a full one, depending on what & when you check.

The new-style approach to the problem is adaptation. I've written a
short article on adaptation, with some references to the adaptation
PEP, and also to PyProtocols, that is a recent implementation of the
idea that goes much further than the original PEP did. Some big
projects like Zope already use adaptation.

http://pythonnotes.blogspot.com/2004/11/what-is-adaptation.html

The use case for adaptation is as follows; you call adapt(object,
protocol), and the adapt call will return an object that is guaranteed
to support your protocol. Of course, if your object is from the
correct base class, then it's automatically returned. If the user
implements a new class from the scratch, but that happens to implement
your protocol, then he may implement the proper adaptation hooks to
make the adapt() call succeed.

The main advantage of using adaptation, in your case, is that it
cleans up the code somewhat. You call adapt() only once, and you can
simply use the object that is returned from it without fear. In
opposition to isinstance (which only does a simple inheritance check),
adapt() can do some work on the object it receives to 'transform' it
into a new object that supports the protocol. It's a much more dynamic
approach to the problem.

--
Carlos Ribeiro
Consultoria em Projetos
blog: http://rascunhosrotos.blogspot.com
blog: http://pythonnotes.blogspot.com
mail: (e-mail address removed)
mail: (e-mail address removed)
 
D

Donn Cave

"Dan Perl said:
I'm not sure what you mean by an "identifiable attribute", but I think we
are going back to an issue I pointed out in another posting in this thread.
It makes a huge difference whether your code is something that only you are
using and changing during the next few weeks or it's an off-the-shelf
library that will be maintained and changed for years from now and will be
used by many clients. What is "identifiable" to you (or even to someone in
the next cubicle) for the next few weeks may not be identifiable a few years
from now or to someone who downloaded your module from the web.

I wasn't really worried about people yet. Perhaps I should have
said more simply that there is no attribute, period, for this.
Instead, in effect, the (base) class itself is used as that attribute.
That's an unnecessary limitation, because we can easily support
the required semantics from an unrelated class.

I must have missed your post, but I infer that you're claiming that
subtype polymorphism is easier to comprehend. It wouldn't have
occurred to me. I do think the current situation is a little
defective when it comes to reasoning about complicated code, but
I don't think subtyping is a very obvious way to define interfaces.
On one hand, it obviously isn't true that all objects that might
support an interface must inherit that support from a common base,
and it also isn't true that all objects derived from a common base
reliably support that interface. How easy would it be to inherit
from "Bunch" and add some feature that confounds that function we
saw earlier with the isinstance()?

Donn Cave, (e-mail address removed)
 
D

Dan Perl

Donn Cave said:
I wasn't really worried about people yet. Perhaps I should have
said more simply that there is no attribute, period, for this.
Instead, in effect, the (base) class itself is used as that attribute.
That's an unnecessary limitation, because we can easily support
the required semantics from an unrelated class.

I must have missed your post, but I infer that you're claiming that
subtype polymorphism is easier to comprehend. It wouldn't have
occurred to me. I do think the current situation is a little
defective when it comes to reasoning about complicated code, but
I don't think subtyping is a very obvious way to define interfaces.
On one hand, it obviously isn't true that all objects that might
support an interface must inherit that support from a common base,
and it also isn't true that all objects derived from a common base
reliably support that interface. How easy would it be to inherit
from "Bunch" and add some feature that confounds that function we
saw earlier with the isinstance()?

I was rather saying that subtype polymorphism (through inheritance, rather
than protocols) and checking with isinstance is safer. You are right that
inheritance is not foolproof either. But if someone is inheriting from your
base class, they are bound to be more aware of the interface and to be more
careful when overriding an attribute that would completely change the
behavior of their objects. A protocol can be broken in many other ways. In
the case of a well known protocol like iterators most people know better
than to use the name "next" for any attribute other than a method used for
iteration. But if you create a little known protocol interface with a
poorly chosen attribute name like "x" it is very likely that someone is
going to use the same name in a class for some attribute that is completely
different (e.g., data instead of a method). And if they are not explicitly
inheriting from a base class they may never be aware they are obfuscating a
behavior that is being used somewhere in their code. Note that I said
"explicitly inheriting", which infers that people can make the same kind of
mistakes also if they inherit from some class without knowing it.

This discussion feels to me like deja vu. It's a kind of philosophical
discussion in which I've been involved before, with me on this side of the
argument and with other people being on the side that says that "python is
designed for people who rely more on their users and for users who are more
reliable, the other people should use other languages" (my own
paraphrasing). I'm not saying this with any disrespect toward the other
side. After all, this is probably inherent in a dynamically typed language,
and dynamic typing has other advantages instead. It's just something I
still have to get used to: what is python good for, in what kind of
applications it should be used and when should you just use a different
(perhaps a strongly typed) language.

Dan
 
D

Donn Cave

"Dan Perl said:
I was rather saying that subtype polymorphism (through inheritance, rather
than protocols) and checking with isinstance is safer. You are right that
inheritance is not foolproof either. But if someone is inheriting from your
base class, they are bound to be more aware of the interface and to be more
careful when overriding an attribute that would completely change the
behavior of their objects. A protocol can be broken in many other ways. In
the case of a well known protocol like iterators most people know better
than to use the name "next" for any attribute other than a method used for
iteration. But if you create a little known protocol interface with a
poorly chosen attribute name like "x" it is very likely that someone is
going to use the same name in a class for some attribute that is completely
different (e.g., data instead of a method). And if they are not explicitly
inheriting from a base class they may never be aware they are obfuscating a
behavior that is being used somewhere in their code. Note that I said
"explicitly inheriting", which infers that people can make the same kind of
mistakes also if they inherit from some class without knowing it.

This discussion feels to me like deja vu. It's a kind of philosophical
discussion in which I've been involved before, with me on this side of the
argument and with other people being on the side that says that "python is
designed for people who rely more on their users and for users who are more
reliable, the other people should use other languages" (my own
paraphrasing). I'm not saying this with any disrespect toward the other
side. After all, this is probably inherent in a dynamically typed language,
and dynamic typing has other advantages instead. It's just something I
still have to get used to: what is python good for, in what kind of
applications it should be used and when should you just use a different
(perhaps a strongly typed) language.

I'm very sympathetic to the advantages of static typing
(NB, I read here that Python is strongly, though dynamically,
typed. It is not statically typed.) Rather than embrace
subtype polymorphism through inheritance, however, I see it
as evidence that no one has figured out how to make static
typing really work with OOP. There has to be a better way
to do it.

Donn Cave, (e-mail address removed)
 
D

Dan Perl

Donn Cave said:
I'm very sympathetic to the advantages of static typing
(NB, I read here that Python is strongly, though dynamically,
typed. It is not statically typed.) Rather than embrace
subtype polymorphism through inheritance, however, I see it
as evidence that no one has figured out how to make static
typing really work with OOP. There has to be a better way
to do it.

You're right, I should have said statically typed instead of strongly typed.

But did you really mean "no one has figured out how to make *static* typing
really work with OOP" or did you mean *dynamic* typing?
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,743
Messages
2,569,478
Members
44,899
Latest member
RodneyMcAu

Latest Threads

Top