string formatting with mapping & '*'... is this a bug?

P

Pierre Fortin

Hi!

"Python Essential Reference" - 2nd Ed, on P. 47 states that a string
format can include "*" for a field width (no restrictions noted); yet...
' 2'

Now, with a mapping....

These work as expected...
' 2'

Attempting to specify a field width via '*' to a mapping fails....
Traceback (most recent call last):
Traceback (most recent call last):
File "<stdin>", line 1, in ?
TypeError: format requires a mapping

Am I expecting too much...?

Pierre
 
O

OKB (not okblacke)

Pierre said:
Attempting to specify a field width via '*' to a mapping fails....

Traceback (most recent call last):

Traceback (most recent call last):
File "<stdin>", line 1, in ?
TypeError: format requires a mapping

I don't think this has anything to do with the * thing. You can't
mix the sequence and mapping forms of %. For instance, this also fails

Traceback (most recent call last):
File "<pyshell#8>", line 1, in -toplevel-
"%(two)d %d" % (m, 6)
TypeError: format requires a mapping

The error message is telling you the truth: you're passing the %
operator a tuple, but your format string has a "%(two)" in it, which
means it requires not a tuple but a mapping.

--
--OKB (not okblacke)
Brendan Barnwell
"Do not follow where the path may lead. Go, instead, where there is
no path, and leave a trail."
--author unknown
 
P

Pierre Fortin

I don't think this has anything to do with the * thing. You can't

mix the sequence and mapping forms of %. For instance, this also fails


Traceback (most recent call last):
File "<pyshell#8>", line 1, in -toplevel-
"%(two)d %d" % (m, 6)
TypeError: format requires a mapping

The error message is telling you the truth: you're passing the %
operator a tuple, but your format string has a "%(two)" in it, which
means it requires not a tuple but a mapping.

Darn... I was hoping that Python would let me format strings with
mappings in a more readable/maintainable way than thusly:
' 2'
-or-' 2'

Changing my copy of the book on P.48 to read:
"""
In addition, the asterisk (*) character may be used in place of any number
in any** width field. If present, the width will be read from the next
item in the tuple.

** EXCEPT with mappings described in (1) above
"""


Pierre
 
D

Diez B. Roggisch

Darn... I was hoping that Python would let me format strings with
mappings in a more readable/maintainable way than thusly:

Maybe you can simply combine the sequence and the map - if you don't have
numeric keys, that might work. Like this:

vals = ['first' , 'second']
map = {'foo' : 'bar'}

map2 = dict(map.items() + [(str(i), v) for i, v in enumerate(vals)])

print "%(0)s %(foo)s %(1)s" % map2

Not perfect, but IMHO better than your eval-based solution.
 
A

Alex Martelli

Diez B. Roggisch said:
map2 = dict(map.items() + [(str(i), v) for i, v in enumerate(vals)])

An equivalent ctor call that's slightly better, IMHO:

map2 = dict([ (str(i),v) for i,v in enumerate(vals) ], **map)

Don't ignore the power of dict's keyword arguments...
Not perfect, but IMHO better than your eval-based solution.

I agree, mine is just a tiny improvement on your post, IMHO.


Alex
 
P

Pierre Fortin

Diez B. Roggisch said:
map2 = dict(map.items() + [(str(i), v) for i, v in enumerate(vals)])

An equivalent ctor call that's slightly better, IMHO:

map2 = dict([ (str(i),v) for i,v in enumerate(vals) ], **map)

Don't ignore the power of dict's keyword arguments...
Not perfect, but IMHO better than your eval-based solution.

I agree, mine is just a tiny improvement on your post, IMHO.


Alex

Thanks for that; but it's not what I wanted... (see original post)

I was hoping to use the likes of: "%(key)*.*f" % map
however, unlike with the non-(key) formats, there appears to be no way to
specify a "*.*" size when a map is used...

Pierre
 
B

Bengt Richter

Diez B. Roggisch said:
map2 = dict(map.items() + [(str(i), v) for i, v in enumerate(vals)])

An equivalent ctor call that's slightly better, IMHO:

