String format method. Dictionaries - python

I am learning how to use the string format method, and I've found something I don't understand. When I do:
>>> s = "My {comp[b]}"
>>> s.format(comp = {'a': 'laptop', 'b': 'desktop'})
'My desktop'
I get the expected results. But when I try to define the dictionary out of the method, and just used the name inside:
>>> comp = {'a': 'laptop', 'b': 'desktop'}
>>> s = "My {comp[b]}"
>>> s.format(comp)
I get KeyError: 'comp'. Why?

In your second example you're not naming the parameter that you're passing to format. Your final line should be:
>>> s.format(comp=comp)

format differentiates the use between positional and named arguments.
By using the name of the variable inside the format string, you are required to give a named argument with that specific name.
In the first you are because you are calling "".format(<name>=<var>) while in the second case your are just giving a positional argument (position 0) that is filled by the comp dictionary.
If you have read the documentation you have noticed that format can use positional arguments in this way: "Hello {0}!".format(<arg0>).
In your second case, instead of giving the required name argument comp you are giving the position argument 0.

Related

Create dict object using space in key name

Is there a way to create a dict object with a space in the key?
# This way works
>>> d = {'a b': 1}
>>> d
{'a b': 1}
# Is it possible to create the same using this method?
>>> d = dict('a b'=1)
File "<stdin>", line 1
SyntaxError: expression cannot contain assignment, perhaps you meant "=="?
No, this manner of constructing a dictionary cannot handle a key with a space.
https://docs.python.org/3/library/stdtypes.html#dict shows numerous methods for constructing dictionaries. The first one is what you're trying to do:
a = dict(one=1, two=2, three=3)
But the following note says:
Providing keyword arguments as in the first example only works for
keys that are valid Python identifiers.
A string value, as you're trying, is not a valid identifier. And an identifier cannot includes spaces, so a b without quotes will not work either.

converting dict to typing.NamedTuple in python

For most, I am not sure if it's the right question to be asked but I couldn't yet found out why there are two different types of named tuple...
I have read " What's the difference between namedtuple and NamedTuple?" page.
However, I still don't understand how to convert a dictionary to a NamedTuple.
I have tried this code :
from collections import namedtuple
def convert(dictionary):
return namedtuple('GenericDict', dictionary.keys())(**dictionary)
however, this piece of code only converts the dict to a namedtuple from the collection module.
I was wondering if anyone can help me out on this.
How should I make a function to transform any random dict into a typing.NamedTuple.
Assume we have a class of NamedTuple like this :
class settingdefault(NamedTuple):
epoch : int = 8
train_size : float = 0.8
b: str = "doe"
and we just want to get an input of dict from the user and transform it to NamedTuple. So if there was an element missing it can get replaced by the settingdefault class.
and lets assume that the example dict is :
config = dict(e=10, b="test")
BTW, I want it to be like a function. other than that I know how to do it like :
setting = settingdefault(config['a'], config['b'])
I want to be able to have it for cases that I don't know the keys of the coming config dict as it can be anything.
Once again for the clarification ! My question is about typing.NamedTuple not the collections.namedtuple .
In this case, it's probably easiest to use typing's own metaclass for NamedTuple to create the class. The tricky part is that typing.NamedTuples need to have types associated for the fields.
Your conversion functions will end up looking something like this:
def convert(class_specs):
field_types = {field: type(value) for field, value in class_specs.items()}
return typing.NamedTupleMeta(
'GenericDict', [], dict(class_specs, __annotations__=field_types))
and like your example you can use it as follows:
>>> Coordinates = convert({'x': 1, 'y': 23})
>>> Coordinates()
GenericDict(x=1, y=23)
>>> Coordinates(0, 0)
GenericDict(x=0, y=0)
Getting rid of the defaults
If you don't want to use the values from the dictionary as defaults, you can simply directly use the NamedTuple constructor like so:
def convert(class_specs):
return typing.NamedTuple('GenericDict',
[(field, type(value)) for field, value in class_specs.items()])
>>> Coordinates = convert({'x': 42, 'y': 21})
>>> Coordinates()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: __new__() missing 2 required positional arguments: 'x' and 'y'
>>> Coordinates(x=1, y=2)
GenericDict(x=1, y=2)
Getting rid of defaults and type information
If you don't want the defaults or typing information, you can simply switch it out for the generic typing.Any using:
def convert(class_specs):
return typing.NamedTuple('GenericDict',
[(field, typing.Any) for field in class_specs.keys()])

