Format **kwargs to f-string - python

I am trying to format keyword arguments passed to a function, in such a way that the string output (has to be f-string in this case) would look like arg1=arg1_value, arg2=arg2_value,...
Apparently it is not possible to just do:
out = f'{**kwargs}'
so I have managed to do it in this way:
def to_string(**kwargs):
return ', '.join([f'{k}={v}' for k,v in kwargs.items()])
> to_string(a=1, b=2)
'a=1, b=2'
Is there a way to do this more simply?

You can unpack an iterable (cf [another question]) but not a dictionnary.
Because you want the exact same output as f"{a=}, {b=}, {c=}" you will not find a solution much shorter than what you already have.

Related

Python 3: How to create a list to print its every element on a new line - without loops or sep = "\n"

Let's say I have a list 'mylist'. For example, I prepared it like this:
mylist = ['AB', 'CD', 'EF']
I need to print out each element of 'mylist' on a new line.
However, I am not allowed to use loops (as in 'for i in mylist...') and I can't say:
print(*mylist, sep = "\n")
Although the last command gives exactly the output I need.
No, I have to somehow create/prepare 'mylist' in such a way that the desired print output is achieved based on the following command:
print(mylist)
Is it even possible?
Notice, it's not a duplicate post - it's more about how to format the list to begin with, not about printing per se.
Really simple:
print ("\n".join(mylist))
The method str.join uses the string to join an iterable.
The standard list type has a number of built-in behaviors, including a representation with brackets around the list and commas between (reprs of) the items. There's no way to get the behavior you desire, from the statement you specify (print(mylist)), and use the standard list type.
You may define a class MyList whose __str__() method generates the desired format:
class MyList(list):
def __str__(self):
return "\n".join(self)
mylist = MyList(['AB', 'CD', 'EF'])
print(mylist)
Or you may redefine print():
def print(*args, print=print, **kwargs):
if type(args[0]) is list and len(args) == 1:
args = ["\n".join(args[0])]
print(*args, **kwargs)
Either way is just hiding the code away somewhere else in order to stick to some arbitrary rule about what you're allowed to write. If you're going to do that, in a class is probably the more acceptable spot to put it.
You can print each element separated too on a new line such as:
print(mylist[0]), print(mylist[1])
I found the solution.
'mylist' shouldn't be a list, it should be created as a string like this - then it prints as desired:
mylist = 'AB\nCD\nEF'
print(mylist)

Python: Sorting a list of classes

I have a list called columnVariable. This list contains a bunch of instances of a class. Each class has a property sequenceNumber
when I run the following command:
for obj in columnVariable:
print obj.sequenceNumber
I get the following output
3
1
2
10
11
I'd like to sort the list by the property sequenceNumber. The output I like is:
1
2
3
10
11
When I try the following:
for obj in columnVariable:
print sorted(obj.sequenceNumber)
I get the output
[u'3']
[u'1']
[u'2']
[u'0', u'1']
[u'1', u'1']
It looks like each individual sequence number is being sorted instead of sorting the items in the list based on the sequence number.
I'm newer to python and so a little help would be appreciated. Thanks!
you may want to try this:
print sorted(columnVariable, key = lambda x: int(x.sequenceNumber))
You should use sorted with a key argument. eg.
from operator import attrgetter
for obj in sorted(columnVariable, key=attrgetter('sequenceNumber')):
print(obj)
Edit:
Since you want to sort strings numerically, it's more appropriate to use a lambda function here
for obj in sorted(columnVariable, key=lambda x: int(x.sequenceNumber)):
print(obj)
Some people struggle to understand lambda functions, so I'll mention that it's ok to write a normal function definition for your key function
def int_sequence_number_key(obj):
return int(obj.sequenceNumber)
for obj in sorted(columnVariable, key=int_sequence_number_key):
print(obj)
Now it's also possible to write tests for int_sequence_number_key which is important for covering corner cases (eg what happens if you can have None or some other objects that can't be converted to int)
The python sorted function takes a key function as an argument, which can be used to specify how to sort. In this case, you would use sorted(columnVariable, key=lambda x:x.sequenceNumber).
The above code can be made faster by using the operator module:
from operator import attrgetter
sorted(columnVariable, key=attergetter("sequenceNumber")).

variadic / arbitrary number of *args or ** kwargs in str.format() in Python

I'm making a socket client and need to define a function which sends the following kind of message to server: sometext,[name_1],[name_2],...,[name_n]. Actually the message is more complex, but for the sake of an example I simplified it.
Where:
... - represents the rest of the names (comma delimited) between name_2 and the last name_n
there could be arbitrary number of names each time
I know there are *args and **kwargs, but how to correctly use them in this case with str.format()? I need something like this:
def send_names(*args):
print('sometext,{*args},'.format(*args)) # as an example we just print
I know this code doesn't work because {*args} is illegal here.
The code works when I know the number of *args beforehand, like in here:
def send_names(*args):
print('sometext,{0},{1},{2}'.format(*args)) # as an example we just print
What am I missing?
You can join the *args to accomplish what you want:
def send_names(*args):
print('sometext, {0}'.format(', '.join(args)))
send_names('message1', 'message2', 'message3')
result:
sometext, message1, message2, message3
You cannot use *args or **kwargs to apply to a variable number of slots, no. You'd have to create the slots yourself based on the length:
','.join(['{}'] * len(args)).format(*args)
You can then interpolate the result of that into another template as needed. The above works with any type of argument normally accepted by a formatting slot.
Demo:
>>> args = ('foo', 'bar')
>>> ','.join(['{}'] * len(args)).format(*args)
'foo,bar'
>>> args = ('foo', 'bar', 'baz')
>>> ','.join(['{}'] * len(args)).format(*args)
'foo,bar,baz'
>>> args = (1, 2, 3)
>>> ','.join(['{}'] * len(args)).format(*args)
'1,2,3'
I think you should avoid using .format and just ', '.join the args as needed. You didn't mention any specific reason why you need to .format the strings in the question. Just like the solution by #Selcuk