map2 = dict([ (str(i),v) for i,v in enumerate(vals) ], **map)

Don't ignore the power of dict's keyword arguments...
Not perfect, but IMHO better than your eval-based solution.

I agree, mine is just a tiny improvement on your post, IMHO.


Alex

Thanks for that; but it's not what I wanted... (see original post)

I was hoping to use the likes of: "%(key)*.*f" % map
however, unlike with the non-(key) formats, there appears to be no way to
specify a "*.*" size when a map is used...
Well, you could subclass str and override its __mod__ method ;-)
If you had class format(str): ..., you could then write
format('%(key)*.*f") % map
and have it do whatever you like.

Regards,
Bengt Richter
 
P

Pierre Fortin

Well, you could subclass str and override its __mod__ method ;-)
If you had class format(str): ..., you could then write
format('%(key)*.*f") % map
and have it do whatever you like.

This must be one of those days when my density goes up.... :^/

Can you fill in the blanks on this...? Mismatched quotes aside, I just
don't seem to grok how map's key gets in there since there is a ")"
between the format string and "%" -- then, I still don't see how "*.*"
gets set as it would in: "%*.*f" % (width,max,float)

I'll try to finally get a handle on subclassing/overriding with this....
:>

Pierre
 
B

Bengt Richter

This must be one of those days when my density goes up.... :^/

Can you fill in the blanks on this...? Mismatched quotes aside, I just
Oops on the quotes ;-/
don't seem to grok how map's key gets in there since there is a ")"
between the format string and "%" -- then, I still don't see how "*.*"
gets set as it would in: "%*.*f" % (width,max,float)

I'll try to finally get a handle on subclassing/overriding with this....
:>
I was making a quick comment that if you really wanted to you could
intercept all the arguments and interpret them as you wish. The line I wrote
would demand some conventions about what was in the map, or indeed it could
be a custom mapping object. To see the arguments, we can do (starting with
your original post example mapping):
' 2'

And you showed that: Traceback (most recent call last):
File "<stdin>", line 1, in ?
TypeError: format requires a mapping

Let's see:
... def __mod__(self, *a, **kw): print repr(self), a, kw
...
'%(two)*d' ({'two': 2, 'one': 1},) {}

But we need an argument for the '*': '%(two)*d' (({'two': 2, 'one': 1}, 6),) {}


Ok, we see that we have the custom string back as self, and a is the args tuple
for the rhs of the % operator, and there are no keyword args.

But we need a way to pick up the '*' values. I think the most straightforward way
would be to have a mapping argument be the first, for %(name)...x lookups, and
optional additional arguments like an ordinary tuple, like
'%(key)*.*f' (({'key': 2.2999999999999998}, 7, 3),) {}

The intent being like ' 2.300'

You could go hog wild and allow multiple mapped values in a single
format, as in this contrived combination:
'%(key)*.("three")f' (({'three': 3, 'key': 2.2999999999999998}, 7),) {}

BTW, note that a mapping only has to support __getitem__, so you can make a
custom mapping to synthesize interesting named things or find them in interesting
places, and even have special methods that could be called from format.__mod__ for
whatever you think up. Just so your end product is somewhat explainable.

This contrivance illustrates custom mapping as a one-liner class instance
returning the map key uppercased instead of looking something up.
'HI HO'

It's more readable if you define a class as usual ;-) ... def __getitem__(self, key): return key.upper()
... 'HI HO'

Custom stuff tends to be slower than builtins written in C, of course.
I'll leave the rest as an exercise from here ;-)

Regards,
Bengt Richter
 
A

Alex Martelli

Pierre Fortin said:
I was hoping to use the likes of: "%(key)*.*f" % map
however, unlike with the non-(key) formats, there appears to be no way to
specify a "*.*" size when a map is used...

It is not clear to me where you would expect to get the values for those
stars from -- 'map' being a dictionary it has no concept of "ordering",
so there is no concept of "next value" after map['key']. If I had this
kind of problem I would doubtlessly tack it with _two_ formattings,
first one to get the values for the stars (with explicit names), giving
a clean format string, then another to get the value for the data. Say:

'%%(key)%(width)d.%(prec)df' % map % map

Not all that readable, alas. Alternatively, you could use a non-keyed
format string and extract things from the map on the RHS...:

'%*.*f' % [map[x] for x in ('key', 'width', 'prec')]

I personally consider this a bit more readable, but that's quite
debatable, of course...


Alex
 
P

Pierre Fortin

Pierre Fortin said:
I was hoping to use the likes of: "%(key)*.*f" % map
however, unlike with the non-(key) formats, there appears to be no way
to specify a "*.*" size when a map is used...

It is not clear to me where you would expect to get the values for those
stars from -- 'map' being a dictionary it has no concept of "ordering",
so there is no concept of "next value" after map['key']. If I had this
kind of problem I would doubtlessly tack it with _two_ formattings,
first one to get the values for the stars (with explicit names), giving
a clean format string, then another to get the value for the data. Say:

'%%(key)%(width)d.%(prec)df' % map % map

Not all that readable, alas. Alternatively, you could use a non-keyed
format string and extract things from the map on the RHS...:

'%*.*f' % [map[x] for x in ('key', 'width', 'prec')]

I personally consider this a bit more readable, but that's quite
debatable, of course...


Alex

Thanks to you and Bengt for your patience with this problem...

Since I'd already dipped my toe in the dual-map water in an earlier post,
a "Duh!" moment occured to me as I read your responses... Realizing that
examples rarely show the true complexity of a problem, the answer (for me)
lay in keeping the formatting and data maps separate... I find mapped
strings immensely more readable/maintainable than the simple same-quantity
and gotta-be-in-the-right-order ones:

change=["down","up","unchanged"]
"""On %10s, the closing price of $%6.2f was %s
from the day's open of $%6.2f
....
....
""" % (Date, Close, change[int(float(Open>= ...], Open, ... )

especially when the string needs to be reworked by re-ordering, adding
and/or deleting items.

Here's my reasonably (to me :) readable/maintainable solution to this core
problem... Only the "fmt" statement needs tweaking to generate the
desired appearance.

#!/usr/bin/env python

def mkdict(**kwargs):
return kwargs

fmt = { 'wDate':10, 'wOpen':6, 'wHigh':6, 'wLow':6, # width
'wClose':6, 'wVolume':10, 'wAdjClose':6,
'pDate':10, 'pOpen':2, 'pHigh':2, 'pLow':2, # precision
'pClose':2, 'pVolume':0, 'pAdjClose':2 }

# data will be read from several thousand files
sampledata = [
"9-Sep-04,19.49,20.03,19.35,19.93,60077400,19.93",
"8-Sep-04,18.96,19.53,18.92,18.97,52020600,18.96",
"7-Sep-04,18.98,19.18,18.84,18.85,45498100,18.84",
]

change=["down","up","n/c"]

for D in sampledata:
Date, Open, High, Low, Close, Volume, AdjClose = D.split(',')

map = mkdict(Date=Date,
Open=float(Open),
High=float(High),
Low=float(Low),
Close=float(Close),
Volume=int(Volume),
AdjClose=float(AdjClose),
#
Change=change[int(float(AdjClose) >= float(Open)) +
int(float(AdjClose) == float(Open))]
)

# line continuations must be left-justified to avoid extra spaces
print "%%(Date)%(wDate)d.%(pDate)ds \
%%(Open)%(wOpen)d.%(pOpen)df \
%%(High)%(wHigh)d.%(pHigh)df \
%%(Low)%(wLow)d.%(pLow)df \
%%(Close)%(wClose)d.%(pClose)df \
%%(Volume)%(wVolume)d.%(pVolume)dd \
%%(AdjClose)%(wAdjClose)d.%(pAdjClose)df \
%%(Change)s \
" % fmt % map


Generates:
9-Sep-04 19.49 20.03 19.35 19.93 60077400 19.93 up
8-Sep-04 18.96 19.53 18.92 18.97 52020600 18.96 n/c
7-Sep-04 18.98 19.18 18.84 18.85 45498100 18.84 down

At least, now I understand why mappings are the exception to the "*[.*]"
rule of formatting... :^)

Thanks!
Pierre
 
A

Alex Martelli

Pierre Fortin said:
def mkdict(**kwargs):
return kwargs

You don't need this with Python 2.3 and later -- just call dict with
keyword args, it just works!-)
fmt = { 'wDate':10, 'wOpen':6, 'wHigh':6, 'wLow':6, # width
...

....and you don't use the keyword form here, which WOULD be nice;-).
# line continuations must be left-justified to avoid extra spaces

nope, you can format as you wish, thanks to Python's handy idea
(mutuated from C) of merging logically adjacent literals:
print "%%(Date)%(wDate)d.%(pDate)ds \
%%(Open)%(wOpen)d.%(pOpen)df \

You could use, say:

print ( "%%(Date)%(wDate)d.%(pDate)ds"
" %%(Open)%(wOpen)d.%(pOpen)df "
etc etc
)

Anyway, your solution is neat but it seems a bit repetitive to me. Any
time you have to type the same thing right over and over, aka
"boilerplate", there is a reduction in clarity and legibility and a risk
of typos. I would therefore suggest a more advanced idea...:

class formatter:
def __init__(self, format_map, default_width=6, default_prec=2):
self.__dict__.update(locals())
def __getitem__(self, varname):
return '%%(%s)%d.%d" % (varname,
self.format_map.get('w'+varname, self.default_width),
self.format_map.get('p'+varname, self.default_prec))

now you can modify your code to do
fmt = formatter(fmt)
[[you can actually simplify your fmt dict a lot thanks to the
defaults]], and then code the printing like:

print ( '%(Date)ss %(Open)sf %(High)sf %(Low)sf %%(Change)s'
% fmt % map )

It seems to me that this is more readable and easier to maintain than
the repetitious style that you need if fmt is a plain dictionary.

The real point here is that the RHS operand of % can be ANY mapping:
code your own __getitem__ and make it return whatever computed result is
most convenient to you.


Alex
 
P

Pierre Fortin

You don't need this with Python 2.3 and later -- just call dict with
keyword args, it just works!-)
Cool! :^)

