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


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

PyWart: More surpises via "implict conversion to boolean" (and other steaming piles!)

Started byRick Johnson <rantingrickjohnson@gmail.com>
First post2014-02-10 10:45 -0800
Last post2014-02-11 12:57 -0500
Articles 20 on this page of 22 — 12 participants

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


Contents

  PyWart: More surpises via "implict conversion to boolean" (and other steaming piles!) Rick Johnson <rantingrickjohnson@gmail.com> - 2014-02-10 10:45 -0800
    Re: PyWart: More surpises via "implict conversion to boolean" (and other steaming piles!) Mark Lawrence <breamoreboy@yahoo.co.uk> - 2014-02-10 19:15 +0000
    Re: PyWart: More surpises via "implict conversion to boolean" (and other steaming piles!) Ned Batchelder <ned@nedbatchelder.com> - 2014-02-10 14:17 -0500
    Re: PyWart: More surpises via "implict conversion to boolean" (and other steaming piles!) Rotwang <sg552@hotmail.co.uk> - 2014-02-10 21:12 +0000
      Re: PyWart: More surpises via "implict conversion to boolean" (and other steaming piles!) Ned Batchelder <ned@nedbatchelder.com> - 2014-02-10 17:00 -0500
      Re: PyWart: More surpises via "implict conversion to boolean" (and other steaming piles!) Terry Reedy <tjreedy@udel.edu> - 2014-02-10 17:59 -0500
    Re: PyWart: More surpises via "implict conversion to boolean" (and other steaming piles!) Chris Angelico <rosuav@gmail.com> - 2014-02-11 09:30 +1100
    Re: PyWart: More surpises via "implict conversion to boolean" (and other steaming piles!) Steven D'Aprano <steve@pearwood.info> - 2014-02-11 06:30 +0000
      Re: PyWart: More surpises via "implict conversion to boolean" (and other steaming piles!) Tim Chase <python.list@tim.thechases.com> - 2014-02-11 09:26 -0600
      Re: PyWart: More surpises via "implict conversion to boolean" (and other steaming piles!) Travis Griggs <travisgriggs@gmail.com> - 2014-02-11 07:36 -0800
        Re: PyWart: More surpises via "implict conversion to boolean" (and other steaming piles!) Jussi Piitulainen <jpiitula@ling.helsinki.fi> - 2014-02-11 18:07 +0200
          Re: PyWart: More surpises via "implict conversion to boolean" (and other steaming piles!) Chris Angelico <rosuav@gmail.com> - 2014-02-12 03:14 +1100
        Re: PyWart: More surpises via "implict conversion to boolean" (and other steaming piles!) Steven D'Aprano <steve@pearwood.info> - 2014-02-13 04:11 +0000
          Re: PyWart: More surpises via "implict conversion to boolean" (and other steaming piles!) Chris Angelico <rosuav@gmail.com> - 2014-02-13 15:30 +1100
            Re: PyWart: More surpises via "implict conversion to boolean" (and other steaming piles!) Roy Smith <roy@panix.com> - 2014-02-13 09:58 -0500
              Re: PyWart: More surpises via "implict conversion to boolean" (and other steaming piles!) Chris Angelico <rosuav@gmail.com> - 2014-02-14 06:17 +1100
          Re: PyWart: More surpises via "implict conversion to boolean" (and other steaming piles!) Tim Chase <python.list@tim.thechases.com> - 2014-02-13 05:39 -0600
          Re: PyWart: More surpises via "implict conversion to boolean" (and other steaming piles!) Tim Chase <python.list@tim.thechases.com> - 2014-02-13 05:51 -0600
          Re: PyWart: More surpises via "implict conversion to boolean" (and other steaming piles!) Ian Kelly <ian.g.kelly@gmail.com> - 2014-02-13 15:00 -0700
      Re: PyWart: More surpises via "implict conversion to boolean" (and other steaming piles!) Chris Angelico <rosuav@gmail.com> - 2014-02-12 02:52 +1100
      Re: PyWart: More surpises via "implict conversion to boolean" (and other steaming piles!) Travis Griggs <travisgriggs@gmail.com> - 2014-02-11 08:19 -0800
      Re: PyWart: More surpises via "implict conversion to boolean" (and other steaming piles!) Terry Reedy <tjreedy@udel.edu> - 2014-02-11 12:57 -0500

Page 1 of 2  [1] 2  Next page →


#65836 — PyWart: More surpises via "implict conversion to boolean" (and other steaming piles!)

