8

[EDIT 00]: I've edited several times the post and now even the title, please read below.

I just learned about the format string method, and its use with dictionaries, like the ones provided by vars(), locals() and globals(), example:

name = 'Ismael'
print 'My name is {name}.'.format(**vars())

But I want to do:

name = 'Ismael'
print 'My name is {name}.' # Similar to ruby

So I came up with this:

def mprint(string='', dictionary=globals()):
    print string.format(**dictionary)

You can interact with the code here: http://labs.codecademy.com/BA0B/3#:workspace

Finally, what I would love to do is to have the function in another file, named my_print.py, so I could do:

from my_print import mprint

name= 'Ismael'
mprint('Hello! My name is {name}.')

But as it is right now, there is a problem with the scopes, how could I get the the main module namespace as a dictionary from inside the imported mprint function. (not the one from my_print.py)

I hope I made myself uderstood, if not, try importing the function from another module. (the traceback is in the link)

It's accessing the globals() dict from my_print.py, but of course the variable name is not defined in that scope, any ideas of how to accomplish this?

The function works if it's defined in the same module, but notice how I must use globals() because if not I would only get a dictionary with the values within mprint() scope.

I have tried using nonlocal and dot notation to access the main module variables, but I still can't figure it out.


[EDIT 01]: I think I've figured out a solution:

In my_print.py:

def mprint(string='',dictionary=None):
    if dictionary is None:
        import sys
        caller = sys._getframe(1)
        dictionary = caller.f_locals
    print string.format(**dictionary)

In test.py:

from my_print import mprint

name = 'Ismael'
country = 'Mexico'
languages = ['English', 'Spanish']

mprint("Hello! My name is {name}, I'm from {country}\n"
       "and I can speak {languages[1]} and {languages[0]}.")

It prints:

Hello! My name is Ismael, I'm from Mexico
and I can speak Spanish and English.

What do you think guys? That was a difficult one for me!

I like it, much more readable for me.


[EDIT 02]: I've made a module with an interpolate function, an Interpolate class and an attempt for a interpolate class method analogous to the function.

It has a small test suite and its documented!

I'm stuck with the method implementation, I don't get it.

Here's the code: http://pastebin.com/N2WubRSB

What do you think guys?


[EDIT 03]: Ok I have settled with just the interpolate() function for now.

In string_interpolation.py:

import sys


def get_scope(scope):
    scope = scope.lower()
    caller = sys._getframe(2)
    options = ['l', 'local', 'g', 'global']

    if scope not in options[:2]:
        if scope in options[2:]:
            return caller.f_globals
        else:
            raise ValueError('invalid mode: {0}'.format(scope))
    return caller.f_locals


def interpolate(format_string=str(),sequence=None,scope='local',returns=False):
    if type(sequence) is str:
        scope = sequence
        sequence = get_scope(scope)
    else:
        if not sequence:
            sequence = get_scope(scope)

    format = 'format_string.format(**sequence)'
    if returns is False:
        print eval(format)

    elif returns is True:
        return eval(format)

Thanks again guys! Any opinions?


[EDIT 04]:

This is my last version, it has a test, docstrings and describes some limitations I've found: http://pastebin.com/ssqbbs57

You can quickly test the code here: http://labs.codecademy.com/BBMF#:workspace

And clone grom git repo here: https://github.com/Ismael-VC/python_string_interpolation.git

ROMANIA_engineer
  • 54,432
  • 29
  • 203
  • 199