nope, you can format as you wish, thanks to Python's handy idea
(mutuated from C) of merging logically adjacent literals:

I'd bumped into er... "issues" a few years ago with "print (...)" so I
never re-explored it... thanks for updating me... :>

Anyway, your solution is neat but it seems a bit repetitive to me.

Agreed... but I've not yet explored classes beyond some really trivial
stuff..

class formatter:
def __init__(self, format_map, default_width=6, default_prec=2):
self.__dict__.update(locals())
def __getitem__(self, varname):
return '%%(%s)%d.%d" % (varname,
self.format_map.get('w'+varname, self.default_width),
self.format_map.get('p'+varname, self.default_prec))

now you can modify your code to do
fmt = formatter(fmt)

OK so far... this works, until....
[[you can actually simplify your fmt dict a lot thanks to the
defaults]], and then code the printing like:

AH-HA!!!! anything _not_ specified is defaulted... took me a minute to
realize that I needed to remove the EXplicits in favor of the IMplicits...
not just remove their values... DUH!! Nice!!

MANY THANKS!!

Pierre

PS: Here's the latest incarnation of the test script... for me, the
"voodoo" part is
self.__dict__.update(locals())
Not sure I could have found that, since without it, the error:
Traceback (most recent call last):
File "./foo", line 40, in ?
print ( "%(Date)ss %(Open)sf %(High)sf %(Low)sf "
File "./foo", line 7, in __getitem__
return "%%(%s)%d.%d" % (varname,
AttributeError: formatter instance has no attribute 'format_map'
seems cryptic until one realizes it's an initilization problem...
Right?

-------------------------
#!/usr/bin/env python

class formatter:
def __init__(self, format_map, default_width=6, default_prec=2):
self.__dict__.update(locals())
def __getitem__(self, varname):
return "%%(%s)%d.%d" % (varname,
self.format_map.get('w'+varname, self.default_width),
self.format_map.get('p'+varname, self.default_prec))

fmt = dict( wDate=10, pDate=10,
wVolume=10, pVolume=0,
wChange=5, pChange=5, )

fmt = formatter(fmt)

# data will be read from several thousand files
sampledata = [
"9-Sep-04,19.49,20.03,19.35,19.93,60077400,19.93",
"8-Sep-04,18.96,19.53,18.92,18.97,52020600,18.96",
"7-Sep-04,18.98,19.18,18.84,18.85,45498100,18.84",
]

change=["down","up","n/c"]

for D in sampledata:
Date, Open, High, Low, Close, Volume, AdjClose = D.split(',')

map = dict(Date=Date,
Open=float(Open),
High=float(High),
Low=float(Low),
Close=float(Close),
Volume=int(Volume),
AdjClose=float(AdjClose),
Change=change[int(float(AdjClose) >= float(Open)) +
int(float(AdjClose) == float(Open))]
)

print ( "%(Date)ss %(Open)sf %(High)sf %(Low)sf "
"%(Close)sf %(Volume)sd %(AdjClose)sf %(Change)ss"
% fmt % map )
----------------------

MUCH simpler/cleaner!! Though I gotta understand how the 2 mappings got
used. Is it as simple as print() iterating over N mappings, taking the
next character(s) for the format..? Elegant if so... though more than 2
mappings could be mind-bending... :^)

Note that I changed "%%(Change)s" to likewise get rid of that oddity...

This has turned into quite a "less is more" education for me... :^)