FromRick Johnson <rantingrickjohnson@gmail.com>
Date2014-02-10 10:45 -0800
SubjectPyWart: More surpises via "implict conversion to boolean" (and other steaming piles!)
Message-ID<85c2698c-d681-4511-b111-bb1e549ece93@googlegroups.com>
## START CODE ###########################################
def foo():
    # foo represents a patternless function
    # or method that returns a Boolean value
    # based on some internal test.
    #
    if 1==1:
        return True
    return False
#
# The fun begins when two tiny chars are forgotten,
# however, since the code is legal, python will happily
# give us the wrong answer.
#
if foo: # <- forgot parenthesis!
    print 'implicit conversion to bool bites!'
else:
    #
    # This block will NEVER execute because foo is
    # ALWAYS True!
    #
#
# Some introspection to understand why this happened.
#
print 'foo =', foo
print 'bool(foo) ->', bool(foo)
#
## END CODE #############################################

It's obvious i did not follow the syntactical rules of
Python, i understand that, however, there are three design
flaws here that are contributing to this dilemma:

    1. Implicit conversion to Boolean is evil

    2. Conditionals should never accept parameter-less
    functions. If you want to check if a callable is
    True or False, then use "if bool(callable)". Any
    usage of a bare callable in conditionals should
    raise SyntaxError.

    3. Implicit introspection is evil, i prefer all
    references to a callable's names to result in a CALL
    to that callable, not an introspection!
    Introspection should ALWAYS be explicit!

For a long time i thought Python's idea of a returning the
value of a callable-- who's name is unadorned with "(...)"
--was a good idea, however, i am now wholly convinced that
this design is folly, and the reason is two fold:

    1. Parenthesis should not be required for parameter-
    less functions. I realize this is a bit more
    complicated in languages like Python where
    attributes are exposed to the public, but still, not
    reason enough to require such onerous typing.

    2. Implicit introspection is evil. I would prefer an
    explicit method attached to all callables over a
    sugar for "callable.call". We should never consume
    syntactical sugars UNLESS they can greatly reduce
    the density of code (like math operators for
    instance!)

[toc] | [next] | [standalone]


#65839

FromMark Lawrence <breamoreboy@yahoo.co.uk>
Date2014-02-10 19:15 +0000
Message-ID<mailman.6634.1392059753.18130.python-list@python.org>
In reply to#65836
On 10/02/2014 18:45, Rick Johnson wrote:
> ## START CODE ###########################################
> def foo():
>      # foo represents a patternless function
>      # or method that returns a Boolean value
>      # based on some internal test.
>      #
>      if 1==1:
>          return True
>      return False
> #
> # The fun begins when two tiny chars are forgotten,
> # however, since the code is legal, python will happily
> # give us the wrong answer.
> #
> if foo: # <- forgot parenthesis!
>      print 'implicit conversion to bool bites!'
> else:
>      #
>      # This block will NEVER execute because foo is
>      # ALWAYS True!
>      #
> #
> # Some introspection to understand why this happened.
> #
> print 'foo =', foo
> print 'bool(foo) ->', bool(foo)
> #
> ## END CODE #############################################
>
> It's obvious i did not follow the syntactical rules of
> Python, i understand that, however, there are three design
> flaws here that are contributing to this dilemma:
>
>      1. Implicit conversion to Boolean is evil
>
>      2. Conditionals should never accept parameter-less
>      functions. If you want to check if a callable is
>      True or False, then use "if bool(callable)". Any
>      usage of a bare callable in conditionals should
>      raise SyntaxError.
>
>      3. Implicit introspection is evil, i prefer all
>      references to a callable's names to result in a CALL
>      to that callable, not an introspection!
>      Introspection should ALWAYS be explicit!
>
> For a long time i thought Python's idea of a returning the
> value of a callable-- who's name is unadorned with "(...)"
> --was a good idea, however, i am now wholly convinced that
> this design is folly, and the reason is two fold:
>
>      1. Parenthesis should not be required for parameter-
>      less functions. I realize this is a bit more
>      complicated in languages like Python where
>      attributes are exposed to the public, but still, not
>      reason enough to require such onerous typing.
>
>      2. Implicit introspection is evil. I would prefer an
>      explicit method attached to all callables over a
>      sugar for "callable.call". We should never consume
>      syntactical sugars UNLESS they can greatly reduce
>      the density of code (like math operators for
>      instance!)
>

This particular PyWart would immediately be caught if another PyWart, 
namely the unittest module, were to be used to catch this programming error.

All of your preferences can be met by raising an issue on the bug 
tracker and providing a patch that changes code, docs and test suites as 
appropriate.  Simples :)