How can we pass bytes as the key of keyword arguments to functions?

Let us have the following example:
def fun(**args):
print(str(args))
dic = {'name': 'Pulkit'}
fun(**dic)
This code works fine and I have the following output:
{'name': 'Pulkit'}
Now lets pass the value as bytes:
dic_vb = {'name': b'Pulkit'}
fun(**dic_vb)
This also works fine and have the following output:
{'name': b'Pulkit'}
But things change when I try to have the key as bytes:
dic_kb = {b'name': 'Pulkit'}
This results in TypeError saying:
TypeError: fun() keywords must be strings
Is there any way we can pass bytes as keyword arguments. I also checked CPython code at repo which deals with keyword arguments and seems like we can't pass. Is there any workaround or do I need to make sure unicodes are passed.
I am dealing with a codebase where we have lot of such instances on Python 2 and need to be ported to Python 3. So is only way possible is to convert all of the keyword arguments to unicodes?
No, keywords need to be valid identifiers, it is the same reason why you cannot provide dictionaries that have numbers as keys to a function expecting **kwargs:
>>> dic = {1: 'Pulkit'}
>>> fun(**dic)
TypeErrorTraceback (most recent call last)
<ipython-input-54-83c29ed0f08e> in <module>()
3
4 dic = {1: 'Pulkit'}
----> 5 fun(**dic)
TypeError: fun() keywords must be strings
That is also what you saw in the source, with the negation (!) of PyUnicode_Check acting as the enforcer.
It is required; you need to provide keys that are able to act as names. To see what is and what is not allowed, just try assigning it to a name in the interactive interpreter and see what errs:
>>> b'my_list' = [...] # err
>>> 23 = [21, 22] # err
A viable (and sole, if decode is not allowed) option is passing the dictionary as just another argument and then accessing the values through with normal dictionary look-ups.
Of course, if decoding the bytes to strings before invoking the function is a viable alternative I would suggest you pursue that option:
>>> dic = {b'name': 'Pulkit'}
>>> fun(**{b.decode(): v for b, v in dic.items()})

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.

Using append() and readline for completion in python

Specifically can I provide append() a Null/None value in Python?
I am trying to add auto complete functionality to a command line application, so am using readline to obtain anything the user may have typed at the raw_input prompt.
I'm getting an issue when I try to tab (with no value entered into the console) and get this message: "append() takes exactly one argument (0 given)"
Here is the code:
tokens = readline.get_line_buffer().split()
if not tokens or readline.get_line_buffer()[-1] == ' ':
tokens.append()
I'm using the example provided here because of the traverse function where the depth of the tree isn't an issue:
https://www.ironalbatross.net/wiki/index.php5?title=Python_Readline_Completions#Complex_problem_.28Regular_Grammar.29
tokens variable is a list, so lists method append really takes exactly one argument.
>>> a = []
>>> a
>>> []
>>> a.append(1)
>>> a
>>> [1]
>>> a.append()
>>> TypeError: append() takes exactly one argument (0 given)
>>> a.append(None)
>>> a
>>> [1, None]
append require exactly one argument
None object can't invoke append function
OK I managed to fix it... wasn't sure what value to provide append() when there was no value returned by readline so did this and it worked:
def complete(self,text,state):
try:
tokens = readline.get_line_buffer().split()
if not tokens or readline.get_line_buffer()[-1] == ' ':
tokens.append(text)
Thanks guys!

Categories

Resources