Thanks again!
 
A

Alex Martelli

Pierre Fortin said:
[[you can actually simplify your fmt dict a lot thanks to the
defaults]], and then code the printing like:

AH-HA!!!! anything _not_ specified is defaulted... took me a minute to
realize that I needed to remove the EXplicits in favor of the IMplicits...
not just remove their values... DUH!! Nice!!

MANY THANKS!!

You're welcome!
Pierre

PS: Here's the latest incarnation of the test script... for me, the
"voodoo" part is
self.__dict__.update(locals())

Ah, sorry, my favourite idiom to avoid the boilerplate of

def __init__(self, fee, fie, foo, fum):
self.fee = fee
self.fie = fie
self.foo = foo
self.fum = fum

Boilerplate is bad, and I'm keen on "Once, and ONLY once!" as a
programming practice, so having to mention each of those names three
times in totally repetitive ways makes me shiver. My favourite idiom
does, per se, leave a silly self.self around (which means a silly
reference loop) so I should remember to 'del self.self' after it...

Not sure I could have found that, since without it, the error:
Traceback (most recent call last):
File "./foo", line 40, in ?
print ( "%(Date)ss %(Open)sf %(High)sf %(Low)sf "
File "./foo", line 7, in __getitem__
return "%%(%s)%d.%d" % (varname,
AttributeError: formatter instance has no attribute 'format_map'
seems cryptic until one realizes it's an initilization problem...
Right?

Right, you have to self.format_map=format_map and so on, or shortcut
that through the above-mentioned idiom.
Date, Open, High, Low, Close, Volume, AdjClose = D.split(',')

map = dict(Date=Date,
Open=float(Open),
High=float(High),
Low=float(Low),
Close=float(Close),
Volume=int(Volume),
AdjClose=float(AdjClose),
Change=change[int(float(AdjClose) >= float(Open)) +
int(float(AdjClose) == float(Open))]
)

I could suggest some deboilerplatization here, too, but maybe that would
be pushing things too far...
print ( "%(Date)ss %(Open)sf %(High)sf %(Low)sf "
"%(Close)sf %(Volume)sd %(AdjClose)sf %(Change)ss"
% fmt % map )
----------------------

MUCH simpler/cleaner!! Though I gotta understand how the 2 mappings got
used. Is it as simple as print() iterating over N mappings, taking the
next character(s) for the format..? Elegant if so... though more than 2
mappings could be mind-bending... :^)

No, print itself has nothing to do with it; % is left associative, so

print something % fmt % map

is equivalent to, say:

temp1 = something % fmt
temp2 = temp1 % map
print temp2

Come to think of that, you SHOULD hoist the "real format" out of the
loop, what I'm calling temp1 (silly name) in this snippet -- the fmt
does not change between legs of the loop, so you don't really want to
recompute the identical temp1 thousands of times (premature optimization
is the root of all evil in programming, sure, but here, this simple
'hoist a constant out of the loop' is just avoiding waste... waste not,
want not... -- and in some sense splitting the format computation out of
the formatting makes things _simpler_!).

Note that I changed "%%(Change)s" to likewise get rid of that oddity...

Yep, makes things more regular. Though the computation of Change itself
is strange, but, whatever... it IS a Python oddity that True+False
equals 1;-).