-- 
My fellow Pythonistas, ask not what our language can do for you, ask 
what you can do for our language.

Mark Lawrence

---
This email is free from viruses and malware because avast! Antivirus protection is active.
http://www.avast.com

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


#65840

FromNed Batchelder <ned@nedbatchelder.com>
Date2014-02-10 14:17 -0500
Message-ID<mailman.6635.1392059870.18130.python-list@python.org>
In reply to#65836
On 2/10/14 1:45 PM, Rick Johnson wrote:
> ## START CODE ###########################################
> def foo():
>      # foo represents a patternless function
>      # or method that returns a Boolean value
>      # based on some internal test.
>      #
>      if 1==1:
>          return True
>      return False
> #
> # The fun begins when two tiny chars are forgotten,
> # however, since the code is legal, python will happily
> # give us the wrong answer.
> #
> if foo: # <- forgot parenthesis!
>      print 'implicit conversion to bool bites!'
> else:
>      #
>      # This block will NEVER execute because foo is
>      # ALWAYS True!
>      #
> #
> # Some introspection to understand why this happened.
> #
> print 'foo =', foo
> print 'bool(foo) ->', bool(foo)
> #
> ## END CODE #############################################
>
> It's obvious i did not follow the syntactical rules of
> Python, i understand that, however, there are three design
> flaws here that are contributing to this dilemma:
>
>      1. Implicit conversion to Boolean is evil
>
>      2. Conditionals should never accept parameter-less
>      functions. If you want to check if a callable is
>      True or False, then use "if bool(callable)". Any
>      usage of a bare callable in conditionals should
>      raise SyntaxError.
>
>      3. Implicit introspection is evil, i prefer all
>      references to a callable's names to result in a CALL
>      to that callable, not an introspection!
>      Introspection should ALWAYS be explicit!
>
> For a long time i thought Python's idea of a returning the
> value of a callable-- who's name is unadorned with "(...)"
> --was a good idea, however, i am now wholly convinced that
> this design is folly, and the reason is two fold:
>
>      1. Parenthesis should not be required for parameter-
>      less functions. I realize this is a bit more
>      complicated in languages like Python where
>      attributes are exposed to the public, but still, not
>      reason enough to require such onerous typing.
>
>      2. Implicit introspection is evil. I would prefer an
>      explicit method attached to all callables over a
>      sugar for "callable.call". We should never consume
>      syntactical sugars UNLESS they can greatly reduce
>      the density of code (like math operators for
>      instance!)
>

It seems like you are only looking at how to improve the error you just 
stumbled over, and not how the full proposal would work out, or even if 
you would like it better.

You haven't made the entire idea explicit yet.  How would I pass the 
function foo to another function?  The word "foo" now means, invoke foo. 
  You mean an explicit method attached to callables, like 
"foo.as_callable" ?   But why doesn't the word "foo" there invoke foo? 
Surely the word "as_callable" isn't special, so is it that 
"foo.anything" means direct attribute access on foo?  So to perform 
attribute access on the result of foo I need "foo().something" ?  In 
what cases does the word foo invoke the function, and when doesn't it?

It's not possible to make a programming language error-proof.  There 
will always be mistakes programmers can make.

-- 
Ned Batchelder, http://nedbatchelder.com

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


#65844

FromRotwang <sg552@hotmail.co.uk>
Date2014-02-10 21:12 +0000
Message-ID<ldbfbo$i5t$1@dont-email.me>
In reply to#65836
On 10/02/2014 18:45, Rick Johnson wrote:
> [...]
>
>      3. Implicit introspection is evil, i prefer all
>      references to a callable's names to result in a CALL
>      to that callable, not an introspection!

So, for example, none of

     isinstance(x, myclass)

     map(myfunc, range(10))

     x = property(x_get, x_set)

would still work?

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


#65845

FromNed Batchelder <ned@nedbatchelder.com>
Date2014-02-10 17:00 -0500
Message-ID<mailman.6638.1392069630.18130.python-list@python.org>
In reply to#65844
On 2/10/14 4:12 PM, Rotwang wrote:
> On 10/02/2014 18:45, Rick Johnson wrote:
>> [...]
>>
>>      3. Implicit introspection is evil, i prefer all
>>      references to a callable's names to result in a CALL
>>      to that callable, not an introspection!
>
> So, for example, none of
>
>      isinstance(x, myclass)
>
>      map(myfunc, range(10))
>
>      x = property(x_get, x_set)
>
> would still work?

I guess neither would:

     except ValueError:

:(

-- 
Ned Batchelder, http://nedbatchelder.com

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


#65849

FromTerry Reedy <tjreedy@udel.edu>
Date2014-02-10 17:59 -0500
Message-ID<mailman.6641.1392073188.18130.python-list@python.org>
In reply to#65844
On 2/10/2014 4:12 PM, Rotwang wrote:
> On 10/02/2014 18:45, Rick Johnson wrote:
>> [...]
>>
>>      3. Implicit introspection is evil, i prefer all
>>      references to a callable's names to result in a CALL
>>      to that callable, not an introspection!
>
> So, for example, none of
>      isinstance(x, myclass)
>      map(myfunc, range(10))
>      x = property(x_get, x_set)
> would still work?

No. That is what makes this a troll post, for fun, rather than a serious 
proposal.

-- 
Terry Jan Reedy

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


#65847

FromChris Angelico <rosuav@gmail.com>
Date2014-02-11 09:30 +1100
Message-ID<mailman.6640.1392071413.18130.python-list@python.org>
In reply to#65836
On Tue, Feb 11, 2014 at 5:45 AM, Rick Johnson
<rantingrickjohnson@gmail.com> wrote:
> if foo: # <- forgot parenthesis!
>     print 'implicit conversion to bool bites!'

You also forgot the parentheses on the second line, and that's nothing
to do with boolification :)

ChrisA

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


#65891

FromSteven D'Aprano <steve@pearwood.info>
Date2014-02-11 06:30 +0000
Message-ID<52f9c392$0$11128$c3e8da3@news.astraweb.com>
In reply to#65836
On Mon, 10 Feb 2014 10:45:40 -0800, Rick Johnson wrote:

> ## START CODE ########################################### 
> def foo():
>     # foo represents a patternless function 

Patternless? I have never heard that term before in this context. Do you 
mean a parameter-less or argument-less function?


>     # or method that returns a Boolean value 
>     # based on some internal test.
>     #
>     if 1==1:
>         return True
>     return False

This always returns True, since 1 always equals 1.


> # The fun begins when two tiny chars are forgotten, 
> # however, since the code is legal, python will happily 
> # give us the wrong answer.
> #
> if foo: # <- forgot parenthesis!
>     print 'implicit conversion to bool bites!'

No it doesn't. It rocks. You have found one tiny little disadvantage, 
about the size of a mote of dust floating: if you write functions that 
take no arguments (already a code-smell) and forget the brackets 
(mistakes will happen...) and have no tests to ensure that both branches 
of the `if` are tested (what, are you careless and negligent?), then you 
might be bitten by a bug of your own creation.

Compared to that mote, the usefulness and convenience of duck-typing bools 
is about the size of Mt Everest.


> else:
>     #
>     # This block will NEVER execute because foo is 
>     # ALWAYS True!

Correct. And since foo() is also always True, there is no difference.



[...]
> It's obvious i did not follow the syntactical rules of Python, i
> understand that, 

No you don't understand that. You *did* follow the syntactical rules of 
Python. `if foo` is perfectly correct syntax, if it were not, you would 
have got a SyntaxError exception at compile-time.

You need to understand the difference between syntax and semantics. This 
is invalid English syntax:

"Cat mat on sat the."

This is valid syntax, but semantically wrong:

"The mat sat on the cat."

This is both syntactically and semantically correct:

"The cat sat on the mat."


> however, there are three design flaws here that are
> contributing to this dilemma:
> 
>     1. Implicit conversion to Boolean is evil

It's not implicit conversion. You're not understanding what is going on. 
foo is not converted to a bool, foo remains a function object. Rather, 
it's duck-typing truthiness, which is no different from any other duck-
typing. If it swims like a bool and quacks like a bool, it might as well 
be a bool.

Duck-typing is a design feature, not a flaw.


>     2. Conditionals should never accept parameter-less functions.

How is the conditional supposed to know that its term is a function? 
What's so special about parameter-less functions? You can forget to call 
functions with any number of parameters, especially if they take default 
values.

Let's suppose that you get your way. Which of the following if-tests 
should be prohibited, and when should they be prohibited? At compile time?

if random.random() > 0.5:
    spam = lambda x=23: x
    eggs = 42
else:
    spam = 42
    eggs = lambda x=23: x

if spam:
    print "spam is a truthy value"
if eggs:
    print "eggs is a truthy value"




>     If you
>     want to check if a callable is True or False, then use "if
>     bool(callable)". 

Ewww. That's horrible. bool() should only be used to get a canonical bool 
object, e.g. for writing to a database or something. It should never be 
used just because you are scared of Python's power.



>     Any usage of a bare callable in conditionals should
>     raise SyntaxError.

