Beginner question - How to effectively pass a large list

C

Carl Banks

Bengt said:
Well, according to the argument, we would be dealing with an
optimizing compiler, so presumably the compiler would see a name
DEFAULT_LIST and simply compile a call-time binding of param to
whatever DEFAULT_LIST was bound to, and not bother further. It could
notice that the DEFAULT_LIST binding was still undisturbed, and that
it was to an immutable tuple with no mutable elements, which ISTM is
effectively a constant, but that analysis would be irrelevant, since
the semantics would be copying pre-existing binding (which is pretty
optimized anyway).
The dict literal looks to me to be made up entirely of immutable
keys and values, so the value of that literal expression seems to me
to be a constant. If you had call time evaluation, you would be
evaluating that expression each time, and the result would be a
fresh mutable dict with that constant initial value each time. ISTM
that could be optimized as
param=private_dict_compile_time_created_from_literal.copy(). OTOH,
if you used a pre-computed binding like DEFAULT_LIST, and wrote

SHARED_DICT = {'1': 'A', '2': 'B', '3': 'C', '4': 'D'}
def func(param=SHARED_DICT):
pass


then at def-time the compiler would not see the literal, but rather
a name bound to a mutable dict instance. The call-time effect would
be to bind param to whatever SHARED_DICT happened to be bound to,
just like for DEFAULT_LIST. But the semantics, given analysis that
showed no change to the SHARED_DICT _binding_ before the func call,
would be to share a single mutable dict instance. This is unlike the
semantics of

def func(param={'1': 'A', '2': 'B', '3': 'C', '4': 'D'}):
pass

which implies a fresh mutable dict instance bound to param, with the
same initial value (thus "constant" in a shallow sense at least,
which in this case is fully constant).

Good analysis.

I'd worry a bit about the meaning of names used in initialization expressions
if their values are to be looked up at call time. E.g., do you really want

a = 2
def foo(x=a): print 'x =', x
...
...
a = 'eh?'
foo()

to print 'eh?' By the time you are past a lot of ...'s, ISTM the
code intent is not so clear. But you can make dynamic access to the
current a as a default explicit by

class Defer(object):
def __init__(self, lam): self.lam = lam

def foo(x=Defer(lambda:a)):
if isinstance(x, Defer): x=x.lam()
print 'x =', x

The semantics are different. I'd prefer to have the best of both
worlds and be able to do both, as now, though I might not object to
some nice syntactic sugar along the lines suggested by OP Stian
S?iland. E.g., short spelling for the above Defer effect:

def foo(x:a): print 'x =', x

All good points; doing something like this always seems to have
further repurcussions.
 
C

Carl Banks

Rainer said:
Carl said:
Now, do have any evidence that non-constant, default arguments (as
they are now) are USEFUL?

def draw_pixel(x, y, color, surface=screen):
screen[y][x] = color

Was that so hard? (Although this would still work if surface was
evaluated call time.)

A function that mutates its arguments should not be called with "fresh"
arguments, implicitly or explicitly. If the purpose of the function is to
modify its arguments, then doing so would throw away the effect of the
function. If the purpose of the function is not to modify its arguments,
then it shouldn't do so.


In the function:

def a(b=[]):
pass

b=c is either part of the function, or part of the definition. If
it's part of the definition, it gets evaulated once, and the function
gets the same object each time. If it's part of the function, it gets
evaluated every call, and b gets a new list every time. Either way is
consistent.

You ideas about what a function's purpose is are just not relevant to
whether the time of evaluation is consistent.
 
R

Rainer Deyke

Carl said:
You ideas about what a function's purpose is are just not relevant to
whether the time of evaluation is consistent.

I'm not talking about whether or not time of evaluation is consistent -
clearly either way is consistent so long as it is consistently used. I'm
saying that getting a fresh copy of default args is useless and dangerous
because it only helps with functions which shouldn't be written in the first
place.
 
C

Carl Banks

Rainer said:
I'm not talking about whether or not time of evaluation is consistent -
clearly either way is consistent so long as it is consistently used. I'm
saying that getting a fresh copy of default args is useless and dangerous
because it only helps with functions which shouldn't be written in the first
place.


Well, ok. I don't agree that it's dangerous, and there are certainly
useful functions that modify their arguments that could benefit from a
fresh copy.

def top_secret_code(a,b,c,stub=[]):
stub.extend([f(a,b),g(b,c),g(c,a)])
return stub
 
R

Rainer Deyke

