Accepting integers as keys of **kwargs - python

Keywords have to be strings
>>> def foo(**kwargs):
... pass
...
>>> foo(**{0:0})
TypeError: foo() keywords must be strings
But by some black magic, namespaces are able to bypass that
>>> from types import SimpleNamespace
>>> SimpleNamespace(**{0:0})
namespace()
Why? And how? Could you implement a Python function that can receive integers in the kwargs mapping?

Could you implement a Python function that can receive integers in the kwargs mapping?
No, you can't. The Python evaluation loop handles calling functions defined in Python code differently from calling a callable object defined in C code. The Python evaluation loop code that handles keyword argument expansion has firmly closed the door on non-string keyword arguments.
But SimpleNamespace is not a Python-defined callable, it is defined entirely in C code. It accepts keyword arguments directly, without any validation, which is why you can pass in a dictionary with non-string keyword arguments.
That's perhaps a bug; you are supposed to use the C-API argument parsing functions, which all do guard against non-string keyword arguments. SimpleNamespace was initially designed just as a container for the sys.implementation data*, and wasn't really designed for other uses.
There might be other such exceptions, but they'll all be C-defined callables, not Python functions.
* The time.get_clock_info() method also runs an instance of the SimpleNamespace class; it's the only other place that the type is currently used.

SimpleNamespace now rejects integer keyword keys. As Martijn supposed, the original behavior was a bug. It seems that it was fixed by bpo-31655: Validate keyword names in SimpleNamespace constructor in v3.9.0b2, and then backported to 3.6.