This has turned into quite a "less is more" education for me... :^)

Thanks again!

And again, you're welcome!


Alex
 
P

Pierre Fortin

Ah, sorry, my favourite idiom to avoid the boilerplate of

def __init__(self, fee, fie, foo, fum):
self.fee = fee
self.fie = fie
self.foo = foo
self.fum = fum

Boilerplate is bad, and I'm keen on "Once, and ONLY once!" as a
programming practice, so having to mention each of those names three
times in totally repetitive ways makes me shiver. My favourite idiom

I fully agree with "once and ONLY once"... but you've pointed out that
"not-at-all is better than once"... :^)
does, per se, leave a silly self.self around (which means a silly
reference loop) so I should remember to 'del self.self' after it...

Since "self" is a convention (could use "this"), t'would be nice if Python
could avoid foo.foo ref. loops in a future release...
map = dict(Date=Date,
Open=float(Open),
High=float(High),
Low=float(Low),
Close=float(Close),
Volume=int(Volume),
AdjClose=float(AdjClose),
Change=change[int(float(AdjClose) >= float(Open)) +
int(float(AdjClose) == float(Open))]
)

I could suggest some deboilerplatization here, too, but maybe that would
be pushing things too far...

Hey! You're on a roll and I'm open for more abu... er education if this
isn't an imposition... :^)
No, print itself has nothing to do with it; % is left associative, so

