2

I currently have the code:

import sys

shell = sys.stdout.shell

my_name = ("Bob", "STRING")
friends_name = ("Jeff", "KEYWORD")
question = ("My name is %s in green and my friend's name is %s in red" % (my_name, friends_name))
shell.write(question)

I am trying to achieve an output of:

My name is Bob in green and my friend's name is Jeff in red.

And in the output the word Bob is green and Jeff is red.

However the code I tried simply gives an output of:

My name is ('Bob', 'STRING') in green and my friend's name is ('Jeff', 'KEYWORD') in red

I do know that just typing shell.write("Bob", "STRING") works but that takes up too many lines of code if you need to print more things in colour.

I have also tried to do

my_name = shell.write("Bob", "STRING")

But when you print that it just outputs 3

I need an answer of how to achieve the output, and also why it outputs 3 when printing

my_name = shell.write("Bob", "STRING")

I would also like to know why it strangely also prints 'Bobby' in green upon being defined like above

PS. I am on a Windows machine with python 3.4.3

Rlz
  • 1,649
  • 2
  • 13
  • 36
  • `my_name = shell.write("Bob", "STRING")` gives 3 because `write()` returns the number of characters written ("Bob" is 3 characters). – cdarke Nov 05 '16 at 08:09
  • Oh ok so If I used `my_name = shell.write("Bobby", "STRING")` it would return 5 @cdarke – Rlz Nov 05 '16 at 08:15
  • Also, when I define it like that it prints Bobby in green. I didn't even ask it to print anything – Rlz Nov 05 '16 at 08:17
  • It should be noted that the existence of sys.stdout.shell is an internal implementation detail of how a user code execution process sends text to the IDLE shell through a socket. It is subject to change. Also, the particular mapping of the tags 'STRING' and 'KEYWORD' to green and orange (not red) is merely the default and subject to change by any user who defines a custom color mapping on the Highlighting tab of the options dialog. – Terry Jan Reedy Nov 06 '16 at 00:59
  • A bit more explanation: The IDLE user process currently sends both stdout and stderr output to the visible Shell through one socket connection. Chunks of text in the two streams are tagged 'stdout' or 'stderr' to differentiate, Text written to the shell get the corresponding color-coding. The default is a blue or red tint, but this again is subject to customization. – Terry Jan Reedy Nov 06 '16 at 01:31
  • @TerryJanReedy: thanks for your input. I ended-up diving into the source-code myself. The main thing I learnt is why so many things work differently in IDLE because of the monkey-patching going on. The default colours are set in a configuration file, but it isn't documented so I stopped short of suggesting it could be changed. I couldn't find a public interface to change colours programatically. – cdarke Nov 06 '16 at 06:47
  • @cdarke The documented way to customize configuration is to use the dialog. The .def (ault) files in idlelib should not be touched. Mis-editing the .cfg files in $HOME/.idlerc/ can cause problems. Definitely 'at your own risk'. There have been SO questions answered by 'delete the corrupted .cfg files'. Since syntax colors are only used to mark user code before execution, user code should normally have nothing to do with them. – Terry Jan Reedy Nov 06 '16 at 18:20
  • @TerryJanReedy: exactly, that's why I didn't suggest it. IDLE was never designed to be used this way anyway, the OP should use the GUI tools in tkinter and not IDLE. – cdarke Nov 07 '16 at 08:12

1 Answers1

1

Here's a way to do it.

import sys

def shell_print(txt, *args):

    out = txt.split("%s")
    # vars = [*args]     # See comments
    vars = list(args)
    for t in out:
        sys.stdout.shell.write(t)
        if vars:
            v = vars.pop(0)
            sys.stdout.shell.write(*v)

my_name = ("Bob", "STRING")
friends_name = ("Jeff", "KEYWORD")
question = "My name is %s in green and my friend's name is %s in red" 

shell_print(question, my_name, friends_name)

It's only a QAD (Quick and Dirty) solution, and will only for with "%s", but its a start. So the first parameter is the string containing the %s place holders, the following parameters (any number of them) are the variables, with their attributes, to be inserted.

EDIT: The basic principle is that first we take the string in txt and split it around the %s's, so we are left with (in the example) a list like this (out):

["My name is ", "in green and my friend's name is", "in red"]

Then we loop through (iterate) this list. We write the next element of out then look at the first element in args, which is a tuple. Assuming there is one, then we pass those two tuple elements to write().

sys.stdout.shell.write(*v)

The * does unpacking, that is, if there are two elements in the tuple called v then it will pass two arguments.

We converted the args into a list so that we can pop() the elements. The pop(0) method removes a element from the list, returning what it removed. So every time we go around the loop we always get the first element in the list.

