-1
class myClass():
  def __init__(self, name, age, username):
      self.name = name
      self.username = username
      self.age = age
      self.result = None

  def post(self, message):
        self.data = doSomethingVeryComplex()
        print(message) 

instance = myClass("Robstersgaming", "50", "Rob")
myClass.post(f"Hello {self.name} nice name!!! {self.age}, {self.username}, {self.data}") 

I only want the message to be evaluated after doSomethingVeryComplex has given the class the data it needs. My goal is for a message to be passed into a class that use its own instance variables. however, obviously in this case I could just do f"Hello {instance.name"} however, in my situation that is not a possibility as I don't know when the data will be put into self. I need my class to be able to entirely handle the construction of the message as soon as it gets the data. So the user will be able to input something with placeholders for variables then once the data is received and put into self it is able to evaluate it according to whatever the user put in.

Robby S
  • 11
  • 1
  • 6
  • It's not clear what you're asking. Why not print `f"Hello {self.name} nice name!!! {self.age}, {self.username}"` from the `post` method itself? That way it's defined for all instances of your class and all you need to do is call `post()` to get that message? Do you have a different use-case in mind? Can you give some examples of how you're intending to use this `post` method? – ddejohn Mar 07 '21 at 03:15
  • Does this answer your question? [Named placeholders in string formatting](https://stackoverflow.com/questions/2286648/named-placeholders-in-string-formatting) – CryptoFool Mar 07 '21 at 03:15
  • @blorgon the reason is I want the messages to be varying but have access to the instance vars. This example is bad at showing the issue lol I will fix it but I need it so I can pass any message with any of the available instance variables that post has access to and only have them be evaluated when post() is called. so I could do ```instance(message="My name is {self.name}")``` then later do ```instance(message="My age is {self.age})``` and access different messages with different variables under the same class and method – Robby S Mar 07 '21 at 03:18
  • @CryptoFool if by named placeholders it is talking about .format() then no because I want which variables, the order they appear in, etc. to be decided by the caller. So ```instance1(message="My name is {self.name}")``` ```instance2(message="My age is {self.age})``` like this where within my class I can have the message change with different variables and a different message. Unless I am misunderstanding... – Robby S Mar 07 '21 at 03:21

3 Answers3

2

I think you want a normal string, not an f-string. You can put names in curly brackets if you call format on the string and pass the appropriate values as keyword arguments:

class myClass():
    def __init__(self, name, age, username):
        self.name = name
        self.username = username
        self.age = age

    def post(self, message):
        print(message.format(name=self.name, age=self.age, username=self.username) 

instance = myClass("Robstersgaming", "50", "Rob")
instance.post("Hello {name} nice name!!! {age}, {username}")

If all the attributes of your class are valid targets for the formatting, you make the formatting call use message.format(**self.__dict__) to include them all automatically. It's fine if a format string doesn't use every attribute, unused keyword arguments will be ignored by str.format

Blckknght
  • 100,903
  • 11
  • 120
  • 169
1

There might be a more elegant way, but here's one way to do it:

  def post(self, message):
        print(message.format_map(locals()))

instance.post("Hello {self.name} nice name!!! {self.age}, {self.username}")

Then the print function passes the locals() dictionary to str.format_map() to substitute the local variables into the final string.

This is not so great because you need to know the internal attributes of the object and the name of the self argument to the post() method which is not required to be self at all.

mhawke
  • 84,695
  • 9
  • 117
  • 138
  • An alternative to using `locals()` with `format_map` might be to use regular `format` and pass `self=self` as the argument. – Blckknght Mar 07 '21 at 03:55
  • @Blckknght: yes, another good suggestion. Overall I think requiring `self` in the format string is undesirable and that your answer is a better solution. – mhawke Mar 07 '21 at 04:00
  • The perk of using `self` (or some other name, you don't need to use that one for your keyword) is that the format string can look up attributes that are really `property` descriptors, and do so lazily, only when needed. The two approaches I offered either loose the laziness (if you pass all the values explicitly (with e.g. `foo=self.foo`, which eagerly looks up the `foo` attribute even if there is no `{foo}` in the format string), or you can't use properties at all (with `**self.__dict__`). – Blckknght Mar 07 '21 at 04:07
0

Here's a very flexible way to do what you want. I admit that it's an extension of @BlckKnght's excellent answer. This answer goes further, by putting all of the potential values that can be expanded into a string into a single dict. By doing this, you can collect a whole bunch of stuff in a dict, and your users can pick and choose what values they want to use. You don't have to decide for every situation just what values you want to pass into a format statement for a particular situation.

Note that here, I added date and time to the dict, but in the example, I use the time but not the date. You can put 100 items in the dict, and it gets no harder to format individual strings, either for the user or for you. That's what's great about this method. You build the dict once, and then just pass it into the format string, instead of having to every time list all of the individual variables that the user might want to use in their string.

Note that in addition to providing the dict with your reusable values, you can still pass in values specific to the particular string you're formatting. Like here, where you pass in data as a unique variable just for the one example format call.

Here's your code, expanded to demonstrate this:

from datetime import datetime

def doSomethingVeryComplex():
    return "A Complex Result"

class MyClass:

  def __init__(self, name, age, username):
      self.vars = {
          'name': name,
          'age': age,
          'username': username,
          'date': datetime.today().strftime("%d/%m/%Y"),
          'time': datetime.today().strftime("%H:%M:%S"),
      }
      self.data = None
      self.result = None

  def post(self, message):
        self.data = doSomethingVeryComplex()
        print(message.format(**self.vars, data = self.data))

instance = MyClass("Robstersgaming", "50", "Rob")
instance.post("Hello {name} nice name!!! The time is: {time} - {age}, {username}, {data}")

Result:

Hello Robstersgaming nice name!!! The time is: 21:44:42 - 50, Rob, A Complex Result
CryptoFool
  • 21,719
  • 5
  • 26
  • 44