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


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

Deep vs. shallow copy?

Started byAlex van der Spek <zdoor@xs4all.nl>
First post2014-03-12 14:25 +0000
Last post2014-03-13 23:31 +0000
Articles 20 on this page of 22 — 13 participants

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


Contents

  Deep vs. shallow copy? Alex van der Spek <zdoor@xs4all.nl> - 2014-03-12 14:25 +0000
    Re: Deep vs. shallow copy? Skip Montanaro <skip@pobox.com> - 2014-03-12 09:48 -0500
    Re: Deep vs. shallow copy? Zachary Ware <zachary.ware+pylist@gmail.com> - 2014-03-12 10:00 -0500
      Re: Deep vs. shallow copy? Alex van der Spek <zdoor@xs4all.nl> - 2014-03-12 15:29 +0000
        Re: Deep vs. shallow copy? Wayne Brehaut <wbrehaut@mcsnet.ca> - 2014-03-12 13:06 -0600
        Re: Deep vs. shallow copy? Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2014-03-12 23:07 +0000
          Re: Deep vs. shallow copy? Rustom Mody <rustompmody@gmail.com> - 2014-03-12 20:09 -0700
            Re: Deep vs. shallow copy? Ian <hobson42@gmail.com> - 2014-03-13 11:38 +0000
              Re: Deep vs. shallow copy? Rustom Mody <rustompmody@gmail.com> - 2014-03-13 08:28 -0700
                Re: Deep vs. shallow copy? random832@fastmail.us - 2014-03-13 13:25 -0400
          Re: Deep vs. shallow copy? Roy Smith <roy@panix.com> - 2014-03-13 07:44 -0400
            Re: Deep vs. shallow copy? Marko Rauhamaa <marko@pacujo.net> - 2014-03-13 14:27 +0200
              Re: Deep vs. shallow copy? Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2014-03-13 23:41 +0000
                Re: Deep vs. shallow copy? Chris Angelico <rosuav@gmail.com> - 2014-03-14 10:55 +1100
                  Re: Deep vs. shallow copy? Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2014-03-14 01:41 +0000
                Re: Deep vs. shallow copy? Ian Kelly <ian.g.kelly@gmail.com> - 2014-03-13 18:08 -0600
                Re: Deep vs. shallow copy? Chris Angelico <rosuav@gmail.com> - 2014-03-14 11:22 +1100
                  Re: Deep vs. shallow copy? Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2014-03-14 01:59 +0000
                Re: Deep vs. shallow copy? Rustom Mody <rustompmody@gmail.com> - 2014-03-13 19:57 -0700
                  Re: Deep vs. shallow copy? Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2014-03-14 04:43 +0000
                Re: Deep vs. shallow copy? Mark Lawrence <breamoreboy@yahoo.co.uk> - 2014-03-14 06:17 +0000
            Re: Deep vs. shallow copy? Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2014-03-13 23:31 +0000

Page 1 of 2  [1] 2  Next page →


#68283 — Deep vs. shallow copy?

FromAlex van der Spek <zdoor@xs4all.nl>
Date2014-03-12 14:25 +0000
SubjectDeep vs. shallow copy?
Message-ID<53206e6a$0$2886$e4fe514c@news2.news.xs4all.nl>
I think I understand the difference between deep vs. shallow copies but 
I was bitten by this:

with open(os.path.join('path', 'foo.txt', 'rb') as txt:
     reader = csv.reader(txt)
     data = [row.append(year) for row in reader]

This does not work although the append does complete. The below works:

with open(os.path.join('path', 'foo.txt', 'rb') as txt:
     reader = csv.reader(txt)
     data = [row + [year] for row in reader]

However in this context I am baffled. If someone can explain what is 
going on here, I would be most grateful.

Alex van der Spek

[toc] | [next] | [standalone]


#68284

FromSkip Montanaro <skip@pobox.com>
Date2014-03-12 09:48 -0500
Message-ID<mailman.8092.1394635720.18130.python-list@python.org>
In reply to#68283
On Wed, Mar 12, 2014 at 9:25 AM, Alex van der Spek <zdoor@xs4all.nl> wrote:
> with open(os.path.join('path', 'foo.txt', 'rb') as txt:
>      reader = csv.reader(txt)
>      data = [row.append(year) for row in reader]

Forget deep v. shallow copies. What is the value of the variable year?
And why would you expect list.append to return anything?

Skip

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


#68285

FromZachary Ware <zachary.ware+pylist@gmail.com>
Date2014-03-12 10:00 -0500
Message-ID<mailman.8093.1394636439.18130.python-list@python.org>
In reply to#68283
On Wed, Mar 12, 2014 at 9:25 AM, Alex van der Spek <zdoor@xs4all.nl> wrote:
> I think I understand the difference between deep vs. shallow copies but
> I was bitten by this:
>
> with open(os.path.join('path', 'foo.txt', 'rb') as txt:
>      reader = csv.reader(txt)
>      data = [row.append(year) for row in reader]
>
> This does not work although the append does complete. The below works:
>
> with open(os.path.join('path', 'foo.txt', 'rb') as txt:
>      reader = csv.reader(txt)
>      data = [row + [year] for row in reader]
>
> However in this context I am baffled. If someone can explain what is
> going on here, I would be most grateful.

Deep/shallow copying doesn't really come into this.  row.append()
mutates the list (row), it doesn't return a new list.  Like most
in-place/mutating methods in Python, it returns None instead of self
to show that mutation was done, so your listcomp fills `data` with
Nones; there is no copying done at all.  The second example works as
you expected because `row + [year]` results in a new list, which the
listcomp is happy to append to `data`--which does mean that `row` is
copied.

To avoid the copy that the second listcomp is doing (which really
shouldn't be necessary anyway, unless your rows are astronomically
huge), you have a couple of options.  First, you can expand your
listcomp and use append:

   with open(os.path.join('path', 'foo.txt'), 'rb') as txt: # with
your typo fixed ;)
       reader = csv.reader(txt)
       data = []
       for row in reader:
           row.append(year)
           data.append(row)

To me, that's pretty readable and pretty clear about what it's doing.
Then there's this option, which I don't recommend:

   import operator
   with open(os.path.join('path', 'foo.txt'), 'rb') as txt:
       reader = csv.reader(txt)
       data = [operator.iadd(row, [year]) for row in reader]

This works because operator.iadd is basically shorthand for
row.__iadd__([year]), which does return self (otherwise, the
assignment part of `row += [year]` couldn't work).  But, it's not as
clear about what's happening, and only saves a whole two lines (maybe
3 if you already have operator imported).

Hope this helps,
-- 
Zach

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


#68286

FromAlex van der Spek <zdoor@xs4all.nl>
Date2014-03-12 15:29 +0000
Message-ID<53207d77$0$2886$e4fe514c@news2.news.xs4all.nl>
In reply to#68285
On Wed, 12 Mar 2014 10:00:09 -0500, Zachary Ware wrote:

> On Wed, Mar 12, 2014 at 9:25 AM, Alex van der Spek <zdoor@xs4all.nl>
> wrote:
>> I think I understand the difference between deep vs. shallow copies but
>> I was bitten by this:
>>
>> with open(os.path.join('path', 'foo.txt', 'rb') as txt:
>>      reader = csv.reader(txt)
>>      data = [row.append(year) for row in reader]
>>
>> This does not work although the append does complete. The below works:
>>
>> with open(os.path.join('path', 'foo.txt', 'rb') as txt:
>>      reader = csv.reader(txt)
>>      data = [row + [year] for row in reader]
>>
>> However in this context I am baffled. If someone can explain what is
>> going on here, I would be most grateful.
> 
> Deep/shallow copying doesn't really come into this.  row.append()
> mutates the list (row), it doesn't return a new list.  Like most
> in-place/mutating methods in Python, it returns None instead of self to
> show that mutation was done, so your listcomp fills `data` with Nones;
> there is no copying done at all.  The second example works as you
> expected because `row + [year]` results in a new list, which the
> listcomp is happy to append to `data`--which does mean that `row` is
> copied.
> 
> To avoid the copy that the second listcomp is doing (which really
> shouldn't be necessary anyway, unless your rows are astronomically
> huge), you have a couple of options.  First, you can expand your
> listcomp and use append:
> 
>    with open(os.path.join('path', 'foo.txt'), 'rb') as txt: # with
> your typo fixed ;)
>        reader = csv.reader(txt)
>        data = []
>        for row in reader:
>            row.append(year)
>            data.append(row)
> 
> To me, that's pretty readable and pretty clear about what it's doing.
> Then there's this option, which I don't recommend:
> 
>    import operator
>    with open(os.path.join('path', 'foo.txt'), 'rb') as txt:
>        reader = csv.reader(txt)
>        data = [operator.iadd(row, [year]) for row in reader]
> 
> This works because operator.iadd is basically shorthand for
> row.__iadd__([year]), which does return self (otherwise, the assignment
> part of `row += [year]` couldn't work).  But, it's not as clear about
> what's happening, and only saves a whole two lines (maybe 3 if you
> already have operator imported).
> 
> Hope this helps,

Thank you, that helped immensely! 

Having been taught programming in Algol60 Python still defeats me at times!
Particularly since Algol60 wasn't very long lived and what came
thereafter (FORTRAN) much worse.

I get it now, the below is equivalent! 

I am perfectly happy with the one copy of the list row + [year]. 
Just wanted to learn something here and I have!


Python 2.6.5 (r265:79063, Feb 27 2014, 19:44:14) 
[GCC 4.4.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> a = [1,2,3]
>>> b = 'val'
>>> a.append(b)
>>> a
[1, 2, 3, 'val']
>>> c = a.append(b)
>>> print c
None
>>> 

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


#68291

FromWayne Brehaut <wbrehaut@mcsnet.ca>
Date2014-03-12 13:06 -0600
Message-ID<akb1i994eequ1amumqv6ilbrm82vpiacr5@4ax.com>
In reply to#68286
On 12 Mar 2014 15:29:59 GMT, Alex van der Spek <zdoor@xs4all.nl>
wrote:

>On Wed, 12 Mar 2014 10:00:09 -0500, Zachary Ware wrote:
>
>> On Wed, Mar 12, 2014 at 9:25 AM, Alex van der Spek <zdoor@xs4all.nl>
>> wrote:

=== 8< ===

>Having been taught programming in Algol60 Python still defeats me at times!
>Particularly since Algol60 wasn't very long lived and what came
>thereafter (FORTRAN) much worse.

Actually, Algol 60 lived on (and lives on, though not so much used now
outside of Scandinavia) in an improved and OOP-extended form in Simula
67 (now just Simula). Most implementations excpt that for DEC-System10
were, however, overpriced and poorly marketed, so we had to wait for
C++ (improved and OOP-extended C) for OOP to catch on.

=== 8< ===

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


#68308

FromSteven D'Aprano <steve+comp.lang.python@pearwood.info>
Date2014-03-12 23:07 +0000
Message-ID<5320e8c9$0$29994$c3e8da3$5496439d@news.astraweb.com>
In reply to#68286
On Wed, 12 Mar 2014 15:29:59 +0000, Alex van der Spek wrote:

> Having been taught programming in Algol60 Python still defeats me at
> times! Particularly since Algol60 wasn't very long lived and what came
> thereafter (FORTRAN) much worse.

Fortran came first. Fortran was the first high-level language which 
allowed the programmer to write things that looked rather like the sorts 
of mathematical expressions they were used to.

There were a few higher-level assembly languages that came before 
Fortran, such as SpeedCoding, Fortran's predecessor, but Fortran was the 
first truly high-level programming language, and even in 1957 it came 
with an optimizing compiler.

I'm not really familiar with Algol, but I do know Pascal, and you should 
think of the append method to be like a Pascal procedure. Because Python 
doesn't have true procedures, it follows the convention that returning 
the special object None signals the intention to return nothing at all. 
Hence your example below:


>>>> c = a.append(b)
>>>> print c
> None





-- 
Steven D'Aprano
http://import-that.dreamwidth.org/

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


#68323

FromRustom Mody <rustompmody@gmail.com>
Date2014-03-12 20:09 -0700
Message-ID<0852919b-e5ac-42cc-b30f-f465a144e24e@googlegroups.com>
In reply to#68308
On Thursday, March 13, 2014 4:37:53 AM UTC+5:30, Steven D'Aprano wrote:
> On Wed, 12 Mar 2014 15:29:59 +0000, Alex van der Spek wrote:

> > Having been taught programming in Algol60 Python still defeats me at
> > times! Particularly since Algol60 wasn't very long lived and what came
> > thereafter (FORTRAN) much worse.


> I'm not really familiar with Algol, but I do know Pascal, and you should 
> think of the append method to be like a Pascal procedure. Because Python 
> doesn't have true procedures, it follows the convention that returning 
> the special object None signals the intention to return nothing at all. 
> Hence your example below:

Yes... Algol I dont know. But to the extent that it corresponds to
Pascal the following may help.

Pascal is remarkably clean and cleaner than what came before -- Lisp
-- and after -- C, all its derivatives, python... almost everything.

First off there are expressions and statements, and no messing with
the distinction.  C started laissez-faire. No void, just default
functions intended to be used as procedures to int.  This was
problematic to enough people that void was introduced soon enough.

But its still messy:
An assignment like x = 3 is both an expression and (like) a statement.
It is mostly used as a statement by appending a semicolon.
It can also be used as an expression as in y = x = 3.

Try explaining this to a first year class and you'd know what a mess it is.
For the most part if you think youve got it you probably havent.

Python is in some respects worse than C, in some better.

Its better in that assignment statements are not re-purposeable as
expressions. Its worse in that procedures are simulated by None
returning functions -- this means syntax errors which C would give
when using a void function as an expression, you wont get in python.

On the whole, whether your language supports it or not, its best to
think of the world as separated into actions and values.

Call the action-world the 'imperative' world.
Call the value-world the 'functional' world ('declarative' would be
better but 'functional' is too entrenched).

[Following table meant to be read with fixed (courier) font]

|                         | Imperative | Functional          |
| Language entity         | Statement  | Expression          |
| Denote (and think with) | Action     | Value               |
| Abstracted into         | Procedure  | Function            |
| Atoms are               | Assignment | Constant/Variable   |
| Data Structures         | Mutable    | Immutable           |
| Loop primitive          | Recursion  | Iteration           |
| World is                | In time    | Timeless (Platonic) |

Generally mixing these two worlds is necessary for any non-trivial
programming but is commonly problematic.

Your error was to use a functional form -- list comprehension -- and
embed an imperative construct -- row.append -- into that.  This is
always a gaffe

Legal mixing is done thus
Expression -> Statement by putting the expression on the rhs of an assignment
Statement -> Expression by putting the statement(s) into a function

While most programmers think this unproblematic, Backus' widely-cited
Turing Award lecture is devoted to showing why this is a problem
http://www.thocp.net/biographies/papers/backus_turingaward_lecture.pdf

For an old Algol/Fortran programmer it may be a worthwhile read considering
Backus invented Fortran :-)

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


#68326

FromIan <hobson42@gmail.com>
Date2014-03-13 11:38 +0000
Message-ID<mailman.8125.1394710730.18130.python-list@python.org>
In reply to#68323
On 13/03/2014 03:09, Rustom Mody wrote:
> Call the action-world the 'imperative' world.
> Call the value-world the 'functional' world ('declarative' would be
> better but 'functional' is too entrenched).
>
> [Following table meant to be read with fixed (courier) font]
>
> |                         | Imperative | Functional          |
> | Language entity         | Statement  | Expression          |
> | Denote (and think with) | Action     | Value               |
> | Abstracted into         | Procedure  | Function            |
> | Atoms are               | Assignment | Constant/Variable   |
> | Data Structures         | Mutable    | Immutable           |
> | Loop primitive          | Recursion  | Iteration           |
> | World is                | In time    | Timeless (Platonic) |
Small typo I think in that the looping Primitives are switched about?

Regards

Ian

-- 
Ian Hobson
29 Manorfield Close, Northampton NN3 9SL,
Tel: 01604 513875
Preparing eBooks for Kindle and ePub formats to give the best reader experience.

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


#68334

FromRustom Mody <rustompmody@gmail.com>
Date2014-03-13 08:28 -0700
Message-ID<04d2454f-4a22-482c-9d41-f2e1d2032675@googlegroups.com>
In reply to#68326
On Thursday, March 13, 2014 5:08:22 PM UTC+5:30, Ian wrote:
> On 13/03/2014 03:09, Rustom Mody wrote:
> > Call the action-world the 'imperative' world.
> > Call the value-world the 'functional' world ('declarative' would be
> > better but 'functional' is too entrenched).
> > [Following table meant to be read with fixed (courier) font]
> > |                         | Imperative | Functional          |
> > | Language entity         | Statement  | Expression          |
> > | Denote (and think with) | Action     | Value               |
> > | Abstracted into         | Procedure  | Function            |
> > | Atoms are               | Assignment | Constant/Variable   |
> > | Data Structures         | Mutable    | Immutable           |
> > | Loop primitive          | Recursion  | Iteration           |
> > | World is                | In time    | Timeless (Platonic) |
> Small typo I think in that the looping Primitives are switched about?

Heh! I was hesitating to put that line at all: For one thing its a
hackneyed truth in the non-FP community. For another, in practical
Haskell, use of frank recursion is regarded as as sign of programming
immaturity:

http://www.willamette.edu/~fruehr/haskell/evolution.html

So I guess I ended up typing it in the wrong order!

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


#68337

Fromrandom832@fastmail.us
Date2014-03-13 13:25 -0400
Message-ID<mailman.8131.1394731512.18130.python-list@python.org>
In reply to#68334
On Thu, Mar 13, 2014, at 11:28, Rustom Mody wrote:
> Heh! I was hesitating to put that line at all: For one thing its a
> hackneyed truth in the non-FP community. For another, in practical
> Haskell, use of frank recursion is regarded as as sign of programming
> immaturity:

And IIRC Lisp and Scheme have loop macros (whose semantics are more like
a goal seek than iteration IIRC).

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


#68327

FromRoy Smith <roy@panix.com>
Date2014-03-13 07:44 -0400
Message-ID<roy-493F5F.07442713032014@news.panix.com>
In reply to#68308
In article <5320e8c9$0$29994$c3e8da3$5496439d@news.astraweb.com>,
 Steven D'Aprano <steve+comp.lang.python@pearwood.info> wrote:

> Because Python doesn't have true procedures

What do you mean by "true procedure"?  Are you just talking about 
subroutines that don't return any value, i.e. fortran's SUBROUTINE vs. 
FUNCTION?

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


#68328

FromMarko Rauhamaa <marko@pacujo.net>
Date2014-03-13 14:27 +0200
Message-ID<87y50el1vf.fsf@elektro.pacujo.net>
In reply to#68327
Roy Smith <roy@panix.com>:

>  Steven D'Aprano <steve+comp.lang.python@pearwood.info> wrote:
>
>> Because Python doesn't have true procedures
>
> What do you mean by "true procedure"? Are you just talking about
> subroutines that don't return any value, i.e. fortran's SUBROUTINE vs.
> FUNCTION?

Ah, the "no true procedure" argument:

 - No true procedure returns a value.

 - That's false. Python's procedures return None.

 - They are not true procedures.


Marko

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


#68342

FromSteven D'Aprano <steve+comp.lang.python@pearwood.info>
Date2014-03-13 23:41 +0000
Message-ID<5322420e$0$29994$c3e8da3$5496439d@news.astraweb.com>
In reply to#68328
On Thu, 13 Mar 2014 14:27:48 +0200, Marko Rauhamaa wrote:

> Roy Smith <roy@panix.com>:
> 
>>  Steven D'Aprano <steve+comp.lang.python@pearwood.info> wrote:
>>
>>> Because Python doesn't have true procedures
>>
>> What do you mean by "true procedure"? Are you just talking about
>> subroutines that don't return any value, i.e. fortran's SUBROUTINE vs.
>> FUNCTION?
> 
> Ah, the "no true procedure" argument:
> 
>  - No true procedure returns a value.
> 
>  - That's false. Python's procedures return None.

Are you trolling again? 

I'm sure that you know quite well that Python doesn't have a procedure 
type. It uses a single keyword, def, for creating both functions and 
functions-that-return-None.

We should all agree that functions-that-return-None are used for the same 
purpose as procedures, but they are still functions, and they have a 
return result, namely None. If you don't believe me, believe Python:

py> def func():
...     return 42
...
py> def proc():
...     pass
...
py> type(func)
<class 'function'>
py> type(proc)
<class 'function'>
py> repr(proc())
'None'

In languages with procedures, that last line would be an error (either at 
compile-time, or run-time) since a procedure wouldn't return anything to 
use as argument to repr. But I'm sure that you know that.


>  - They are not true procedures.

Correct. They are functions that return None, rather than a subroutine 
that doesn't have any return value at all. But I'm sure you know that.





-- 
Steven D'Aprano
http://import-that.dreamwidth.org/

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


#68343

FromChris Angelico <rosuav@gmail.com>
Date2014-03-14 10:55 +1100
Message-ID<mailman.8135.1394754947.18130.python-list@python.org>
In reply to#68342
On Fri, Mar 14, 2014 at 10:41 AM, Steven D'Aprano
<steve+comp.lang.python@pearwood.info> wrote:
> Are you trolling again?
>
> I'm sure that you know quite well that Python doesn't have a procedure
> type. It uses a single keyword, def, for creating both functions and
> functions-that-return-None.

I'm going to troll for a moment and give you a function that has no
return value.

def procedure():
    raise Exception

But seriously, this is something that some functions do when they need
to distinguish between returning something and not returning anything.
Look at a dictionary's subscripting (which is effectively a function
call):

>>> x={1:2}
>>> x[1]
2
>>> x[3]
Traceback (most recent call last):
  File "<pyshell#17>", line 1, in <module>
    x[3]
KeyError: 3

It can't return None to indicate "there was no such key in the
dictionary", so it raises instead. There's only one way for a Python
function to not have a return value: it has to not return.

ChrisA

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


#68351

FromSteven D'Aprano <steve+comp.lang.python@pearwood.info>
Date2014-03-14 01:41 +0000
Message-ID<53225e65$0$29994$c3e8da3$5496439d@news.astraweb.com>
In reply to#68343
On Fri, 14 Mar 2014 10:55:44 +1100, Chris Angelico wrote:

> On Fri, Mar 14, 2014 at 10:41 AM, Steven D'Aprano
> <steve+comp.lang.python@pearwood.info> wrote:
>> Are you trolling again?
>>
>> I'm sure that you know quite well that Python doesn't have a procedure
>> type. It uses a single keyword, def, for creating both functions and
>> functions-that-return-None.
> 
> I'm going to troll for a moment and give you a function that has no
> return value.

Heh, you're not trolling. You're just trying to be pedantic. But not 
pedantic enough... 


> def procedure():
>     raise Exception

This does have a return result, and it is None. It's just that the 
function never reaches the return, it exits early via an exception.

py> from dis import dis
py> dis(procedure)
  2           0 LOAD_GLOBAL              0 (Exception)
              3 RAISE_VARARGS            1
              6 LOAD_CONST               0 (None)
              9 RETURN_VALUE

That *may* be able to be optimized away by a smarter compiler, or perhaps 
it can't be. There may be some technical reason why code objects have to 
end with a return no matter what:

py> dis(compile("func()", "", "exec"))
  1           0 LOAD_NAME                0 (func)
              3 CALL_FUNCTION            0 (0 positional, 0 keyword pair)
              6 POP_TOP
              7 LOAD_CONST               0 (None)
             10 RETURN_VALUE

Or maybe it's just an optimization that nobody has bothered with since 
the benefit is so trivial. But either way, all callables (sub-routines) 
in Python are functions, i.e. in principle they could, or should, return 
a result, even if in practice some of them don't. There is no callable 
type which lacks the ability to return a result.

Naturally they may not actually return a result if they never exit:

def this_is_a_function():
    while 1:
        pass
    return "You'll never see this!"

or if they exit via an exception:

def also_a_function():
    if 1:
        raise ValueError
    return "You'll never see this either!"

or if they just kill the running Python environment stone dead:

def still_a_function():
    import os
    os._exit(1)
    return "Have I beaten this dead horse enough?"


> But seriously, this is something that some functions do when they need
> to distinguish between returning something and not returning anything.
> Look at a dictionary's subscripting (which is effectively a function
> call):
> 
>>>> x={1:2}
>>>> x[1]
> 2
>>>> x[3]
> Traceback (most recent call last):
>   File "<pyshell#17>", line 1, in <module>
>     x[3]
> KeyError: 3

Yes, I'm aware that Python functions can raise exceptions :-)

 
> It can't return None to indicate "there was no such key in the
> dictionary", so it raises instead. There's only one way for a Python
> function to not have a return value: it has to not return.

Exactly my point. Functions return a value, and None is a value; 
procedures return, but not with a value. Python has the former, but not 
the later. Instead, we make do with the convention that something which 
is *intended* to be used as a procedure should return None to signal 
that, and the caller should just ignore the return result.




-- 
Steven D'Aprano
http://import-that.dreamwidth.org/

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


#68345

FromIan Kelly <ian.g.kelly@gmail.com>
Date2014-03-13 18:08 -0600
Message-ID<mailman.8136.1394755770.18130.python-list@python.org>
In reply to#68342
On Thu, Mar 13, 2014 at 5:55 PM, Chris Angelico <rosuav@gmail.com> wrote:
> I'm going to troll for a moment and give you a function that has no
> return value.
>
> def procedure():
>     raise Exception

>>> import dis
>>> dis.dis(procedure)
  2           0 LOAD_GLOBAL              0 (Exception)
              3 RAISE_VARARGS            1
              6 LOAD_CONST               0 (None)
              9 RETURN_VALUE
>>> def get_procedure_return_value():
...     """Returns the return value of procedure()."""
...     return procedure.__code__.co_consts[0]
...
>>> print(get_procedure_return_value())
None

Look, there it is!

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


#68346

FromChris Angelico <rosuav@gmail.com>
Date2014-03-14 11:22 +1100
Message-ID<mailman.8137.1394756567.18130.python-list@python.org>
In reply to#68342
On Fri, Mar 14, 2014 at 11:08 AM, Ian Kelly <ian.g.kelly@gmail.com> wrote:
> On Thu, Mar 13, 2014 at 5:55 PM, Chris Angelico <rosuav@gmail.com> wrote:
>> I'm going to troll for a moment and give you a function that has no
>> return value.
>>
>> def procedure():
>>     raise Exception
>
>>>> import dis
>>>> dis.dis(procedure)
>   2           0 LOAD_GLOBAL              0 (Exception)
>               3 RAISE_VARARGS            1
>               6 LOAD_CONST               0 (None)
>               9 RETURN_VALUE

That's a return value in the same way that exec() has a return value
[1]. If somehow the raise fails, it'll return None.

>>>> def get_procedure_return_value():
> ...     """Returns the return value of procedure()."""
> ...     return procedure.__code__.co_consts[0]
> ...
>>>> print(get_procedure_return_value())
> None
>
> Look, there it is!

Succeeds by coincidence. From what I can see, *every* CPython function
has const slot 0 dedicated to None. At least, I haven't been able to
do otherwise.

>>> def function(x):
    return x*2+1

>>> import dis
>>> dis.dis(function)
  2           0 LOAD_FAST                0 (x)
              3 LOAD_CONST               1 (2)
              6 BINARY_MULTIPLY
              7 LOAD_CONST               2 (1)
             10 BINARY_ADD
             11 RETURN_VALUE
>>> function.__code__.co_consts
(None, 2, 1)

Your return value retriever would say it returns None still, but it doesn't.

Trollbridge: you have to pay a troll to cross.

ChrisA

[1] I'm not talking about Python's 'exec' statement, but about the
Unix exec() API, eg execlpe() - see http://linux.die.net/man/3/exec

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


#68352

FromSteven D'Aprano <steve+comp.lang.python@pearwood.info>
Date2014-03-14 01:59 +0000
Message-ID<53226269$0$29994$c3e8da3$5496439d@news.astraweb.com>
In reply to#68346
On Fri, 14 Mar 2014 11:22:43 +1100, Chris Angelico wrote:

> Trollbridge: you have to pay a troll to cross.

Heh :-)

But seriously, there is a distinction to be made between returning from a 
sub-routine, and returning from a sub-routine with a return result. There 
may be alternative methods of exiting the sub-routine, e.g. a GOTO or 
COMEFROM that jumps outside of the function. Exceptions are a form of 
safe, limited GOTO: they can only jump out of a function, not into the 
middle of an arbitrary chunk of code, and they clean up the call stack 
when they jump. But these alternative methods are not what people 
consider *returning* from a sub-routine.

Unless they're trolling :-)




-- 
Steven D'Aprano
http://import-that.dreamwidth.org/

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


#68353

FromRustom Mody <rustompmody@gmail.com>
Date2014-03-13 19:57 -0700
Message-ID<738224f9-7e11-49c2-869e-381b3ca3103f@googlegroups.com>
In reply to#68342
On Friday, March 14, 2014 5:11:03 AM UTC+5:30, Steven D'Aprano wrote:
> On Thu, 13 Mar 2014 14:27:48 +0200, Marko Rauhamaa wrote:

> > Roy Smith :
> >>  Steven D'Aprano  wrote:
> >>> Because Python doesn't have true procedures
> >> What do you mean by "true procedure"? Are you just talking about
> >> subroutines that don't return any value, i.e. fortran's SUBROUTINE vs.
> >> FUNCTION?
> > Ah, the "no true procedure" argument:
> >  - No true procedure returns a value.
> >  - That's false. Python's procedures return None.

> Are you trolling again? 

> I'm sure that you know quite well that Python doesn't have a procedure 
> type. It uses a single keyword, def, for creating both functions and 
> functions-that-return-None.

> We should all agree that functions-that-return-None are used for the same 
> purpose as procedures, but they are still functions, and they have a 
> return result, namely None. If you don't believe me, believe Python:

> py> def func():
> ...     return 42
> ...
> py> def proc():
> ...     pass
> ...
> py> type(func)
> py> type(proc)
> py> repr(proc())
> 'None'

> In languages with procedures, that last line would be an error (either at 
> compile-time, or run-time) since a procedure wouldn't return anything to 
> use as argument to repr. But I'm sure that you know that.

> >  - They are not true procedures.

> Correct. They are functions that return None, rather than a subroutine 
> that doesn't have any return value at all. But I'm sure you know that.

I believe that you, Marko (and I) are saying exactly the same thing:

Wear language-lawyer hat: 
Python has no procedures -- just functions which may return None

Wear vanilla programmer hat:
The concept (Pascal) procedure is simulated by function-returning-None

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


#68355

FromSteven D'Aprano <steve+comp.lang.python@pearwood.info>
Date2014-03-14 04:43 +0000
Message-ID<53228908$0$29994$c3e8da3$5496439d@news.astraweb.com>
In reply to#68353
On Thu, 13 Mar 2014 19:57:53 -0700, Rustom Mody wrote:

> I believe that you, Marko (and I) are saying exactly the same thing:

I believe that you and I are saying practically the same thing.


> Wear language-lawyer hat:
> Python has no procedures -- just functions which may return None

Almost. Functions (or methods) can return None as a regular value, e.g. 
the re.match and re.search functions return a MatchObject if there is a 
match, and None if there is not. Here, the fact the function returns None 
is nothing special -- it could have return 0, or -1, or "Surprise!" if 
the function author had wanted, the important thing is that you use it as 
a function. Normally you call the function for it's return result, even 
if that result happens to be None.

On the other hand, Python also has functions/methods which you call for 
their side-effects, not for their return result. In Pascal, Fortran and 
C, for example, the language provides a special type of subroutine that 
can only be called for it's side-effects. It is common to call these 
subroutines "procedures", and Python does not have them. We only have the 
convention that if a function is intended to be called for it's side-
effects (a procedure), it should return None.


> Wear vanilla programmer hat:
> The concept (Pascal) procedure is simulated by function-returning-None

Yes, agreed on this one.



-- 
Steven

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


Page 1 of 2  [1] 2  Next page →

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


csiph-web