By the way, we are "popping" from the front of the list (that's the zero), which is inefficient (more efficient to pop from the end). But the list will be small so it is not a big deal.

2nd EDIT:

Improved version, including further tests:

import sys

def shell_print(txt, *args):
    shell = sys.stdout.shell
    out = txt.split("%s")
    argc = len(args)

    for i, t in enumerate(out):
        shell.write(t)
        if i < argc:
            sargs = (str(args[i][0]), args[i][1])
            shell.write(*sargs)

my_name = ("Bob", "STRING")
friends_name = ("Jeff", "KEYWORD")
question = "My name is %s in green and my friend's name is %s in red\n" 
shell_print(question, my_name, friends_name)

# Test all tags
valid_tags = {"COMMENT","KEYWORD","BUILTIN","STRING","DEFINITION","SYNC",
              "TODO","ERROR"}
for tag in valid_tags:
    shell_print("\n", (tag, tag))

# Other types
my_num = (1234, "STRING")
my_float = (3.142, "COMMENT")

text = "\nMy number: %s, My float: %s\n" 
shell_print(text, my_num, my_float)
cdarke
  • 42,728
  • 8
  • 80
  • 84
  • with the vars = [*args] bit – Rlz Nov 05 '16 at 08:31
  • OK, I tested this on 3.5, but that shouldn't make a difference, what is the error? – cdarke Nov 05 '16 at 08:32
  • The error is: Can use starred expression only as assignment target – Rlz Nov 05 '16 at 08:33
  • I will give you an upvote for now but I cannot accept it as the answer until it works right – Rlz Nov 05 '16 at 08:36
  • Are you sure you are running python 3? That sounds like a Python 2 error – cdarke Nov 05 '16 at 08:36
  • Yes I am sure it is python 3 – Rlz Nov 05 '16 at 08:37
  • Also, just a quick question, why do I find it so hard to get upvotes on my questions? – Rlz Nov 05 '16 at 08:40
  • That was weird, like I said, it worked fine on 3.5. Oh well, glad its OK now – cdarke Nov 05 '16 at 08:41
  • Upvotes are a strange thing. In this case (sorry to be blunt) you showed that you did not understand that calling a function with two parameters (the `shell.write()` is not the same as passing a single string with embedded values. I wouldn't worry about it! – cdarke Nov 05 '16 at 08:43
  • 1
    Added some explanation – cdarke Nov 05 '16 at 08:53
  • If it was a larger list, how would you then pop it from the end? Would you reverse the list? If you did, how would you then take it from the exact end? Would you have to keep looking up the length of the list? Awesome explanation by the way! :) – Rlz Nov 05 '16 at 09:01
  • If it was a larger list then I would probably reverse it. However I should emphasise that this solution was very much the first thing which snapped into my head. For a large list I wouldn't `pop()` or `reverse()` but keep track of position, i.e. I wouldn't change the size. Yes, that would mean checking that the position was in bounds, but I could just trap an `IndexError` exception for that. There are several ways to do this. – cdarke Nov 05 '16 at 09:32
  • Wow, your very good at python! Can you please write as a new answer what you would do for a larger list? Thanks! – Rlz Nov 05 '16 at 09:35
  • I didn't answer your question "how would you then take it from the exact end?". That's easy, its the default position in `pop()`, so `vars.pop()` removes the last element. – cdarke Nov 05 '16 at 09:35
  • The answer for a larger list will take some time which I don't have right now. Do you need types other than strings? For example you just use `%s`, should I support other formats like `%d`? If you can wait I'll work on a fuller solution. BTW I'm only average at Python, but I've been programming a for while. – cdarke Nov 05 '16 at 09:39
  • Thanks for the help and the fuller version I am sure it will be amazing but there is one more question I would like to know for now – Rlz Nov 05 '16 at 09:58
  • How do I make the question a different colour as well? – Rlz Nov 05 '16 at 09:58
  • Do you mean the colour in the Stack Overflow question? I don't think you can. – cdarke Nov 05 '16 at 10:33
  • I mean the My name is bit – Rlz Nov 05 '16 at 10:34
  • The `My name is %s in green and my friend's name is %s in red` – Rlz Nov 05 '16 at 10:34
  • Can you change the colour of that? – Rlz Nov 05 '16 at 10:36
  • I have no idea. I didn't know you could do these colour changes in IDLE until I looked at your question, and can find no documentation on it. Where did you get the information for your `shell.write()` from? – cdarke Nov 05 '16 at 13:11
  • 2
    @odarke This is an undocumented IDLE-specific internal interface. Someone must have read the code very carefully to discover this 'use at your own risk' trick. See the comment I just added to the question. – Terry Jan Reedy Nov 06 '16 at 01:21
  • @TerryJanReedy don't know if you realised but you put odarke instead of cdarke. Anyway, can you think of ANY way to change the colour of the `My name is %s in green and my friend's name is %s in red`? Is there any way without setting the colour for all text? – Rlz Nov 06 '16 at 09:05
  • I did pick the comment up. You didn't answer my question, where did you get the information from to do the `shell.write`? @TerryJanReedy is correct, you are using undocumented interfaces here, and we have to wonder why you are doing it. IDLE is not an end-user tool and there are many IDEs that are easier to use (subjective), so why are you fixated on colour in IDLE? Python is often run from the command-line, and none of this would work there. – cdarke Nov 06 '16 at 09:19
  • Check out [This answer here](http://stackoverflow.com/a/36183935/5959326) on how to change colours, that is how I got it. I was just not sure on how to add colours like I stated in the question. Thanks for the answer, it has really helped. I am running python in the IDLE because that was the only way I could get the colours to change. I tried to get colours to change in loads of different ways but none of them worked so I went with this idea. – Rlz Nov 06 '16 at 12:04
  • Now I have answered your question, can you answer mine? – Rlz Nov 06 '16 at 12:05
  • I don't think there is a way to do that programatically in IDLE. Have you considered writing a Tk application instead? IDLE is written in Tk and the interfaces for text are quite easy. There is also a simple wrapper around TK called `easygui` https://pypi.python.org/pypi/easygui/0.98.0 – cdarke Nov 06 '16 at 14:31