Of course.... lack of sleep triggered that faulty logic...
Come to think of that, you SHOULD hoist the "real format" out of the
loop,

That's a given in my book... :>
it IS a Python oddity that True+False
equals 1;-).

That's OK with me... it was True+True equals *1* (OR v. +) that forced me
to wrap them with int()'s

Take care,
Pierre
 
J

Jeff Epler

Pierre Fortin said:
I was hoping to use the likes of: "%(key)*.*f" % map
however, unlike with the non-(key) formats, there appears to be no way to
specify a "*.*" size when a map is used...

It is not clear to me where you would expect to get the values for those
stars from -- 'map' being a dictionary it has no concept of "ordering",
so there is no concept of "next value" after map['key'].

Perhaps Python string formatting should be enhanced similarly to the $
extension (from SuS) in C.
By default, the arguments are used in the order given, where
each `*' and each conversion specifier asks for the next argument
(and it is an error if insufficiently many arguments are
given). One can also specify explicitly which argument is
taken, at each place where an argument is required, by writing
`%m$' instead of `%' and `*m$' instead of `*', where the
decimal integer m denotes the position in the argument list of
the desired argument, indexed starting from 1. Thus,
printf("%*d", width, num);
and
printf("%2$*1$d", width, num);
are equivalent.
In the case of Python, this would become
print "%(width)*(num)d" % {'width': width, 'num': num}

Jeff

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.2.1 (GNU/Linux)

iD8DBQFBQctPJd01MZaTXX0RAiQfAJ48AyWgk/77DdQQKiD2Pcrtw500MQCdGrkX
/Z8tnGwoT0pcrV773W1Gx0Q=
=wLh0
-----END PGP SIGNATURE-----
 
P

Pierre Fortin

In the case of Python, this would become
print "%(width)*(num)d" % {'width': width, 'num': num}

If this did change, I'd prefer:
print "%(num)*(width)d" % {'width': width, 'num': num}
^^^ ^^^^^
in keeping with "in this order" (PyEssenRef 2nd ed. - P.47)

Pierre
 
J

Jeff Epler