HarmonicaMuse
  • 7,633
  • 37
  • 52
  • 2
    related: [Is a string formatter that pulls variables from its calling scope bad practice?](http://stackoverflow.com/questions/13312240/is-a-string-formatter-that-pulls-variables-from-its-calling-scope-bad-practice) – jfs May 12 '13 at 08:10
  • 1
    related: [Printing Variable names and contents as debugging tool; looking for emacs/Python shortcut](http://stackoverflow.com/questions/2813227/printing-variable-names-and-contents-as-debugging-tool-looking-for-emacs-python) – jfs May 12 '13 at 08:13
  • This is an interesting exercice, but I would warn against this kind of implicit behavior: Python discourages it ("Explicit is better than implicit" means here that `mprint('…', vars())` is better than `mprint('…')` going back to the caller and getting its local variables), and I think that it does for good reasons (explicit code is arguably easier to read and maintain). – Eric O. Lebigot May 12 '13 at 13:28
  • Would it make any difference if the code were properly documented? something like `help(mprint)` `mprint([string[, dictionary]]) -> string` `'''plus concise docstring'''` What I uderstand about data hiding and interface design is that the caller only needs to know, the pre-conditions for the call, what is the behaviour, what does it does and what returns after the call, etc. But not how this is achieved. I think it is arguably easier to read, less typing (bugs),and it wouldn't be implicit or magic if it's explicily explained. What would be the difference with this or any other wrapper? Thanks! – HarmonicaMuse May 12 '13 at 14:35
  • btw, you could use [`string.Formatter` to accept an arbitrary mapping as `.format_map()` does](http://stackoverflow.com/q/8218652/4279) e.g., `def mprint(.., _format=Formatter().format): ..` – jfs May 12 '13 at 15:35
  • I like the idea! but I can't even get how to implement an interpolate method, this would make the module more general, I like that, could you give me an example? Thanks! – HarmonicaMuse May 12 '13 at 22:11
  • the link in my first comment contains an example – jfs May 15 '13 at 04:14

3 Answers3

2

Modules don't share namespaces in python, so globals() for my_print is always going to be the globals() of my_print.py file ; i.e the location where the function was actually defined.

def mprint(string='', dic = None):
    dictionary = dic if dic is not None else globals()
    print string.format(**dictionary)

You should pass the current module's globals() explicitly to make it work.

Ans don't use mutable objects as default values in python functions, it can result in unexpected results. Use None as default value instead.

A simple example for understanding scopes in modules:

file : my_print.py

x = 10
def func():
    global x
    x += 1
    print x

file : main.py

from my_print import *
x = 50
func()   #prints 11 because for func() global scope is still 
         #the global scope of my_print file
print x  #prints 50
Community
  • 1
  • 1
Ashwini Chaudhary
  • 244,495
  • 58
  • 464
  • 504
  • Isn't there a way to get the namespace of `main.py` from,`my_print.py` as a dict? I'm reading about inheritance, right now, wouldn't a class dot notation do the trick? But then I would have to make the class callable ¿? LOL I'll call it a day for now, thanks Ashwini! – HarmonicaMuse May 12 '13 at 07:27
  • From python docs: http://docs.python.org/2/library/functions.html#vars So I need to get main.py `__dict__` from inside mprint, is it really impossible? I can't stop thinking about this. – HarmonicaMuse May 12 '13 at 07:36
  • @user2374329 You can use `main.__dict__`, but it'll only contain the variables that were defined when `main` was imported. BTW Inheritance is related to classes not modules. – Ashwini Chaudhary May 12 '13 at 07:43
  • Thanks again I was completely lost. – HarmonicaMuse May 12 '13 at 09:40
1

Part of your problem - well, the reason its not working - is highlighted in this question.

You can have your function work by passing in globals() as your second argument, mprint('Hello my name is {name}',globals()).

Although it may be convenient in Ruby, I would encourage you not to write Ruby in Python if you want to make the most out of the language.

Community
  • 1
  • 1
Burhan Khalid
  • 169,990
  • 18
  • 245
  • 284
  • I just want to type less really! ;) I started using the idiom `''.format(**vars())` a lot in my scripts, then I reallized I had to refactor that, so I did (tried), I don't really care if it looks like ruby or not, since I didn't knew anything about programming until recently, but still it looks much more readable to me, and I use it a lot for simple formatting. I would like to be able to call it like this: `from my_print import mprint; name= 'Ismael'; mprint('Hello! My name is {name}.')` Without the globals, etc call each time just to know how could it be done, there must be a way! – HarmonicaMuse May 12 '13 at 06:59
  • Basically what I want to achieve, is to make this the default behaviour: `mprint('Hello my name is {name}', vars())` without explicitly calling vars() each time. Thanks for the link by the way! – HarmonicaMuse May 12 '13 at 07:04
0

Language Design Is Not Just Solving Puzzles: ;)

http://www.artima.com/forums/flat.jsp?forum=106&thread=147358

Edit: PEP-0498 solves this issue!

The Template class from the string module, also does what I need (but more similar to the string format method), in the end it also has the readability I seek, it also has the recommended explicitness, it's in the Standard Library and it can also be easily customized and extended.

http://docs.python.org/2/library/string.html?highlight=template#string.Template

from string import Template

name = 'Renata'
place = 'hospital'
job = 'Dr.'
how = 'glad'
header = '\nTo Ms. {name}:'

letter = Template("""
Hello Ms. $name.

I'm glad to inform, you've been
accepted in our $place, and $job Red
will ${how}ly recieve you tomorrow morning.
""")

print header.format(**vars())
print letter.substitute(vars())

The funny thing is that now I'm getting more fond of using {} instead of $ and I still like the string_interpolation module I came up with, because it's less typing than either one in the long run. LOL!

Run the code here:

http://labs.codecademy.com/BE3n/3#:workspace

HarmonicaMuse
  • 7,633
  • 37
  • 52