Yes, I know its to "ensure" that parameters are valid but doesn't that
defeat the whole purpose of one of Python's main feature? Polymorphism
and code reuse. Who cares if a parameter is not of type(X), as long as
it has the methods and member variables necessary. If the parameter is
missing stuff, then an exception is thrown.
The reason I'm asking is because I don't use type checking at all but I
ses a lot of other code use it. So, is there a good reason I should be
using it too?
In general I agree with you. I can think of two cases where I use type
checking, at least temporarily:
One is where you have an API and you want some sort of polymorphism.
For instance, EmPy has an include API that takes either 1. a filename (a
sring) or 2. a file-like object. It does (from memory):
if type(fileOrFilename) is types.StringType:
# it's a string, so open a file
file = open(fileOrFilename, 'r')
else:
# it's a file-like object, so just treat it like one
file = fileOrFilename
[A design requirement of EmPy is that is 1.5.2 compatible, so no
"type(...) is str" or "use file, not open" comments, please.] In other
words, maybe for some special (invariably builtin) types, I'll do
something special, but otherwise I'll just assume the passed-in object
has the proper interface.
The other is debugging. If I'm building complex data structures and I
want to make sure that _I_, as the implementor of the system, don't make
some mistakes, I'll often put an
assert isinstance(argument, SomeSpecificClass)
simply because I know that for the development cycle the only type that
should be passed in there is one I've created, and so if, during
development, anything else ends up there I've made a mistake and I want
to catch it right away. (Such asserts would then be removed before the
API is frozen and the code is released.)
--
Erik Max Francis / m...@alcyone.com / http://www.alcyone.com/max/
__ San Jose, CA, USA / 37 20 N 121 53 W / &tSftDotIotE
/ \ This moment is mine / So I'm taking my time
\__/ Chante Moore
Church / http://www.alcyone.com/pyos/church/
A lambda calculus explorer in Python.
Possibilities (not necessarily disjoint):
1. Habit carried over or based on experience with other languages.
2. Situation-specific need such as changing behavior depending on
type.
3. Application of belief in "look-before-you-leap" rather than
"try-it-and-see".
4. Being careful in life- or mission-critical application.
> Yes, I know its to "ensure" that parameters are valid but doesn't
that
> defeat the whole purpose of one of Python's main feature?
Polymorphism
> and code reuse. Who cares if a parameter is not of type(X), as long
as
> it has the methods and member variables necessary. If the parameter
is
> missing stuff, then an exception is thrown.
>
> The reason I'm asking is because I don't use type checking at all
but I
> ses a lot of other code use it.
There have also been past postings advising against it, especially by
Alex Martelli, citing just the reason you gave above.
> So, is there a good reason I should be using it too?
Your decision.
Terry J. Reedy
I use it in my code generally as a combination of those two; I believe in
"try it and see, but if it fails it must fail atomically", i.e., a failure
does not change anything in the data structure. If I can not guarentee
this, I will do certain checks (usually just the type checking described
in the OP), which while you can never always truly *guarentee* that an
operation will succeed after those checks, you may be able to push the
occurance rate down to an acceptable level.
This especially happens when I'm creating an abstract OO API that I expect
others to fill in, and I have no control over the code that will execute,
nor do I wish to push responsibility for ensuring those properties onto
the programmer with no 'safety net' at all for detecting failures. (It is
their responsibility to meet the requirements, of course, but the more
help you offer them in doing it, the happier everybody is.) A subtle
failure to maintain some property might take the entire application down
with it and corrupt user data in difficult-to-debug or correct ways, so
IMHO it's worth the protection that a little 'look-before-you-leap' can
offer you in that case. (After all, losing user data is The Ultimate
Programming Sin, ranking well above Violating the Python Philosophy. ;-) )
Usually I leave an out, though, by declaring an empty "Tag" class from
which my top-level class derives, and checking against that rather then
the top-level class, so if someone REALLY knows what they are doing, they
can still write a class with that interface from scratch. (Which reminds
me I need to fix that in my code...) It is then easy enough to add that
tag class to any other class, even pre-existing ones, to deliberately
'bypass' the type checks.
> On Sun, 26 Jan 2003 23:13:29 -0500, Terry Reedy wrote:
>> 2. Situation-specific need such as changing behavior depending on
>> type.
>> 3. Application of belief in "look-before-you-leap" rather than
>> "try-it-and-see".
>
> I use it in my code generally as a combination of those two; I believe in
> "try it and see, but if it fails it must fail atomically", i.e., a failure
> does not change anything in the data structure. If I can not guarentee
I find a (very occasional) need for such "low-level atomicity" (more
often the atomicity must be ensured as part of a larger transaction
in an ACID context and thus it's not very useful to bend over backwards
to also guarantee it to such fine granularity), and have documented
the approach as a recipe in the Python Cookbook, online at:
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52291
I've named this approach "_Accurate_ look-before-you-leap". There
is a more detailed analysis and explanation of it in the O'Reilly
printed version of the Python Cookbook, of course. But the key issue
is that even in such very occasional situations type-checking is
hardly ever the best way -- rather, *feature-checking* (checking
that objects have the needed attributes and that certain operations
can be performed with them) is generally superior, in the same vein:
> this, I will do certain checks (usually just the type checking described
> in the OP), which while you can never always truly *guarentee* that an
> operation will succeed after those checks, you may be able to push the
> occurance rate down to an acceptable level.
...i.e., no guarantees, but better chances of "high-granularity
atomicity", and WITHOUT all the costs imposed by type-checking.
> Usually I leave an out, though, by declaring an empty "Tag" class from
> which my top-level class derives, and checking against that rather then
> the top-level class, so if someone REALLY knows what they are doing, they
> can still write a class with that interface from scratch. (Which reminds
But this means they still cannot use an otherwise perfectly acceptable
object they get from somewhere else -- they still have to wrap it with
a delegating-class instance just for the "boilerplate" satisfaction of
making your typechecks happy. Not optimal, IMHO, except perhaps in a
tiny subset of such already pretty-rare cases.
Generalized protocol-adaptation would go a very long way towards making
such debates moot, of course -- but as it doesn't seem likely we'll get
it any time soon, I think it's preferable to keep warning most coders
against the instinctive-but-inferior impulse to typecheck.
Alex
> je...@compy.attbi.com wrote:
>> I use it in my code generally as a combination of those two; I believe in
>> "try it and see, but if it fails it must fail atomically", i.e., a failure
>> does not change anything in the data structure. If I can not guarentee
>
> I find a (very occasional) need for such "low-level atomicity" (more
> often the atomicity must be ensured as part of a larger transaction
> in an ACID context and thus it's not very useful to bend over backwards
> to also guarantee it to such fine granularity), and have documented
> the approach as a recipe in the Python Cookbook, online at:
> http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52291
I still don't like that because just because class A has an "insert"
method and class B has an "insert" method does not mean that it means even
remotely the same thing, even if they have the same signiture. The fact
that the "Tag" class requires an explicit wrapping is a feature, not a
bug: First, I wouldn't expect that there will exist a class anywhere that
wouldn't *already* require a wrapper to work in my system, so adding the
Tag is about 10 chars more. Second, the Tag functions as a semantic kind of
declaration that "Yes, I mean *your* insert, not a list insert, not an
array insert, not an insert-tab-a-into-slot-b insert". If I'm going
through the effort of checking, I want to be *right*, such that it's the
other programmers 'fault' if they don't maintain the contract.
It's like a namespace for methods, sort of. Or a protocol declaration,
albiet a weak one. Simple name correspondence isn't enough to satisfy me
on those rare occasions I care.
That said I just looked through the code I've been thinking of and I
recently refactored it such that there are only two type checks now: One
in an equivalency-checking function to make sure two objects have the same
__class__ (a valid question in an equivalency determination, and only used
for automated testing anyhow), and the other to implement an (optional!)
feature where a node can declare that it will only accept certain types of
children, which makes sense to me. There used to be more but it looks like
they got eliminated in the refactoring. The first case is a wash anyhow,
because it's only used in testing, but in the second, *all* the subclasses
have method name equivalence; actual type checking is necessary, and there
are definately use cases where such a thing is desirable in my app. (And
by use cases, I mean that end-users will appreciate it, not just
developers.)
Short answer: No! I don't think I've yet seen a case where explicit
type-checking did anything but make code 'brittle' -- i.e. cause it to
fail when it receives a type that would have worked fine without the
type-check.
There are probably cases where you have to make an exception. In that
case I'd recommend that, instead of forcing a parameter to have a type
that you know works, just make sure that it isn't a type you know
doesn't work. Preserve the caller's freedom to pass you a type that
you didn't plan for, if it 'does the right thing.'