suggestions on passing a json as a function input - python

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.

Related

the inplace parameter in pandas how it works?

in pandas the inplace parameter make modification on the reference but I know in python data are sent by value not by reference i want to know how this is implemented or how this work
Python’s argument passing model is neither “Pass by Value” nor “Pass by Reference” but it is “Pass by Object Reference”
When you pass a dictionary to a function and modify that dictionary inside the function, the changes will reflect on the dictionary everywhere.
However, here we are dealing with something even less ambiguous. When passing inplace=True to a method call on a pandas object (be it a Series or a DataFrame), we are simply saying: change the current object instead of getting me a new one. Method calls can modify variables of the instances on which they were called - this is independent of whether a language is "call by value" or "call by reference". The only case in which this would get tricky is if a language only had constants (think val) and no variables (think var) - think purely functional languages. Then, it's true - you can only return new objects and can't modify any old ones. In practice, though, even in purest of languages you can find ways to update records in-place.

Python - equivalence of calling function with key-value arguments

Let's consider
foo(arg1=123, arg2=None) and foo(arg1=123).
Tell me please, if these two ways are equivalent ?
No, the two given function signatures (and hence functions) are not equivalent.
In foo(arg1=123, arg2=None), you have two arguments -- arg1 and arg2, which can be used inside the function as local names. Note that, assigning a value of None to some name does not make it anything special/different as far as the assignment statements are concerned. It is in fact a common way to give a placeholder value for a variable that is not mandatory or may be an empty mutable object.
On the other hand, foo(arg1=123) has only one argument arg1, which is available on the function's local scope for use.
Edit:
If you have a function defined as foo(arg1, arg2), both arguments are mandatory (positional) arguments.
So, foo(arg1=21) will throw a TypeError as you have not provided arg2. Whereas, foo(arg1=21, arg2=None) will work just fine as you have provided values for both the arguments.
Edit2:
If you have a function defined as foo(arg1=None, arg2=None) (or something similar i.e. with default values), both arguments are optional (keyword) arguments. In that case, both of the mentioned definitions would be the same.

Accepting integers as keys of **kwargs

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.

Why does PyCharm warn about mutable default arguments? How can I work around them?