Ah, so it should happen at compile-time, not run-time? I'm afraid you're 
not as clear about how Python operates as you perhaps think you are.


>     3. Implicit introspection is evil, i prefer all references to a
>     callable's names to result in a CALL to that callable, not an
>     introspection! Introspection should ALWAYS be explicit!

Again, this is a complete misunderstanding of Python's execution model. 
It is true that callables have names, but they are only used for 
displaying error messages in tracebacks. Otherwise, callables are no 
different from any other object: they can have zero, one or many names 
bound to them, and referring to the name gives you access to the object 
itself.

spam = <any object at all, whether a function or not>
spam  # this is a reference to the object

Introspection has nothing to do with this.



> For a long time i thought Python's idea of a returning the value of a
> callable-- who's name is unadorned with "(...)" --was a good idea,
> however, i am now wholly convinced that this design is folly,

Oh well, not everyone has the sense to recognise good design. You should 
do more Python programming, and less ranting, you might learn something.


> and the
> reason is two fold:
> 
>     1. Parenthesis should not be required for parameter- less functions.

Of course they should. Firstly, parameter-less functions are a code-
smell, and ought to be discouraged. Secondly, even if you have a good 
reason for using one -- for example, random.random -- then the difference 
between referring to the object and calling the object should be clear.

With Python's correct design, we have:

spam  # always, without exception, refers to the object
spam()  # always, without exception, calls the object

With your suggested design, we would have:

spam  # sometimes refers to the object, sometimes calls the object
spam()  # always calls the object

Ruby makes this mistake, and is a lessor language for it.


>     I realize this is a bit more complicated in languages like Python
>     where attributes are exposed to the public, but still, not reason
>     enough to require such onerous typing.

What does the presence or absence of attributes have to do with this?


>     2. Implicit introspection is evil. I would prefer an explicit method
>     attached to all callables over a sugar for "callable.call". We
>     should never consume syntactical sugars UNLESS they can greatly
>     reduce the density of code (like math operators for instance!)

I do not understand what you are trying to say here.



-- 
Steven

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


#65918

FromTim Chase <python.list@tim.thechases.com>
Date2014-02-11 09:26 -0600
Message-ID<mailman.6676.1392132375.18130.python-list@python.org>
In reply to#65891
On 2014-02-11 06:30, Steven D'Aprano wrote:
> You need to understand the difference between syntax and semantics.
> This is invalid English syntax:
> 
> "Cat mat on sat the."
> 
> This is valid syntax, but semantically wrong:
> 
> "The mat sat on the cat."
> 
> This is both syntactically and semantically correct:
> 
> "The cat sat on the mat."

And there are times you *do* want to do unconventional things with
the language, and Python allows that:

http://www.catster.com/files/600px-cat-hiding-under-rug.jpg

because in that particular use case, it *is* semantically correct.

> With Python's correct design, we have:
> 
> spam  # always, without exception, refers to the object
> spam()  # always, without exception, calls the object
> 
> With your suggested design, we would have:
> 
> spam  # sometimes refers to the object, sometimes calls the object
> spam()  # always calls the object
> 
> Ruby makes this mistake, and is a lessor language for it.

One of the (many) reasons Ruby drives me nuts.

-tkc

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


#65919

FromTravis Griggs <travisgriggs@gmail.com>
Date2014-02-11 07:36 -0800
Message-ID<mailman.6677.1392133008.18130.python-list@python.org>
In reply to#65891
On Feb 10, 2014, at 10:30 PM, Steven D'Aprano <steve@pearwood.info> wrote:

>> 
>>    1. Parenthesis should not be required for parameter- less functions.
> 
> Of course they should. Firstly, parameter-less functions are a code-
> smell, and ought to be discouraged. Secondly, even if you have a good 
> reason for using one -- for example, random.random -- then the difference 
> between referring to the object and calling the object should be clear.

Interesting. Can you clarify or provide some links to the "parameter-less functions are a code-smell” bit?

I agree with your points about consistency. I disagree with the original poster that niladic functions should have a different syntax than the others. I empathize with him, I’ve made the same mistake before (being an ardent Smalltalker in the past, it’s an easy habit to have bite you). But the consistency is more important. And in python, things “happen” when parentheses appear. I just accept that.

OTOH, I’m not sure I’ve heard the parameters-less functions are a code one? Is it just loose functions that you’re referring to? As opposed to methods (which are just bound functions)? I could maybe accept that. But methods with fewer arguments, and even none, are a desirable thing. There are code smells that are the opposite in fact, methods with long parameter lists are generally seen as code smell (“passing a paragraph”).