Carl said:
Well, ok. I don't agree that it's dangerous, and there are certainly
useful functions that modify their arguments that could benefit from a
fresh copy.

def top_secret_code(a,b,c,stub=[]):
stub.extend([f(a,b),g(b,c),g(c,a)])
return stub

This is exactly the kind of function I would call inconsistent. Called with
three arguments, it creates and returns a new list. Called with four, it
modifies an existing list and returns a reference to it. I find this highly
counterintuitive. If I was a user of this function and I learned about the
three argument form first, I would expect the four argument form to leave
its fourth argument unmodified and return a new list.
 
T

Terry Reedy

message
particular dict literal, why not?
Well, the value of DEFAULT_LIST is not known a
compile time

To further illustrate the problem with the
wished-for change, consider:

DL = (1,2)
def f(x=DL): print x
[lots of code]
DL = (3,4)
f()

What should this print? Currently, (1,2). To
get, (3,4) instead, one can default x to None and
dynamically reset x in the body to the calltime
value of DL. If default expressions were
evaluated at runtime, the choice of fixing the
default at definition, regardless of subsequent
code, would, as near as I can think of at the
moment, be excluded.

Terry J. Reedy
 
T

Terry Reedy

in message
It avoids the need for ridiculous kludges to check whether there is a
real arg there or not, etc. I'd prefer that Python had used the CL
method in the first place since I find the Python method bizarre and
counterintuitive.

Now that I know you are wishing for the 'CL
method', which I have no knowledge or experience
of, I can better understand why our sense of
'ridiculous kludge', 'bizarre', and
'counterintuitive' are somewhat different.

Terry J. Reedy
 
T

Terry Reedy

Bengt Richter said:
That's useful, but IMO not the optimal syntax,
because cache here is really not
being used as a parameter. That's why I would
like a way to bind locals similarly
without being part of the calling signature. E.g.,

def _tento(n)(
# preset bindings evaluated here at def-time
cache={}
):
try:
return cache[n]
except KeyError:
answer = cache[n] = 10L ** n
return answer

I have also thought of using a ';' as in
def _tento(n; cache = {}):
[etc]

to indicate a def-time initialized non-parameter.

Terry J. Reedy
 
B

Bengt Richter

Bengt Richter said:
That's useful, but IMO not the optimal syntax,
because cache here is really not
being used as a parameter. That's why I would
like a way to bind locals similarly
without being part of the calling signature. E.g.,

def _tento(n)(
# preset bindings evaluated here at def-time
cache={}
):
try:
return cache[n]
except KeyError:
answer = cache[n] = 10L ** n
return answer

I have also thought of using a ';' as in
def _tento(n; cache = {}):
[etc]

to indicate a def-time initialized non-parameter.
That's nice and concise. I wonder how best to use that for generators. I.e.,
the current parameter list of a generator is effectively gen(; current_stuff): ...
where the first part could be interpreted as optional parameters of .next().

Regards,
Bengt Richter
 
G

Georgy Pruss

|
| | > That's useful, but IMO not the optimal syntax,
| because cache here is really not
| > being used as a parameter. That's why I would
| like a way to bind locals similarly
| > without being part of the calling signature.
| E.g.,
| >
| > def _tento(n)(
| > # preset bindings evaluated here at def-time
| > cache={}
| > ):
| > try:
| > return cache[n]
| > except KeyError:
| > answer = cache[n] = 10L ** n
| > return answer
|
| I have also thought of using a ';' as in
| def _tento(n; cache = {}):
| [etc]
|
| to indicate a def-time initialized non-parameter.
|
| Terry J. Reedy

It's getting more and more cryptic. Why not clearly say 'const' for an
object that's not going to change and 'static' for an object that
is ...uhm.. static?
 
A

Asun Friere

Donn Cave said:
Quoth Jp Calderone <[email protected]>:
|The maybe-mutate-maybe-rebind semantics of += lead me to avoid its use
| in most circumstances.

This tacky feature certainly ought to be considered for the
chopping block in version 3, for exactly that reason.

And go back to 'someVariableName = someVariableName + 1' to do
the simple increment? The various 'assignment' operators are
popular with good reason imho.

Perhaps what should be considered is making sure that operators which
appear to be assignment operators actually (and consistently) behave as
such.
 

Ask a Question

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

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

Ask a Question

Members online

Forum statistics

Threads
473,770
Messages
2,569,585
Members
45,080
Latest member
mikkipirss

Latest Threads

Top