No, kwargs cannot be integers. This answer, however, is designed as a (very) short history lesson rather than technical answer (for that, please see #MartijnPierter's answer).
The check was originally added in 2010, in issue 8419 (commit fb88636199c12f63d6c8c89f311cdafc91f30d2f) for Python 3.2 (and I believe Python 2.7.4 as well, but don't quote me on that), and simply checked that call kwargs were strings (and raised a value error if they weren't). It also added PyArg_ValidateKeywordArguments to C-api, which simply performed the above check.
In 2017, issue 29951 changed the error text for Python 3.7 from "keyword arguments must be strings" to "keywords must be strings" in PR 916 (commit 64c8f705c0121a4b45ca2c3bc7b47b282e9efcd8). The error remained a ValueError and it did not change the behaviour in any way, and was simply a small change to the error descriptor.

Related

TypeError: 'x' is an invalid keyword argument for int() [duplicate]

I came across this - in my view - strange behaviour:
"a b c".split(maxsplit=1)
TypeError: split() takes no keyword arguments
Why does str.split() not take keyword arguments, even though it would make sense? I found this behavior both in Python2 and Python3.
See this bug and its superseder.
str.split() is a native function in CPython, and as such exhibits the behavior described here:
CPython implementation detail: An implementation may provide built-in
functions whose positional parameters do not have names, even if they
are ‘named’ for the purpose of documentation, and which therefore
cannot be supplied by keyword. In CPython, this is the case for
functions implemented in C that use PyArg_ParseTuple() to parse their
arguments.
str.split is a builtin method implemented in C. Unfortunately some builtin functions/methods do not accept keyword arguments. See this bug report.

suggestions on passing a json as a function input

I am planning to write a routine to achieve below functionality: According to the "method", and "before" and "after", compute the expense. Not sure if passing a json as an input of the function is a good practice? Does it mean the function is trying to achieve too much? When I assign a default value to "target", Pycharm gives warning "mutable object as default argument".
def assign_expense(target={'fly':{'before':'US', 'after':'JP'}, 'walk':{'before':'blockA', 'after':'blockB'})
method, before, after = abstract_param_from_json(target)
if method='fly':
if before=='US', after=='JP':
func_fly_US_JP
elif before=='MXN', after=='CAD':
func_fly_MX_CAD
if method='walk':
if before=='blockA', after=='blockB':
func_A_to_B
if before=='blockC', after=='blockZ':
func_C_to_Z
Regarding your warning mutable object as default argument
Please refer the following
https://docs.quantifiedcode.com/python-anti-patterns/correctness/mutable_default_value_as_argument.html
"Least Astonishment" and the Mutable Default Argument
Mutable objects as default arguments behave like a static variable
And regarding passing JSON as function input, I don't think it is a bad practice by itself unless the function is doing many things on the JSON.
Ex: If you need to delete/process a particular set of keys in the JSON, you need to pass JSON to the function that does the job.

Why method accepts class name and name 'object' as an argument?

Consider the following code, I expected it to generate error. But it worked. mydef1(self) should only be invoked with instance of MyClass1 as an argument, but it is accepting MyClass1 as well as rather vague object as instance.
Can someone explain why mydef is accepting class name(MyClass1) and object as argument?
class MyClass1:
def mydef1(self):
return "Hello"
print(MyClass1.mydef1(MyClass1))
print(MyClass1.mydef1(object))
Output
Hello
Hello
There are several parts to the answer to your question because your question signals confusion about a few different aspects of Python.
First, type names are not special in Python. They're just another variable. You can even do something like object = 5 and cause all kinds of confusion.
Secondly, the self parameter is just that, a parameter. When you say MyClass1.mydef1 you're asking for the value of the variable with the name mydef1 inside the variable (that's a module, or class, or something else that defines the __getattr__ method) MyClass1. You get back a function that takes one argument.
If you had done this:
aVar = MyClass1()
aVar.mydef1(object)
it would've failed. When Python gets a method from an instance of a class, the instance's __getattr__ method has special magic to bind the first argument to the same object the method was retrieved from. It then returns the bound method, which now takes one less argument.
I would recommend fiddling around in the interpreter and type in your MyClass1 definition, then type in MyClass1.mydef1 and aVar = MyClass1(); aVar.mydef1 and observe the difference in the results.
If you come from a language like C++ or Java, this can all seem very confusing. But, it's actually a very regular and logical structure. Everything works the same way.
Also, as people have pointed out, names have no type associated with them. The type is associated with the object the name references. So any name can reference any kind of thing. This is also referred to as 'dynamic typing'. Python is dynamically typed in another way as well. You can actually mess around with the internal structure of something and change the type of an object as well. This is fairly deep magic, and I wouldn't suggest doing it until you know what you're doing. And even then you shouldn't do it as it will just confuse everybody else.
Python is dynamically typed, so it doesn't care what gets passed. It only cares that the single required parameter gets an argument as a value. Once inside the function, you never use self, so it doesn't matter what the argument was; you can't misuse what you don't use in the first place.
This question only arises because you are taking the uncommon action of running an instance method as an unbound method with an explicit argument, rather than invoking it on an instance of the class and letting the Python runtime system take care of passing that instance as the first argument to mydef1: MyClass().mydef1() == MyClass.mydef1(MyClass()).
Python is not a statically-typed language, so you can pass to any function any objects of any data types as long as you pass in the right number of parameters, and the self argument in a class method is no different from arguments in any other function.
There is no problem with that whatsoever - self is an object like any other and may be used in any context where object of its type/behavior would be welcome.
Python - Is it okay to pass self to an external function

Python 3 built-in functions and classes

When I am creating a list using below statement
a = list('jane')
Am I calling Python's built-in list function or instantiating list class.
My understanding is we are instantiating list class by passing 'jane' as argument.
However, the Python's documentation https://docs.python.org/3/library/functions.html says list() is built-in function.
The docs explicitly say:
class list([iterable])
Rather than being a function, list is actually a mutable sequence type
You can easily check that:
>>> type(list)
type
if it was a function, function would be the output provided by using type.
You're instantiating a list object the same way you'd do if you created your own class and called it. type's __call__ is essentially getting invoked and sets up your instance so, though they aren't a function per se, they are callable.
The fact that they are listed in that specific section is probably for convenience, it might be confusing but reading the description of it is supposed to disambiguate this.
Your question is answered by the very documentation page you mention:
class list([iterable])
Rather than being a function, list is actually a mutable sequence type, as documented in Lists and Sequence Types — list, tuple, range.
In Python, both classes and functions are callable, so in practice, you can treat them alike.
You are instantiating a list.
class list([iterable])
Rather than being a function, list is actually a mutable sequence type, as documented in Lists and Sequence Types — list, tuple, range.

Why does str.split not take keyword arguments?

I came across this - in my view - strange behaviour:
"a b c".split(maxsplit=1)
TypeError: split() takes no keyword arguments
Why does str.split() not take keyword arguments, even though it would make sense? I found this behavior both in Python2 and Python3.
See this bug and its superseder.
str.split() is a native function in CPython, and as such exhibits the behavior described here:
CPython implementation detail: An implementation may provide built-in
functions whose positional parameters do not have names, even if they
are ‘named’ for the purpose of documentation, and which therefore
cannot be supplied by keyword. In CPython, this is the case for
functions implemented in C that use PyArg_ParseTuple() to parse their
arguments.
str.split is a builtin method implemented in C. Unfortunately some builtin functions/methods do not accept keyword arguments. See this bug report.

Categories

Resources