Anyway, I’d love to understand better what you see as the code smell and why.

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


#65922

FromJussi Piitulainen <jpiitula@ling.helsinki.fi>
Date2014-02-11 18:07 +0200
Message-ID<qotr479ljkl.fsf@ruuvi.it.helsinki.fi>
In reply to#65919
Travis Griggs writes:

> in fact, methods with long parameter lists are generally seen as

"If you have a predicate with ten arguments, you probably forgot some"
(heard long time ago over in the Prolog world).

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


#65923

FromChris Angelico <rosuav@gmail.com>
Date2014-02-12 03:14 +1100
Message-ID<mailman.6679.1392135284.18130.python-list@python.org>
In reply to#65922
On Wed, Feb 12, 2014 at 3:07 AM, Jussi Piitulainen
<jpiitula@ling.helsinki.fi> wrote:
> Travis Griggs writes:
>
>> in fact, methods with long parameter lists are generally seen as
>
> "If you have a predicate with ten arguments, you probably forgot some"
> (heard long time ago over in the Prolog world).

Conversely:

"Thirteen pushes for each call! Whew. But now we have our files open."

(from an assembly language programming tutorial, on the DosOpen API in OS/2)

ChrisA

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


#66149

FromSteven D'Aprano <steve@pearwood.info>
Date2014-02-13 04:11 +0000
Message-ID<52fc45e6$0$11128$c3e8da3@news.astraweb.com>
In reply to#65919
On Tue, 11 Feb 2014 07:36:34 -0800, Travis Griggs wrote:

> On Feb 10, 2014, at 10:30 PM, Steven D'Aprano <steve@pearwood.info>
> wrote:
> 
> 
>>>    1. Parenthesis should not be required for parameter- less
>>>    functions.
>> 
>> Of course they should. Firstly, parameter-less functions are a code-
>> smell, and ought to be discouraged. Secondly, even if you have a good
>> reason for using one -- for example, random.random -- then the
>> difference between referring to the object and calling the object
>> should be clear.
> 
> Interesting. Can you clarify or provide some links to the
> "parameter-less functions are a code-smell” bit?