Use an object (e.g., an Enum) as a key in **kwargs?

Very simple question from a Python newbie:
My understanding is that the keys in a dict are able to be just about any immutable data type. Is it possible to pass an immutable object (e.g., a member of an enum class) as a key in the **kwargs dictionary for a function or a class? I have tried it and the answer seems to be "no":
from enum import Enum
class MyEnum(Enum):
X= 'X'
Y= 'Y'
def func(*args,**kwargs):
pass
func(MyEnum.X = 1)
Output:
"SyntaxError: keyword can't be an expression"
However, there may be something I am missing.
EDIT: Note that I am not trying to make the key equal to MyEnum.X.value (which is a string in this case); I want the key to be the actual Enum object, e.g. MyEnum.X.
You're doing:
func(MyEnum.X = 1)
Here, the problem is MyEnum.X = 1 -- Your keyword (MyEnum.X) is actually an expression (getattr(MyEnum, 'X')), and expressions can't be used as keywords in function calls. In fact, only identifiers can be used as keywords.
To get your call to work, you'll need to use dictionary unpacking like this:
func(**{MyEnum.X.name: 1})
Note, to get the name of the attribute, I needed to do MyEnum.X.name or MyEnum.X.value, depending on how you set up your enum -- In your case, I they are the same thing.
>>> from enum import Enum
>>> class Foo(Enum):
... X = 'X'
...
>>> Foo.X.value
'X'
>>> Foo.X.name
'X'
This won't work, because of the way keyword arguments are being processed. The documentation says:
[...] Next, for each keyword argument, the identifier is used to determine the corresponding slot (if the identifier is the same as the first formal parameter name, the first slot is used, and so on) [...]
So there must be a way to match the key from the dictionary to the formal parameter name. The exception:
keywords must be strings
when you try to pass something that's not a string:
func(**{MyEnum.X: 1})
suggest the simplest case is required: keys must be strings.
A possible workaround is to make implicit things explicit: just create a class that contains all the necessary information you want to pass in its attributes and pass it. The code will surely be more readable.
The answer to my original question is indeed "no". However, thanks to the input from mgilson and BartoszKP and others, the following work around I came up with is not a bad solution, and solves my current problem. I offer it for others to look at who are trying to do something similar:
from enum import Enum
class MyEnum(Enum):
X= 'X'
Y= 'Y'
def func(*args,**kwargs):
#replace kwargs with kwargsNew
kwargsNew = {}
for kwkey, kwvalue in kwargs.items():
try: kwargsNew[MyEnum(kwkey)] = kwvalue
except ValueError: kwargsNew[kwkey] = kwvalue
doStuffWithKwargs(kwargsNew)
def doStuffWithKwargs(k):
for K in k:
print(K)
#Pass the name X or Y as the key;
#all other keys not found in `MyEnum` are treated normally
func(X = 1, Y = 2, Z = 3)
Output:
Z
MyEnum.X
MyEnum.Y
(no errors)
Do you actually want to create an instnace of MyEnum?
myenum = MyEnum()
func(myenum.X = 1)
One alternative I have found is to pass a dict into *args instead of **kwargs, or to assign a dict to kwargs[0] directly:
func({MyEnum.X: 1})
func(kwargs = {MyEnum.X: 1})
(No errors produced)
However, I really don't like either of these methods.
EDIT: See my second answer for a much better solution.

Count number of strings

First of all, I do realize that this is a really simple question and please bear with me on this.
How, in python, can I get the numbers of strings? I am trying to do something like this:
def func(input_strings):
# Make the input_strings iterable
if len(input_strings) == 1:
input_strings = (input_strings, )
# Do something
for input_string in input_strings:
analyze_string(input_string)
return
So with this function, if the input is a list, ['string1', 'string2', 'string3'], it will loop over them; if the input is only one string like 'string1', then it will still take care of it, instead of throwing an exception.
However, the len(str) returns the number of characters in the string and wouldn't give me a "1".
I'd really appreciate your help!
Use isinstance to check whether a given value is string:
>>> isinstance('a-string', str)
True
>>> isinstance(['a-string', 'another-string'], str)
False
def func(input_strings):
# Make the input_strings iterable
if isinstance(input_strings, str):
input_strings = (input_strings, )
# Do something
for input_string in input_strings:
analyze_string(input_string)
return
Python 2.x note (Python 2.3+)
Use isinstance('a-string', basestring) if you want to also test unicode. (basestring is the superclass for str and unicode).
I'd suggest using *args to allow the function to accept any number of strings.
def func(*input_strings):
for input_string in input_strings:
analyze_string(input_string)
func("one string")
func("lots", "of", "strings")
If you then want the number of strings, you can simple use len(input_strings).
Have a look at these answers.

Categories

Resources