Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]
Groups > comp.lang.python > #9563 > unrolled thread
| Started by | Inside <fancheyujian@gmail.com> |
|---|---|
| First post | 2011-07-15 12:53 -0700 |
| Last post | 2011-07-16 14:06 +1000 |
| Articles | 10 — 5 participants |
Back to article view | Back to comp.lang.python
Re: list(),tuple() should not place at "Built-in functions" in documentation Inside <fancheyujian@gmail.com> - 2011-07-15 12:53 -0700
Type checking versus polymorphism (was: list(),tuple() should not place at "Built-in functions" in documentation) Ben Finney <ben+python@benfinney.id.au> - 2011-07-16 09:24 +1000
Re: Type checking versus polymorphism (was: list(),tuple() should not place at "Built-in functions" in documentation) Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2011-07-16 12:47 +1000
Re: Type checking versus polymorphism (was: list(), tuple() should not place at "Built-in functions" in documentation) Chris Rebert <clp2@rebertia.com> - 2011-07-15 22:02 -0700
Re: Type checking versus polymorphism (was: list(), tuple() should not place at "Built-in functions" in documentation) Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2011-07-16 16:33 +1000
Re: list(), tuple() should not place at "Built-in functions" in documentation Chris Angelico <rosuav@gmail.com> - 2011-07-16 09:27 +1000
Liskov substitution principle (was: list(), tuple() should not place at "Built-in functions" in documentation) Ben Finney <ben+python@benfinney.id.au> - 2011-07-16 10:04 +1000
Re: Liskov substitution principle (was: list(), tuple() should not place at "Built-in functions" in documentation) Chris Angelico <rosuav@gmail.com> - 2011-07-16 10:17 +1000
Re: Liskov substitution principle Ben Finney <ben+python@benfinney.id.au> - 2011-07-16 11:47 +1000
Re: list(),tuple() should not place at "Built-in functions" in documentation Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2011-07-16 14:06 +1000
| From | Inside <fancheyujian@gmail.com> |
|---|---|
| Date | 2011-07-15 12:53 -0700 |
| Subject | Re: list(),tuple() should not place at "Built-in functions" in documentation |
| Message-ID | <cf0e04cc-a065-4fc4-ab25-777adadafb81@glegroupsg2000goo.googlegroups.com> |
Why I use assertion,please check this code:
"""
class User(object):pass
class Student(User):pass
class Professional(User):pass
def add(user):
assert(user, User)
def add(users):
assert(users, (tuple, list))
#If necessary I'll also check every obj in the sequence to see whether it's a User.
"""
I just follow some coding rules of me:
1. Controlling "input" strictly.
2. In a function keep doubting on its parameters until they're checked.
3. Let potential errors raise as early as possible.
[toc] | [next] | [standalone]
| From | Ben Finney <ben+python@benfinney.id.au> |
|---|---|
| Date | 2011-07-16 09:24 +1000 |
| Subject | Type checking versus polymorphism (was: list(),tuple() should not place at "Built-in functions" in documentation) |
| Message-ID | <874o2nb00n.fsf@benfinney.id.au> |
| In reply to | #9563 |
Inside <fancheyujian@gmail.com> writes: > I just follow some coding rules of me: > 1. Controlling "input" strictly. Asserting that the input is of a specific type is too strict. Does your code work if the input is not a list or tuple? I suspect strongly that the answer is yes, it works fine with any sequence, even ones that don't inherit from ‘list’ nor ‘tuple’. It will probably work with any sequence; it amy even work with any iterable. Instead of insisting on specific types, you should support polymorphism: expect *behaviour* and allow any input that exhibits that behaviour. This is known as “duck typing”: you don't need to care whether it's a duck, you just need to know whether it walks like a duck and quacks like a duck. If it turns out to be a goose, but that won't affect your code, you shouldn't care. > 2. In a function keep doubting on its parameters until they're > checked. This is called “Look Before You Leap” (LBYL) programming, and is generally considered not Pythonic. Rather, “it is Easier to Ask Forgiveness than Permission” (EAFP) is the Python programming style, and fits with its widespread reliance on polymorphism (including “duck typing”). Accept the input, and use it as though it has the correct behaviour – without regard to what type is providing that behaviour. If it doesn't have the expected behaviour, either the type will complain (raising an exception that you can handle at an appropriate level in your code), or your comprehensive unit tests will detect the misbehaviour. If you don't have comprehensive unit tests, that's where you should put your effort of strict interface testing. Not type assertions in the application code. > 3. Let potential errors raise as early as possible. That is good practice. But you should not compromise polymorphism to that. -- \ “The whole area of [treating source code as intellectual | `\ property] is almost assuring a customer that you are not going | _o__) to do any innovation in the future.” —Gary Barnett | Ben Finney
[toc] | [prev] | [next] | [standalone]
| From | Steven D'Aprano <steve+comp.lang.python@pearwood.info> |
|---|---|
| Date | 2011-07-16 12:47 +1000 |
| Subject | Re: Type checking versus polymorphism (was: list(),tuple() should not place at "Built-in functions" in documentation) |
| Message-ID | <4e20fbc9$0$29979$c3e8da3$5496439d@news.astraweb.com> |
| In reply to | #9579 |
Ben Finney wrote: [...snip explanation of duck-typing...] > If you don't have comprehensive unit tests, that's where you should put > your effort of strict interface testing. Not type assertions in the > application code. I agree with everything Ben said here, but he has missed something even more fundamental. Type *checking* breaks duck-typing. Type *assertions* are even worse, because assertions aren't guaranteed to run. If you are using "assert isinstance(...)" to validate input data, there are situations where your validation step does not happen at all, and your code may just break in the least convenient way. So not only are you validating the wrong way, but sometimes you don't validate at all! Assertions are for testing internal program logic, not for validation. (I don't even like using assert for testing. How do you test your code with assertions turned off if you use assert for testing?) -- Steven
[toc] | [prev] | [next] | [standalone]
| From | Chris Rebert <clp2@rebertia.com> |
|---|---|
| Date | 2011-07-15 22:02 -0700 |
| Subject | Re: Type checking versus polymorphism (was: list(), tuple() should not place at "Built-in functions" in documentation) |
| Message-ID | <mailman.1099.1310792526.1164.python-list@python.org> |
| In reply to | #9592 |
On Fri, Jul 15, 2011 at 7:47 PM, Steven D'Aprano <steve+comp.lang.python@pearwood.info> wrote: <snip> > Assertions are for testing internal program logic, not for validation. > > (I don't even like using assert for testing. How do you test your code with > assertions turned off if you use assert for testing?) I would think that would only matter if either the asserted expressions caused side-effects or there was nontrivial logic in the AssertionError handler, which would indicate a rather screwy codebase and point to a possible PEBKAC issue that testing cannot hope to remedy. Cheers, Chris -- http://rebertia.com
[toc] | [prev] | [next] | [standalone]
| From | Steven D'Aprano <steve+comp.lang.python@pearwood.info> |
|---|---|
| Date | 2011-07-16 16:33 +1000 |
| Subject | Re: Type checking versus polymorphism (was: list(), tuple() should not place at "Built-in functions" in documentation) |
| Message-ID | <4e2130a8$0$29982$c3e8da3$5496439d@news.astraweb.com> |
| In reply to | #9598 |
Chris Rebert wrote:
> On Fri, Jul 15, 2011 at 7:47 PM, Steven D'Aprano
> <steve+comp.lang.python@pearwood.info> wrote:
> <snip>
>> Assertions are for testing internal program logic, not for validation.
>>
>> (I don't even like using assert for testing. How do you test your code
>> with assertions turned off if you use assert for testing?)
>
> I would think that would only matter if either the asserted
> expressions caused side-effects or there was nontrivial logic in the
> AssertionError handler, which would indicate a rather screwy codebase
> and point to a possible PEBKAC issue that testing cannot hope to
> remedy.
I'm not sure I follow you there...
For any piece of code, I claim that we should test both with and without
the -O switch. If I don't test it with -O, how do I know it works correctly
when run with -O? Even if I don't use assert yourself, I don't know if the
code I call uses assert badly and therefore is affected by -O.
(I recently discovered that ElementTree, in the Python standard library,
uses assert for data validation in at least some Python versions.)
Besides, I don't know what -O does, apart from disabling assertions. Maybe
nothing else. Maybe lots of things. It may change from version to version.
Who knows? Whatever it does, it *shouldn't* change the semantics of my
code, but it *could*, hence I better test it to find out if anything has
changed.
So are we agreed that it is wise to test code both with and without the -O
switch?
But here's the problem... the -O switch is global, and not module specific.
So if your test suite looks like this:
import mymodule
assert hasattr(mymodule, "__all__")
assert isinstance(mymodule.__version__, str)
assert mymodule.count_spam("spam spam spam") == 3
a, b, c = mymodule.aardvark(42)
assert a < b < c
when running with -0, all those tests LITERALLY go away and you're left with
a significantly smaller test suite:
import mymodule
a, b, c = mymodule.aardvark(42)
which is hardly better than no test at all.
--
Steven
[toc] | [prev] | [next] | [standalone]
| From | Chris Angelico <rosuav@gmail.com> |
|---|---|
| Date | 2011-07-16 09:27 +1000 |
| Subject | Re: list(), tuple() should not place at "Built-in functions" in documentation |
| Message-ID | <mailman.1088.1310772475.1164.python-list@python.org> |
| In reply to | #9563 |
On Sat, Jul 16, 2011 at 5:53 AM, Inside <fancheyujian@gmail.com> wrote: > def add(users): > assert(users, (tuple, list)) > #If necessary I'll also check every obj in the sequence to see whether it's a User. > > I just follow some coding rules of me: > 1. Controlling "input" strictly. > 2. In a function keep doubting on its parameters until they're checked. > 3. Let potential errors raise as early as possible. What you're doing there is writing code in Python, not writing Python code. To be more Pythonic, your code should actually stop caring about whether something is-a User, and instead simply care about whether or not it can be treated as a User. (And for Travaglia fans, yes, a User object WILL have an abuse() method.) Instead of asserting that the parameter is a User, just add it cheerfully to your list, and then when you iterate over the list and call some method on each one, you'll get an exception if one of them doesn't have that method. This allows a huge enhancement to polymorphism, in that you no longer need to worry about what your pointers are; in C++, you can run over a list of users and ask if they're all Students, but then you need to cast all those pointers if you're going to then ask them all what subjects they're studying. In Python, all you do is ask your list of objects what subjects they're studying - all the students will respond, and anything that doesn't know what "studying" is will throw an exception. The analogy with reality breaks down a bit here. I've seen plenty of students with no idea of what it means to study. But Python can handle that too - just 'del' the method in the subclass. ChrisA
[toc] | [prev] | [next] | [standalone]
| From | Ben Finney <ben+python@benfinney.id.au> |
|---|---|
| Date | 2011-07-16 10:04 +1000 |
| Subject | Liskov substitution principle (was: list(), tuple() should not place at "Built-in functions" in documentation) |
| Message-ID | <87zkkf9jl7.fsf_-_@benfinney.id.au> |
| In reply to | #9581 |
Chris Angelico <rosuav@gmail.com> writes:
> The analogy with reality breaks down a bit here. I've seen plenty of
> students with no idea of what it means to study. But Python can handle
> that too - just 'del' the method in the subclass.
No, please don't. That would break the Liskov substitution principle
<URL:https://secure.wikimedia.org/wikipedia/en/wiki/Liskov_substitution_principle>.
By inheriting from a type that provides a method, you're promising that
you will implement at least as much behaviour as the parent. If that
includes a ‘study’ method, then your subclass must also implement (or
inherit) that method.
Code can then be written to expect that, so long as the object inherits
from Student, it will at least have the same minimum level of behaviour
that a Student has.
If you inherit from Student, but delete the ‘study’ method, you would
break any code which assumes any Student has a ‘study’ method –
something that was explicitly promised in the API of Student.
Since you are advocating calling people students when they don't study,
it sounds instead like you want a different set of promises:
class Student(Person):
""" An enrolled student at this institution. """
def study(self, subject):
raise NotImplementedError
class LectureAttendee(Student):
""" Someone who comes to lectures. """
def attend(self, lecture):
pass
class StudentWhoActuallyStudies(Student):
""" A student who actually is capable of studying. """
def study(self, subject):
""" Actually apply my knowledge of how to study. """
alice = StudentWhoActuallyStudies("Alice")
bob = Student("Bob")
alice.study("chemistry") # actual study
bob.study("garden gnome painting") # not implemented!
Now both Alice and Bob fulfil the technical requirements of a student at
the institution, but the expectations of study capability are clear.
Any code using this implementation of Student knows that, if they want a
student who actually studies, they'd better ask for a more specific
type.
See? We can have overstretched analogies *and* remain within the Liskov
substitution principle.
--
\ Eccles: “I just saw the Earth through the clouds!” Lew: “Did |
`\ it look round?” Eccles: “Yes, but I don't think it saw me.” |
_o__) —The Goon Show, _Wings Over Dagenham_ |
Ben Finney
[toc] | [prev] | [next] | [standalone]
| From | Chris Angelico <rosuav@gmail.com> |
|---|---|
| Date | 2011-07-16 10:17 +1000 |
| Subject | Re: Liskov substitution principle (was: list(), tuple() should not place at "Built-in functions" in documentation) |
| Message-ID | <mailman.1095.1310775472.1164.python-list@python.org> |
| In reply to | #9587 |
On Sat, Jul 16, 2011 at 10:04 AM, Ben Finney <ben+python@benfinney.id.au> wrote: > def study(self, subject): > raise NotImplementedError > > See? We can have overstretched analogies *and* remain within the Liskov > substitution principle. > Hehe! Of course I was speaking utterly in jest, but this raises (sorry, never could resist a bad pun) another question: What if the base class implemented study(), and then LazyStudent subclasses Student but makes study() raise NotImpl? Would that break things? In a sense, it breaks the whole "this is a student so it should act like a student" rule. Suppose it raised UtterApathyError instead - does that break the LSP? Chris A PS. The world's first horseless signature... trapped in the air!
[toc] | [prev] | [next] | [standalone]
| From | Ben Finney <ben+python@benfinney.id.au> |
|---|---|
| Date | 2011-07-16 11:47 +1000 |
| Subject | Re: Liskov substitution principle |
| Message-ID | <87sjq79eu0.fsf@benfinney.id.au> |
| In reply to | #9588 |
Chris Angelico <rosuav@gmail.com> writes: > What if the base class implemented study(), and then LazyStudent > subclasses Student but makes study() raise NotImpl? Would that break > things? In a sense, it breaks the whole "this is a student so it > should act like a student" rule. That would break the Liskov substitution principle, yes. Anything that asks for a Student instance should receive an object that can do at least everything Student can do. If you want to implement something that can't do some of the things Student can do, the Liskov substitution principle says you are implementing some object that does *not* inherit from Student. This fits nicely with the principle (whose name I'm currently too lazy to look up) that says you should only use inheritance for “IS-A” relationships. If LazyStudent can't do everything Student can do, then it's false to say LazyStudent IS-A Student, so using inheritance for that would be to state something in code which isn't true. > Suppose it raised UtterApathyError instead - does that break the LSP? Probably, yes. Your point is taken though: it's not something that can be drawn along a bright line. -- \ “Those are my principles. If you don't like them I have | `\ others.” —Groucho Marx | _o__) | Ben Finney
[toc] | [prev] | [next] | [standalone]
| From | Steven D'Aprano <steve+comp.lang.python@pearwood.info> |
|---|---|
| Date | 2011-07-16 14:06 +1000 |
| Message-ID | <4e210e3d$0$29995$c3e8da3$5496439d@news.astraweb.com> |
| In reply to | #9563 |
Inside wrote:
> Why I use assertion,please check this code:
> """
> class User(object):pass
>
> class Student(User):pass
>
> class Professional(User):pass
>
> def add(user):
> assert(user, User)
This does not do what you think it does. All it does is, in some Python
versions, print
SyntaxWarning: assertion is always true, perhaps remove parentheses?
In other Python versions, it is a no-op: it does nothing.
Perhaps you meant this?
assert isinstance(user, User)
Ben has already posted why isinstance type-checking should usually be
avoided in favour of duck-typing ("if it looks like a duck, and sounds like
a duck, and swims like a duck, it might as well be a duck"). But let's
suppose you have good reason for sticking to an explicit type-check. The
problem now is with the assert! Assertions are not guaranteed to run. The
caller can turn them off by running Python with the -O (optimize) switch.
Another problem: AssertionError is the wrong sort of exception to raise on
bad arguments. It should normally be TypeError or ValueError, or some other
more specific exception, with a useful error message.
Assertions are for testing your own program logic, not for validating input.
For example, in one of my own libraries, I have this piece of code:
data = _countiter(data)
assert data.count == 0
total = sum(data)
n = data.count
assert n >= 0
# much later on...
return math.sqrt(value/n)
_countiter is a wrapper that keeps track of how many items have been
iterated over. I take an arbitrary iterator, wrap it, sum the values, then
check that the number of items is not a negative number. If it is, that's a
bug in my program logic, and I should find out as soon as possible, not
much later on when I try to take the square root of it.
Assertions should be rare, and never used for testing arguments (except,
maybe, for purely internal functions that only get called by your own
functions, never by the caller). If the caller ever sees an AssertionError
generated by your code, that is a bug in your code.
--
Steven
[toc] | [prev] | [standalone]
Back to top | Article view | comp.lang.python
csiph-web