http://www.perlmonks.org?node_id=736412

2xlp has asked for the wisdom of the Perl Monks concerning the following question:

python's sprintf is really neat.

you can use placeholders like perl hello =  "Hello %s!" % $name
or with name placeholders, like in a database query hello = """Hello %(name)s""" % { 'name': name }

in all my years of perl , i've never seen named placeholders like this. that could be more my exposure/eduction than anything else though -- does anyone know a way to achieve/emulate this behavior (without, of course, writing my own regex function/module or using many cpan libraries)?

Replies are listed 'Best First'.
Re: python like named placeholders sprintf ?
by runrig (Abbot) on Jan 15, 2009 at 00:06 UTC

    Perl doesn't need the feature since perl interpolates variables in double quoted strings (as ikegami demonstrates above). Python can't do it the same way as perl because it doesn't have sigils (e.g. $@%) in front of its variables, so there's no way to tell what's variable and what's constant without some other markup. The sprintf function itself functions somewhat like what you describe, and has options to format the variable in various ways if using plain old double quote interpolation isn't good enough.

    Oh, and using "placeholders" as you describe in a SQL query string is bad (while these sort of placeholders are good).

Re: python like named placeholders sprintf ?
by ikegami (Patriarch) on Jan 14, 2009 at 23:47 UTC

    does anyone know a way to achieve/emulate this behavior

    Perl's sprintf can refer to arguments by number, but not by name.

    >perl -le"print sprintf '%2$s %1$s', qw( a b )" b a

    Double-quoted strings interpolate, so all you need

    $hello = "Hello $name";

    If you're asking for the format to be variable, then you're asking for a templating system. There are many of those. Here's a fair approximation of the syntax you requested:

    use String::Interpolate qw( ); sub format { my ($pat, $args) = @_; my $t = String::Interpolate->new($args); $t->($pat); return "$t"; } my $hello = format('Hello $name', { name => 'World' });

    Update: Added code
    Update: haoess found Text::Sprintf::Named

Re: python like named placeholders sprintf ?
by jdporter (Paladin) on Jan 15, 2009 at 01:45 UTC

    Sure it does. It's just that the syntax is a little more verbose than in Python.

    print "My first name is $_->{first} and my last name is $_->{last}\n" for { first => 'jd', last => 'porter' };

    ;-)

    Between the mind which plans and the hands which build, there must be a mediator... and this mediator must be the heart.
Re: python like named placeholders sprintf ?
by haoess (Curate) on Jan 15, 2009 at 08:33 UTC
    does anyone know a way to achieve/emulate this behavior?

    Yes, there is Text::Sprintf::Named.

    -- Frank

Re: python like named placeholders sprintf ?
by jeffa (Bishop) on Jan 15, 2009 at 18:20 UTC

    Ummm ... so what is wrong with Perl's sprintf? Why would you need to name the placeholders? If you need that, then you really need a templating language.

    my $hello = sprintf "Hai %s! Time: %s\n" => $name, scalar(localtime);

    PHP has sprintf too, you know. To use database placeholders in PHP you need to look to PEAR, but if you don't want that you can always use sprintf to give placeholders to your code:

    $results = array(); foreach ($tables as $table) { $sql = sprintf(" SELECT id, %s as customer, status, status_error, submit_datetime, FROM stats.%s WHERE direction = %s ", $config['customer_field'], $table, $config['direction'], ); $results[$table] = $dbh->exec( $sql ); } return $results;

    jeffa

    L-LL-L--L-LL-L--L-LL-L--
    -R--R-RR-R--R-RR-R--R-RR
    B--B--B--B--B--B--B--B--
    H---H---H---H---H---H---
    (the triplet paradiddle with high-hat)
    
      I know this isn't a PHP board but what you're suggesting is very dangerous. Using simple sprintf & %s in php like that does not get you all the benefits of ? placeholders in DBI. Later versions of PHP have PDO which is similar to DBI (but not as nice).
Re: python like named placeholders sprintf ?
by 2xlp (Sexton) on Jan 15, 2009 at 20:26 UTC

    @jdporter - brilliant

    @runrig - yes, i meant using bind for interpolation. a lot of db abstraction layers in different languages support naming your bind variables -- instead of ? you'd have ?(varname) or something similar -- and then pass in a hash/dict

    @haoess Text::Sprintf::Named is exactly what i needed.

    @jeffa perl's sprintf is great when you have 2-4 variables that you're putting in. but sometimes your sprintf statements grow... and you end up with 2 paragraphs of text , sometimes which use 1 variable 4+ times ( like a class id for a nested div when autogenerating html ).

    python was rather neat in that it allowed %s and %(named)s -- or any other format for its sprintf style stuff. it probably did arise out of not being able to interpolate strings - but its a simply wonderful feature for writing easy to use , legible code

    thanks to all!

      At PM, we typically don't use @-notation for replies. Sorry, it's a little too Web 2.0 for our tastes ;)

      A sensible alternative (and the local convention) is to put a member's username inside square brackets: []. This links user names to home nodes.

        or respond to each reply individually
Re: python like named placeholders sprintf ?
by Jenda (Abbot) on Jan 15, 2009 at 22:24 UTC
    use Interpolation '%:$$->$' => sub {sprintf '%'.$_[0], $_[1]}; my $x = 41.758; my $name = "Jenda"; print "Hello $%{'06d'}{$x} or $%{' 10.5f'}{$x}, $%{'20s'}{$name}, and +so forth\n"; $x = 1.7; $name = "Leaveolus"; print "Hello $%{'06d'}{$x} or $%{' 10.5f'}{$x}, $%{'20s'}{$name}, and +so forth\n";

    The need for the singlequotes inside the curlies makes it a little less nice, but I do not see a way around that. OTOH, you can add your own formats by modifying the subroutine in that use statement. Or you can define different interpolations if you like:

    use Interpolation 'S' => sub { local $_ = sprintf("%.2f", shift()); 1 while s/^(-?\d+)(\d{3})/$1,$2/; '$'.$_; }; #... print "And the total price is $S{$Price}.\n";