If this did change, I'd prefer:
print "%(num)*(width)d" % {'width': width, 'num': num}
^^^ ^^^^^
in keeping with "in this order" (PyEssenRef 2nd ed. - P.47)

I think I got my Python example wrong. Either way, I think it should
match the SuS printf extension behavior, but with dict keys instead of
numeric indices.

Jeff

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.2.1 (GNU/Linux)

iD8DBQFBQdkzJd01MZaTXX0RAvlqAJ9J/KsIPlmQMHSPZs4o2JBT4qRSVwCgrSf+
U9KOTk0IMBc+fZ1CFDL0G04=
=2QkI
-----END PGP SIGNATURE-----
 
A

Alex Martelli

Pierre Fortin said:
I fully agree with "once and ONLY once"... but you've pointed out that
"not-at-all is better than once"... :^)

Well, there is 'once' here -- the names of the attributes are listed
once, in the argument list of 'def __init__'
Since "self" is a convention (could use "this"), t'would be nice if Python
could avoid foo.foo ref. loops in a future release...

I don't see how to do that cleanly, simply and within general rules.
"Special cases are not special enough to break the rules"...

map = dict(Date=Date,
Open=float(Open),
High=float(High),
Low=float(Low),
Close=float(Close),
Volume=int(Volume),
AdjClose=float(AdjClose),
Change=change[int(float(AdjClose) >= float(Open)) +
int(float(AdjClose) == float(Open))]
)

I could suggest some deboilerplatization here, too, but maybe that would
be pushing things too far...

Hey! You're on a roll and I'm open for more abu... er education if this
isn't an imposition... :^)

Sure, see later.
That's OK with me... it was True+True equals *1* (OR v. +) that forced me
to wrap them with int()'s
Nope:
2

so I'm not sure what you mean...?

Anyway, back to map. You have a bunch of local variables you want to
also set into a dict, except that most of them will need to be passed
through some one-argument function first. Change can fall into that
pattern if you just compute a local variable of that name just before
you compute map, so assume you've done that.

Now, once, outside of the loop, you prepare...:

howto_makemap = dict(Date=str, Volume=int,
Change=change.__getitem__)

then, when it's time to prepare map,

map = {}
for k, v in locals():
try: map[k] = howto_makemap.get(k, float)(v)
except (ValueError, TypeError): pass

voila -- that's all you need. You may end up with a few extraneous
entries in map (corresponding to local variables that just happen to be
castable into a float but have nothing to do with 'map') but that ain't
gonna break anything.


Whether I'd do this in production code -- I dunno. It does reduce
boilerplate, but unfortunately it can only do this via a CLEVER trick.
Now CLEVER is a _criticism_, for code that's meant to be read and
maintained... something that happens over and over, like the
self.__dict__update idea, can be forgiven some cleverness, but a one-off
need is *probably* best met with simplicity even if that does mean some
boilerplate. After all, if it's clever, 8 months from now when you need
to touch your code again you'll have a hard time figuring out what you
were doing here. Good code is linear and obvious, either because of
intrinsic simplicity or familiarity through idiomatic (frequent) use...

So, in production code, I think I'd eschew this cleverness and rather
take the boilerplate hit. Simplicity never comes easy to those of us
who are born with a tortuous mind, but it's still worth striving for!


Alex
 
P

Pierre Fortin

Pierre Fortin <[email protected]> wrote:
"Special cases are not special enough to break the rules"...
Touche'


2

so I'm not sure what you mean...?

My bad... burned by operator priority...
Change=change[float(AdjClose) >= float(Open) +
float(AdjClose) == float(Open) ]
should read:
Change=change[(float(AdjClose) >= float(Open)) +
(float(AdjClose) == float(Open)) ]

So, in production code, I think I'd eschew this cleverness and rather
take the boilerplate hit. Simplicity never comes easy to those of us
who are born with a tortuous mind, but it's still worth striving for!

Amen.... I've been too clever in the past so I'll agree and leave the
boilerplate...

Thanks!
Pierre
-30-
 

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

No members online now.

Forum statistics

Threads
473,995
Messages
2,570,230
Members
46,819
Latest member
masterdaster

Latest Threads

Top