I am using PyCharm (Python 3) to write a Python function which accepts a dictionary as an argument with attachment={}.
def put_object(self, parent_object, connection_name, **data):
...
def put_wall_post(self, message, attachment={}, profile_id="me"):
return self.put_object(profile_id, "feed", message=message, **attachment)
In the IDE, attachment={} is colored yellow. Moving the mouse over it shows a warning.
Default arguments value is mutable
This inspection detects when a mutable value as list or dictionary is
detected in a default value for an argument.
Default argument values are evaluated only once at function definition
time, which means that modifying the default value of the argument
will affect all subsequent calls of the function.
What does this mean and how can I resolve it?
If you don't alter the "mutable default argument" or pass it anywhere where it could be altered just ignore the message, because there is nothing to be "fixed".
In your case you only unpack (which does an implicit copy) the "mutable default argument" - so you're safe.
If you want to "remove that warning message" you could use None as default and set it to {} when it's None:
def put_wall_post(self,message,attachment=None,profile_id="me"):
if attachment is None:
attachment = {}
return self.put_object(profile_id,"feed",message = message,**attachment)
Just to explain the "what it means": Some types in Python are immutable (int, str, ...) others are mutable (like dict, set, list, ...). If you want to change immutable objects another object is created - but if you change mutable objects the object remains the same but it's contents are changed.
The tricky part is that class variables and default arguments are created when the function is loaded (and only once), that means that any changes to a "mutable default argument" or "mutable class variable" are permanent:
def func(key, value, a={}):
a[key] = value
return a
>>> print(func('a', 10)) # that's expected
{'a': 10}
>>> print(func('b', 20)) # that could be unexpected
{'b': 20, 'a': 10}
PyCharm probably shows this Warning because it's easy to get it wrong by accident (see for example Why do mutable default arguments remember mutations between function calls? and all linked questions). However, if you did it on purpose (Good uses for mutable function argument default values?) the Warning could be annoying.
You can replace mutable default arguments with None. Then check inside the function and assign the default:
def put_wall_post(self, message, attachment=None, profile_id="me"):
attachment = attachment if attachment else {}
return self.put_object(profile_id, "feed", message=message, **attachment)
This works because None evaluates to False so we then assign an empty dictionary.
In general you may want to explicitly check for None as other values could also evaluate to False, e.g. 0, '', set(), [], etc, are all False-y. If your default isn't 0 and is 5 for example, then you wouldn't want to stomp on 0 being passed as a valid parameter:
def function(param=None):
param = 5 if param is None else param
This is a warning from the interpreter that because your default argument is mutable, you might end up changing the default if you modify it in-place, which could lead to unexpected results in some cases. The default argument is really just a reference to the object you indicate, so much like when you alias a list to two different identifiers, e.g.,
>>> a={}
>>> b=a
>>> b['foo']='bar'
>>> a
{'foo': 'bar'}
if the object is changed through any reference, whether during that call to the function, a separate call, or even outside the function, it will affect future calls the function. If you're not expecting the behavior of the function to change at runtime, this could be a cause for bugs. Every time the function is called, it's the same name being bound to the same object. (in fact, I'm not sure if it even goes through the whole name binding process each time? I think it just gets another reference.)
The (likely unwanted) behavior
You can see the effect of this by declaring the following and calling it a few times:
>>> def mutable_default_arg (something = {'foo':1}):
something['foo'] += 1
print (something)
>>> mutable_default_arg()
{'foo': 2}
>>> mutable_default_arg()
{'foo': 3}
Wait, what? yes, because the object referenced by the argument doesn't change between calls, changing one of its elements changes the default. If you use an immutable type, you don't have to worry about this because it shouldn't be possible, under standard circumstances, to change an immutable's data. I don't know if this holds for user-defined classes, but that is why this is usually just addressed with "None" (that, and you only need it as a placeholder, nothing more. Why spend the extra RAM on something more complicated?)
Duct-taped problems...
In your case, you were saved by an implicit copy, as another answer pointed out, but it's never a good idea to rely on implicit behavior, especially unexpected implicit behavior, since it could change. That's why we say "explicit is better than implicit". Besides which, implicit behavior tends to hide what's going on, which could lead you or another programmer to removing the duct tape.
...with simple (permanent) solutions
You can avoid this bug magnet completely and satisfy the warning by, as others have suggested, using an immutable type such as None, checking for it at the start of the function, and if found, immediately replacing it before your function gets going:
def put_wall_post(self, message, attachment=None, profile_id="me"):
if attachment is None:
attachment = {}
return self.put_object(profile_id, "feed", message=message, **attachment)
Since immutable types force you to replace them (Technically, you are binding a new object to the same name. In the above, the reference to None is overwritten when attachment is rebound to the new empty dictionary) instead of updating them, you know attachment will always start as None unless specified in the call parameters, thus avoiding the risk of unexpected changes to the default.
(As an aside, when in doubt whether an object is the same as another object, compare them with is or check id(object). The former can check whether two references refer to the same object, and the latter can be useful for debugging by printing a unique identifier—typically the memory location—for the object.)
To rephrase the warning: every call to this function, if it uses the default, will use the same object. So long as you never change that object, the fact that it is mutable won't matter. But if you do change it, then subsequent calls will start with the modified value, which is probably not what you want.
One solution to avoid this issue would be to have the default be a immutable type like None, and set the parameter to {} if that default is used:
def put_wall_post(self,message,attachment=None,profile_id="me"):
if attachment==None:
attachment={}
return self.put_object(profile_id,"feed",message = message,**attachment)
Lists are mutable and as declaring default with def at declaration at compile time will assign a mutable list to the variable at some address
def abc(a=[]):
a.append(2)
print(a)
abc() #prints [2]
abc() #prints [2, 2] as mutable thus changed the same assigned list at func delaration points to same address and append at the end
abc([4]) #prints [4, 2] because new list is passed at a new address
abc() #prints [2, 2, 2] took same assigned list and append at the end
 
To correct this:
def abc(a=None):
if not a:
a=[]
a.append(2)
print(a)
 
This works as every time a new list is created and not referencing the old list as a value always null thus assigning new list at new address

Parameter vs Arguments ? finally,what are they?

I am a beginner in python programming and recently i came across functions,parameters,arguments and...
I have done a lot of research on Parameters and Arguments(Even checked the answers of similar past questions on StackOverflow)but i couldn't get their meanings.
Some say,parameters are variables which we give them to functions while we are defining them and arguments are values that are passed in function once we given them to the function in order to run the function.While some other say no,it's not like that.Parameters and Arguments are same and do the same task...
Can anyone tell me the meaning Parameters and Arguments in a clear way?
Are Parameters and Arguments considered variables?
For what kind of purpose do we use them?
Please don't explain too much complicated,i am a beginner.
Thank you so much.
Per the official documentation:
Parameters are defined by the names that appear in a function definition, whereas arguments are the values actually passed to a function when calling it. Parameters define what types of arguments a function can accept. For example, given the function definition:
def func(foo, bar=None, **kwargs):
pass
foo, bar and kwargs are parameters of func. However, when calling func, for example:
func(42, bar=314, extra=somevar)
the values 42, 314, and somevar are arguments.
The glossary defines them as:
Argument: A value passed to a function (or method) when calling the function.
Parameter: A named entity in a function (or method) definition that specifies an argument (or in some cases, arguments) that the function can accept.
Python doesn't really have "variables" like some other languages - it has "names" referring to "objects". See e.g. "Code like a Pythonista" and "Facts and myths about Python names and values".
Take it this way:
Parameter:
A parameter represents a value that the procedure expects you to pass when you call it. The procedure's declaration defines its parameters.
Argument:
An argument represents the value that you pass to a procedure parameter when you call the procedure. The calling code supplies the arguments when it calls the procedure.
Example:
int add (int value1, int value2) // Here value1 and value2 are PARAMETERS.
{
return value1+value2;
}
Now while calling the function
answer = add(2,3); // Here values 2 and 3 are ARGUMENTS.
Same goes with Python, while declaration, they are parameters, while calling they are arguments.
Some may differ with what i have written, but this is how it is actually known in programming world.

Categories

Resources