Functions map a value to another value. They can be one-to-one, or many-
to-one. (Mathematically, they cannot be one-to-many or many-to-many, 
that's called a relation.) What about zero-to-one?

If the function always returns the same result, e.g.:

def spam():
    return "spam spam spam"


why are you using a function? Just create a constant and use that. 
Calling a function which always returns the same value is a code smell. 
That's not to say it is always wrong, but it smells a bit off.

How about zero-to-many functions? E.g. you have a situation where calling 
function() twice might return different values. Okay, here's an example:

give_me_an_even_number()
=> returns 42
give_me_an_even_number()
=> returns 23

Hmmm. There's a bug in give_me_an_even_number(). How do I reproduce that 
bug? What arguments do I pass? Oh, the same no-arguments as for the 
working call.

Clearly, the function must have *hidden state*. Hidden state (e.g. a 
global variable) makes it hard to reason about the function call, since 
you don't know what the hidden state is. So that's also a bit smelly.

Hidden state is generally bad, because it makes it hard to reason about 
the function call, hard to reproduce results, hard to debug, hard to 
test. Think about the difference in difficulty in confirming that 
math.sin() of some value x returns the value 0.5, and confirming that 
random.random() of some hidden state returns a specific value:

py> assert math.sin(0.5235987755982989) == 0.5

versus:

py> state = random.getstate()
py> random.seed(12345)
py> assert random.random() == 0.41661987254534116
py> random.setstate(state)




[...]
> OTOH, I’m not sure I’ve heard the parameters-less functions are a code
> one? Is it just loose functions that you’re referring to? As opposed to
> methods (which are just bound functions)? I could maybe accept that. But
> methods with fewer arguments, and even none, are a desirable thing.

Methods that appear to take zero arguments actually take one argument, it 
is just that it is written in a different place:

"some string".upper()

is merely different syntax for:

upper("some string")

with the bonus that str.upper and MyClass.upper live in different 
namespaces and so can do different things. So I have no problem with zero-
argument methods, or functions with default values.

Remember that a code smell does not mean the code is bad. Only that it 
needs to be looked at a bit more carefully. Perhaps it is bad. Or 
perhaps, like durian fruit, it smells pretty awful but tastes really good.


> There are code smells that are the opposite in fact, methods with long
> parameter lists are generally seen as code smell (“passing a
> paragraph”).

Absolutely! You'll get no disagreement from me there.


-- 
Steven

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


#66152

FromChris Angelico <rosuav@gmail.com>
Date2014-02-13 15:30 +1100
Message-ID<mailman.6824.1392265865.18130.python-list@python.org>
In reply to#66149
On Thu, Feb 13, 2014 at 3:11 PM, Steven D'Aprano <steve@pearwood.info> wrote:
> Think about the difference in difficulty in confirming that
> math.sin() of some value x returns the value 0.5, and confirming that
> random.random() of some hidden state returns a specific value:
>
> py> assert math.sin(0.5235987755982989) == 0.5
>
> versus:
>
> py> state = random.getstate()
> py> random.seed(12345)
> py> assert random.random() == 0.41661987254534116
> py> random.setstate(state)

Really, the assertion just requires the setting of the seed and the
call to random.random(); the other two are to ensure that you don't
fiddle with anything else that's using random.random(). And since
random.random() is actually just a bound method of some module-level
object, you can actually just create the exact same thing with
explicit rather than implicit state:

>>> random.Random(12345).random()
0.41661987254534116

Which doesn't tamper with the default object's state.

Whether it's a module-level function, a bound method, a closure, or a
callable object, a zero-arg function in Python always has some kind of
implicit state. The question is, what is it doing with it? In the case
of random number generation, maintained state is critical (and making
it implicit is usually sufficient); similar with functions like
input(), where the return value doesn't really depend on the argument
at all [1], and of course anything that iterates over an object is
going to need to change some kind of state (either a pointer, or the
actual data, depending on whether you're looking at eg
iter([1,2,3]).next or [1,2,3].pop). Do you know of any functions in
Python that don't use any implicit state and don't take arguments? I
can't think of any, but of course that proves nothing.

ChrisA

[1] input("Enter your name: ") vs input("What is one plus one? ") will
probably return different values, but that's playing with humans
rather than depending on the value of the argument...

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


#66180

FromRoy Smith <roy@panix.com>
Date2014-02-13 09:58 -0500
Message-ID<roy-3130D7.09582513022014@news.panix.com>
In reply to#66152
In article <mailman.6824.1392265865.18130.python-list@python.org>,
 Chris Angelico <rosuav@gmail.com> wrote:

> Whether it's a module-level function, a bound method, a closure, or a
> callable object, a zero-arg function in Python always has some kind of
> implicit state.

Sometimes, it has a *lot* of implicit state:

os.fork()

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


#66210

FromChris Angelico <rosuav@gmail.com>
Date2014-02-14 06:17 +1100
Message-ID<mailman.6858.1392319086.18130.python-list@python.org>
In reply to#66180
On Fri, Feb 14, 2014 at 1:58 AM, Roy Smith <roy@panix.com> wrote:
> In article <mailman.6824.1392265865.18130.python-list@python.org>,
>  Chris Angelico <rosuav@gmail.com> wrote:
>
>> Whether it's a module-level function, a bound method, a closure, or a
>> callable object, a zero-arg function in Python always has some kind of
>> implicit state.
>
> Sometimes, it has a *lot* of implicit state:
>
> os.fork()

os.fork() always returns 0, so it *clearly* has no state whatsoever.
It also always returns the next available PID, which is only a small
amount of extra state (from the OS). Nothing else affects its return
value!

ChrisA

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


#66164

FromTim Chase <python.list@tim.thechases.com>
Date2014-02-13 05:39 -0600
Message-ID<mailman.6829.1392291557.18130.python-list@python.org>
In reply to#66149
On 2014-02-13 04:11, Steven D'Aprano wrote:
> give_me_an_even_number()
> => returns 42
> give_me_an_even_number()
> => returns 23
> 
> Hmmm. There's a bug in give_me_an_even_number(). How do I reproduce
> that bug? What arguments do I pass? Oh, the same no-arguments as
> for the working call.
> 
> Clearly, the function must have *hidden state*. Hidden state (e.g.
> a global variable) makes it hard to reason about the function call,
> since you don't know what the hidden state is. So that's also a bit
> smelly.

I'd even go so far as to claim that this is the primary reason a
zero-argument function is a code-smell.  Not because zero-argument
functions smell, but because hidden-state smells and zero-argument
functions imply hidden-state.  Date/time functions are a personal pet
peeve for just this reason, and require addressing the hidden-state
of the system clock regardless of parameter-count.  Thus instead of
something like

  class Person:
    def __init__(self, name, dob):
      self.name = name
      self.dob = dob
    def age(self):
      return datetime.date.today() - self.dob

I do

  def age(self, as_of=None):
    if as_of is None:
      as_of = datetime.date.today()
    return as_of = self.dob

allowing me to test the function with known dates.

> > There are code smells that are the opposite in fact, methods with
> > long parameter lists are generally seen as code smell (“passing a
> > paragraph”).
> 
> Absolutely! You'll get no disagreement from me there.

*coughtkintercough*

-tkc


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


#66167

FromTim Chase <python.list@tim.thechases.com>
Date2014-02-13 05:51 -0600
Message-ID<mailman.6832.1392292265.18130.python-list@python.org>
In reply to#66149
On 2014-02-13 05:39, Tim Chase wrote:
>   def age(self, as_of=None):
>     if as_of is None:
>       as_of = datetime.date.today()
>     return as_of = self.dob

and of course I mean

  return as_of - self.dob

which is what I get for typing in the dark and the "-" and "="
keys are adjacent. :-/

-tkc

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


#66260

FromIan Kelly <ian.g.kelly@gmail.com>
Date2014-02-13 15:00 -0700
Message-ID<mailman.6895.1392328815.18130.python-list@python.org>
In reply to#66149

[Multipart message — attachments visible in raw view] — view raw

On Feb 12, 2014 9:16 PM, "Steven D'Aprano" <steve@pearwood.info> wrote:
>
> On Tue, 11 Feb 2014 07:36:34 -0800, Travis Griggs wrote:
>
> > On Feb 10, 2014, at 10:30 PM, Steven D'Aprano <steve@pearwood.info>
> > wrote:
> >
> >
> >>>    1. Parenthesis should not be required for parameter- less
> >>>    functions.
> >>
> >> Of course they should. Firstly, parameter-less functions are a code-
> >> smell, and ought to be discouraged. Secondly, even if you have a good
> >> reason for using one -- for example, random.random -- then the
> >> difference between referring to the object and calling the object
> >> should be clear.
> >
> > Interesting. Can you clarify or provide some links to the
> > "parameter-less functions are a code-smell" bit?
>
>
> Functions map a value to another value. They can be one-to-one, or many-
> to-one. (Mathematically, they cannot be one-to-many or many-to-many,
> that's called a relation.) What about zero-to-one?
>
> If the function always returns the same result, e.g.:
>
> def spam():
>     return "spam spam spam"

That's still one-to-one. There is no such thing as a zero-to-one mapping.

Mathematical functions map a single value to a single value. To represent
multi-argument functions then, the single input takes on the value of an
ordered sequence. The input value of a 0-argument function then is the
empty sequence.

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


#65921

FromChris Angelico <rosuav@gmail.com>
Date2014-02-12 02:52 +1100
Message-ID<mailman.6678.1392133959.18130.python-list@python.org>
In reply to#65891
On Wed, Feb 12, 2014 at 2:36 AM, Travis Griggs <travisgriggs@gmail.com> wrote:
> OTOH, I’m not sure I’ve heard the parameters-less functions are a code one? Is it just loose functions that you’re referring to? As opposed to methods (which are just bound functions)? I could maybe accept that. But methods with fewer arguments, and even none, are a desirable thing. There are code smells that are the opposite in fact, methods with long parameter lists are generally seen as code smell (“passing a paragraph”).
>

'self' is, imo, a parameter. When you call a parameter-less method on
an object, it's usually an imperative with a direct object (or
sometimes a subject):

some_file.close() # "Close some_file"
some_list.shuffle() # "Shuffle some_list"
some_file.readline() # "Some_file, read in a line"

There are times when, for convenience, the object is implicit.

print("some text", file=some_file) # Print that text
print(file=some_file) # Print a blank line
print("some text") # Print that text to sys.stdout
print() # Print a blank line to sys.stdout

So in that situation, the no-args call does make sense. Of course,
this is a call to a function that does take args, but it's accepting
all the defaults and providing no additional content. It's quite
different to actually define a function that mandates exactly zero
arguments, and isn't making use of some form of implicit state (eg a
closure, or maybe a module-level function that manipulates
module-level state - random.random() would be an example of the
latter). Syntactically, Python can't tell the difference between
"print()" and "foo()" where foo can never take args.

I'd say that a function taking no args is code smell, unless it's
obviously taking its state from somewhere else (callbacks, for
instance - maybe you pass a bound method, or maybe a closure, but in
either case it has implicit state that's not described by function
args); but _calling_ with no args isn't as smelly. It's certainly less
common than using args, but there are plenty of times when a type is
called without args, for instance[1].

ChrisA

[1] Okay, that was a really abysmal pun.

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


Page 1 of 2  [1] 2  Next page →

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


csiph-web