How to tell a function to use the default argument values?Get a function argument's default value?Conditionally passing arbitrary number of default named arguments to a functionWhy does initializing a variable via a python default variable keep state across object instantiation?Using default arguments in a function with variable arguments. Is this possible?How to merge two dictionaries in a single expression?How do I check if a list is empty?How do I check whether a file exists without exceptions?How can I safely create a nested directory in Python?Using global variables in a functionHow do I sort a dictionary by value?How to make a chain of function decorators?“Least Astonishment” and the Mutable Default ArgumentHow do I list all files of a directory?How to access environment variable values?

Arthur Somervell: 1000 Exercises - Meaning of this notation

In Japanese, what’s the difference between “Tonari ni” (となりに) and “Tsugi” (つぎ)? When would you use one over the other?

How can bays and straits be determined in a procedurally generated map?

Why doesn't Newton's third law mean a person bounces back to where they started when they hit the ground?

Smoothness of finite-dimensional functional calculus

How does strength of boric acid solution increase in presence of salicylic acid?

Why is consensus so controversial in Britain?

Watching something be written to a file live with tail

tikz: show 0 at the axis origin

Why are electrically insulating heatsinks so rare? Is it just cost?

Pattern match does not work in bash script

Is a conference paper whose proceedings will be published in IEEE Xplore counted as a publication?

Mage Armor with Defense fighting style (for Adventurers League bladeslinger)

How is it possible to have an ability score that is less than 3?

What defenses are there against being summoned by the Gate spell?

Why "Having chlorophyll without photosynthesis is actually very dangerous" and "like living with a bomb"?

Is it possible to do 50 km distance without any previous training?

How much RAM could one put in a typical 80386 setup?

How to find program name(s) of an installed package?

How can I make my BBEG immortal short of making them a Lich or Vampire?

Test if tikzmark exists on same page

Is a tag line useful on a cover?

Do VLANs within a subnet need to have their own subnet for router on a stick?

What do you call a Matrix-like slowdown and camera movement effect?



How to tell a function to use the default argument values?


Get a function argument's default value?Conditionally passing arbitrary number of default named arguments to a functionWhy does initializing a variable via a python default variable keep state across object instantiation?Using default arguments in a function with variable arguments. Is this possible?How to merge two dictionaries in a single expression?How do I check if a list is empty?How do I check whether a file exists without exceptions?How can I safely create a nested directory in Python?Using global variables in a functionHow do I sort a dictionary by value?How to make a chain of function decorators?“Least Astonishment” and the Mutable Default ArgumentHow do I list all files of a directory?How to access environment variable values?






.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty,.everyoneloves__bot-mid-leaderboard:empty height:90px;width:728px;box-sizing:border-box;








21















I have a function foo that calls math.isclose:



import math
def foo(..., rtol=None, atol=None):
...
if math.isclose(x, y, rel_tol=rtol, abs_tol=atol):
...
...


The above fails in math.isclose if I do not pass rtol and atol to foo:



TypeError: must be real number, not NoneType


I do not want to put the system default argument values in my code (what
if they change down the road?)



Here is what I came up with so far:



import math
def foo(..., rtol=None, atol=None):
...
tols =
if rtol is not None:
tols["rel_tol"] = rtol
if atol is not None:
tols["abs_tol"] = atol
if math.isclose(x, y, **tols):
...
...


This looks long and silly and allocates a dict on each invocation of
foo (which calls itself recursively so this is a big deal).



So, what is the best way to tell math.isclose to use the default
tolerances?



PS. There are several related questions. Note that I do not want to know the actual default arguments of math.isclose - all I want it to tell it to use the defaults whatever they are.










share|improve this question



















  • 1





    you can grab the values of the default args: Get a function argument's default value? and come up with a way to define your function using those values. There's probably a way using functools.partial

    – pault
    Apr 3 at 14:46











  • possible duplicate of stackoverflow.com/questions/4670665/… but you will not find better answer in it.

    – naivepredictor
    Apr 3 at 14:49






  • 2





    @pault I tried inspect.signature(math.isclose) and it fails with ValueError: no signature found for builtin <built-in function isclose>.

    – Sanyash
    Apr 3 at 14:50











  • @Sanyash It works for me in python 3.7.2.

    – Aran-Fey
    Apr 3 at 14:53











  • @Aran-Fey indeed, in 3.7.2 it works, but not in 3.6.6

    – Sanyash
    Apr 3 at 14:54

















21















I have a function foo that calls math.isclose:



import math
def foo(..., rtol=None, atol=None):
...
if math.isclose(x, y, rel_tol=rtol, abs_tol=atol):
...
...


The above fails in math.isclose if I do not pass rtol and atol to foo:



TypeError: must be real number, not NoneType


I do not want to put the system default argument values in my code (what
if they change down the road?)



Here is what I came up with so far:



import math
def foo(..., rtol=None, atol=None):
...
tols =
if rtol is not None:
tols["rel_tol"] = rtol
if atol is not None:
tols["abs_tol"] = atol
if math.isclose(x, y, **tols):
...
...


This looks long and silly and allocates a dict on each invocation of
foo (which calls itself recursively so this is a big deal).



So, what is the best way to tell math.isclose to use the default
tolerances?



PS. There are several related questions. Note that I do not want to know the actual default arguments of math.isclose - all I want it to tell it to use the defaults whatever they are.










share|improve this question



















  • 1





    you can grab the values of the default args: Get a function argument's default value? and come up with a way to define your function using those values. There's probably a way using functools.partial

    – pault
    Apr 3 at 14:46











  • possible duplicate of stackoverflow.com/questions/4670665/… but you will not find better answer in it.

    – naivepredictor
    Apr 3 at 14:49






  • 2





    @pault I tried inspect.signature(math.isclose) and it fails with ValueError: no signature found for builtin <built-in function isclose>.

    – Sanyash
    Apr 3 at 14:50











  • @Sanyash It works for me in python 3.7.2.

    – Aran-Fey
    Apr 3 at 14:53











  • @Aran-Fey indeed, in 3.7.2 it works, but not in 3.6.6

    – Sanyash
    Apr 3 at 14:54













21












21








21


3






I have a function foo that calls math.isclose:



import math
def foo(..., rtol=None, atol=None):
...
if math.isclose(x, y, rel_tol=rtol, abs_tol=atol):
...
...


The above fails in math.isclose if I do not pass rtol and atol to foo:



TypeError: must be real number, not NoneType


I do not want to put the system default argument values in my code (what
if they change down the road?)



Here is what I came up with so far:



import math
def foo(..., rtol=None, atol=None):
...
tols =
if rtol is not None:
tols["rel_tol"] = rtol
if atol is not None:
tols["abs_tol"] = atol
if math.isclose(x, y, **tols):
...
...


This looks long and silly and allocates a dict on each invocation of
foo (which calls itself recursively so this is a big deal).



So, what is the best way to tell math.isclose to use the default
tolerances?



PS. There are several related questions. Note that I do not want to know the actual default arguments of math.isclose - all I want it to tell it to use the defaults whatever they are.










share|improve this question
















I have a function foo that calls math.isclose:



import math
def foo(..., rtol=None, atol=None):
...
if math.isclose(x, y, rel_tol=rtol, abs_tol=atol):
...
...


The above fails in math.isclose if I do not pass rtol and atol to foo:



TypeError: must be real number, not NoneType


I do not want to put the system default argument values in my code (what
if they change down the road?)



Here is what I came up with so far:



import math
def foo(..., rtol=None, atol=None):
...
tols =
if rtol is not None:
tols["rel_tol"] = rtol
if atol is not None:
tols["abs_tol"] = atol
if math.isclose(x, y, **tols):
...
...


This looks long and silly and allocates a dict on each invocation of
foo (which calls itself recursively so this is a big deal).



So, what is the best way to tell math.isclose to use the default
tolerances?



PS. There are several related questions. Note that I do not want to know the actual default arguments of math.isclose - all I want it to tell it to use the defaults whatever they are.







python python-3.x






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited yesterday







sds

















asked Apr 3 at 14:39









sdssds

40.4k1498177




40.4k1498177







  • 1





    you can grab the values of the default args: Get a function argument's default value? and come up with a way to define your function using those values. There's probably a way using functools.partial

    – pault
    Apr 3 at 14:46











  • possible duplicate of stackoverflow.com/questions/4670665/… but you will not find better answer in it.

    – naivepredictor
    Apr 3 at 14:49






  • 2





    @pault I tried inspect.signature(math.isclose) and it fails with ValueError: no signature found for builtin <built-in function isclose>.

    – Sanyash
    Apr 3 at 14:50











  • @Sanyash It works for me in python 3.7.2.

    – Aran-Fey
    Apr 3 at 14:53











  • @Aran-Fey indeed, in 3.7.2 it works, but not in 3.6.6

    – Sanyash
    Apr 3 at 14:54












  • 1





    you can grab the values of the default args: Get a function argument's default value? and come up with a way to define your function using those values. There's probably a way using functools.partial

    – pault
    Apr 3 at 14:46











  • possible duplicate of stackoverflow.com/questions/4670665/… but you will not find better answer in it.

    – naivepredictor
    Apr 3 at 14:49






  • 2





    @pault I tried inspect.signature(math.isclose) and it fails with ValueError: no signature found for builtin <built-in function isclose>.

    – Sanyash
    Apr 3 at 14:50











  • @Sanyash It works for me in python 3.7.2.

    – Aran-Fey
    Apr 3 at 14:53











  • @Aran-Fey indeed, in 3.7.2 it works, but not in 3.6.6

    – Sanyash
    Apr 3 at 14:54







1




1





you can grab the values of the default args: Get a function argument's default value? and come up with a way to define your function using those values. There's probably a way using functools.partial

– pault
Apr 3 at 14:46





you can grab the values of the default args: Get a function argument's default value? and come up with a way to define your function using those values. There's probably a way using functools.partial

– pault
Apr 3 at 14:46













possible duplicate of stackoverflow.com/questions/4670665/… but you will not find better answer in it.

– naivepredictor
Apr 3 at 14:49





possible duplicate of stackoverflow.com/questions/4670665/… but you will not find better answer in it.

– naivepredictor
Apr 3 at 14:49




2




2





@pault I tried inspect.signature(math.isclose) and it fails with ValueError: no signature found for builtin <built-in function isclose>.

– Sanyash
Apr 3 at 14:50





@pault I tried inspect.signature(math.isclose) and it fails with ValueError: no signature found for builtin <built-in function isclose>.

– Sanyash
Apr 3 at 14:50













@Sanyash It works for me in python 3.7.2.

– Aran-Fey
Apr 3 at 14:53





@Sanyash It works for me in python 3.7.2.

– Aran-Fey
Apr 3 at 14:53













@Aran-Fey indeed, in 3.7.2 it works, but not in 3.6.6

– Sanyash
Apr 3 at 14:54





@Aran-Fey indeed, in 3.7.2 it works, but not in 3.6.6

– Sanyash
Apr 3 at 14:54












4 Answers
4






active

oldest

votes


















10














One way to do it would be with variadic argument unpacking:



def foo(..., **kwargs):
...
if math.isclose(x, y, **kwargs):
...


This would allow you to specify atol and rtol as keyword arguments to the main function foo, which it would then pass on unchanged to math.isclose.



However, I would also say that it is idiomatic that arguments passed to kwargs modify the behaviour of a function in some way other than to be merely passed to sub-functions being called. Therefore, I would suggest that instead, a parameter is named such that it is clear that it will be unpacked and passed unchanged to a sub-function:



def foo(..., isclose_kwargs=):
...
if math.isclose(x, y, **isclose_kwargs):
...


You can see an equivalent pattern in matplotlib (example: plt.subplots - subplot_kw and gridspec_kw, with all other keyword arguments being passed to the Figure constructor as **fig_kw) and seaborn (example: FacetGrid - subplot_kws, gridspec_kws).



This is particularly apparent when there are mutiple sub-functions you might want to pass keyword arguments, but retain the default behaviour otherwise:



def foo(..., f1_kwargs=, f2_kwargs=, f3_kwargs=):
...
f1(**f1_kwargs)
...
f2(**f2_kwargs)
...
f3(**f3_kwargs)
...


Caveat:



Note that default arguments are only instantiated once, so you should not modify the empty dicts in your function. If there is a need to, you should instead use None as the default argument and instantiate a new empty dict each time the function is run:



def foo(..., isclose_kwargs=None):
if isclose_kwargs is None:
isclose_kwargs =
...
if math.isclose(x, y, **isclose_kwargs):
...


My preference is to avoid this where you know what you're doing since it is more brief, and in general I don't like rebinding variables. However, it is definitely a valid idiom, and it can be safer.






share|improve this answer

























  • Further, document that any additional keyword arguments to foo are passed directly to math.isclose.

    – chepner
    Apr 3 at 14:58











  • @chepner Yup, that's how the FacetGrid example does it. In addition, I think that in a case where keyword arguments are being passed on, an explicit isclose_kwargs or similar argument would be preferred to naked **kwargs.

    – gmds
    Apr 3 at 15:01












  • Don't use mutable default arguments, however. f1_kwargs=None is better.

    – Martijn Pieters
    Apr 3 at 15:10






  • 1





    @gmds: I can still use foo.__defaults__[0]['bar'] = 42 to mess with the defaults. And future refactorings of the function could easily miss that you defined these as defaults. Don't make it that easy to introduce errors.

    – Martijn Pieters
    Apr 3 at 15:25






  • 1





    @MartijnPieters Anyone with the knowledge to modify default arguments in that way is good enough with Python to know that default arguments are instantiated only once. As for the rest, again, a sufficiently skilled programmer will know when to break the rules for a good reason - in this case, brevity. I have edited my answer to note the default argument pitfall, but I think that is sufficient.

    – gmds
    Apr 3 at 15:33


















9














The correct solution would be to use the same defaults as math.isclose(). There is no need to hard-code them, as you can get the current defaults with the inspect.signature() function:



import inspect
import math

_isclose_params = inspect.signature(math.isclose).parameters

def foo(..., rtol=_isclose_params['rel_tol'].default, atol=_isclose_params['abs_tol'].default):
# ...


Quick demo:



>>> import inspect
>>> import math
>>> params = inspect.signature(math.isclose).parameters
>>> params['rel_tol'].default
1e-09
>>> params['abs_tol'].default
0.0


This works because math.isclose() defines its arguments using the Argument Clinic tool:




[T]he original motivation for Argument Clinic was to provide introspection “signatures” for CPython builtins. It used to be, the introspection query functions would throw an exception if you passed in a builtin. With Argument Clinic, that’s a thing of the past!




Under the hood, the math.isclose() signature is actually stored as a string:



>>> math.isclose.__text_signature__
'($module, /, a, b, *, rel_tol=1e-09, abs_tol=0.0)'


This is parsed out by the inspect signature support to give you the actual values.



Not all C-defined functions use Argument Clinic yet, the codebase is being converted on a case-by-case basis. math.isclose() was converted for Python 3.7.0.



You could use the __doc__ string as a fallback, as in earlier versions this too contains the signature:



>>> import math
>>> import sys
>>> sys.version_info
sys.version_info(major=3, minor=6, micro=8, releaselevel='final', serial=0)
>>> math.isclose.__doc__.splitlines()[0]
'isclose(a, b, *, rel_tol=1e-09, abs_tol=0.0) -> bool'


so a slightly more generic fallback could be:



import inspect

def func_defaults(f):
try:
params = inspect.signature(f).parameters
except ValueError:
# parse out the signature from the docstring
doc = f.__doc__
first = doc and doc.splitlines()[0]
if first is None or f.__name__ not in first or '(' not in first:
return
sig = inspect._signature_fromstr(inspect.Signature, math.isclose, first)
params = sig.parameters
return
name: p.default for name, p in params.items()
if p.default is not inspect.Parameter.empty



I'd see this as a stop-gap measure only needed to support older Python 3.x releases. The function produces a dictionary keyed on parameter name:



>>> import sys
>>> import math
>>> sys.version_info
sys.version_info(major=3, minor=6, micro=8, releaselevel='final', serial=0)
>>> func_defaults(math.isclose)
'rel_tol': 1e-09, 'abs_tol': 0.0


Note that copying the Python defaults is very low risk; unless there is a bug, the values are not prone to change. So another option could be to hardcode the 3.5 / 3.6 known defaults as a fallback, and use the signature provided in 3.7 and newer:



try:
# Get defaults through introspection in newer releases
_isclose_params = inspect.signature(math.isclose).parameters
_isclose_rel_tol = _isclose_params['rel_tol'].default
_isclose_abs_tol = _isclose_params['abs_tol'].default
except ValueError:
# Python 3.5 / 3.6 known defaults
_isclose_rel_tol = 1e-09
_isclose_abs_tol = 0.0


Note however that you are at greater risk of not supporting future, additional parameters and defaults. At least the inspect.signature() approach would let you add an assertion about the number of parameters that your code expects there to be.






share|improve this answer




















  • 1





    inspect.signature(math.isclose) does not work in Python 3.6

    – sds
    Apr 3 at 15:19











  • @sds: right, the Argument Clinic conversion was done for 3.7. Tough luck for you!

    – Martijn Pieters
    Apr 3 at 15:23







  • 1





    Also math.isclose.__text_signature__ contains None for me on Python3.6.6.

    – Sanyash
    Apr 3 at 15:25











  • @Sanyash: yes, that's what my answer is saying. That it requires 3.7 or newer for this to work.

    – Martijn Pieters
    Apr 3 at 15:27











  • And I personally am surprised that someone felt that the 3.7+ compatibility requirement for inspect.signature(math.isclose) was worthy of downvoting. Or is there some other aspect that I missed? In which case, please do leave me some feedback in a comment so I can improve my answer.

    – Martijn Pieters
    Apr 3 at 15:54


















6














There really aren't many ways to make a function use its default arguments... You only have two options:



  1. Pass the real default values

  2. Don't pass the arguments at all

Since none of the options are great, I'm going to make an exhaustive list so you can compare them all.




  • Use **kwargs to pass through arguments



    Define your method using **kwargs and pass those to math.isclose:



    def foo(..., **kwargs):
    ...
    if math.isclose(x, y, **kwargs):


    Cons:



    • the parameter names of both functions have to match (e.g. foo(1, 2, rtol=3) won't work)



  • Manually construct a **kwargs dict



    def foo(..., rtol=None, atol=None):
    ...
    kwargs =
    if rtol is not None:
    kwargs["rel_tol"] = rtol
    if atol is not None:
    kwargs["abs_tol"] = atol
    if math.isclose(x, y, **kwargs):


    Cons:



    • ugly, a pain to code, and not fast



  • Hard-code the default values



    def foo(..., rtol=1e-09, atol=0.0):
    ...
    if math.isclose(x, y, rel_tol=rtol, abs_tol=atol):


    Cons:



    • hard-coded values



  • Use introspection to find the default values



    You can use the inspect module to determine the default values at run time:



    import inspect, math

    signature = inspect.signature(math.isclose)
    DEFAULT_RTOL = signature.parameters['rel_tol'].default
    DEFAULT_ATOL = signature.parameters['abs_tol'].default

    def foo(..., rtol=DEFAULT_RTOL, atol=DEFAULT_ATOL):
    ...
    if math.isclose(x, y, rel_tol=rtol, abs_tol=atol):


    Cons:




    • inspect.signature may fail on builtin functions or other functions defined in C






share|improve this answer























  • inspect.signature(math.isclose) does not work in Python 3.6

    – sds
    Apr 3 at 15:18


















2














Delegate the recursion to a helper function so that you only need to build the dict once.



import math
def foo_helper(..., **kwargs):
...
if math.isclose(x, y, **kwargs):
...
...


def foo(..., rtol=None, atol=None):
tols =
if rtol is not None:
tols["rel_tol"] = rtol
if atol is not None:
tols["abs_tol"] = atol

return foo_helper(x, y, **tols)


Or, instead of passing tolerances to foo, pass a function which already incorporates the desired tolerances.



from functools import partial

# f can take a default value if there is a common set of tolerances
# to use.
def foo(x, y, f):
...
if f(x,y):
...
...

# Use the defaults
foo(x, y, f=math.isclose)
# Use some other tolerances
foo(x, y, f=partial(math.isclose, rtol=my_rtel))
foo(x, y, f=partial(math.isclose, atol=my_atol))
foo(x, y, f=partial(math.isclose, rtol=my_rtel, atol=my_atol))





share|improve this answer

























    Your Answer






    StackExchange.ifUsing("editor", function ()
    StackExchange.using("externalEditor", function ()
    StackExchange.using("snippets", function ()
    StackExchange.snippets.init();
    );
    );
    , "code-snippets");

    StackExchange.ready(function()
    var channelOptions =
    tags: "".split(" "),
    id: "1"
    ;
    initTagRenderer("".split(" "), "".split(" "), channelOptions);

    StackExchange.using("externalEditor", function()
    // Have to fire editor after snippets, if snippets enabled
    if (StackExchange.settings.snippets.snippetsEnabled)
    StackExchange.using("snippets", function()
    createEditor();
    );

    else
    createEditor();

    );

    function createEditor()
    StackExchange.prepareEditor(
    heartbeatType: 'answer',
    autoActivateHeartbeat: false,
    convertImagesToLinks: true,
    noModals: true,
    showLowRepImageUploadWarning: true,
    reputationToPostImages: 10,
    bindNavPrevention: true,
    postfix: "",
    imageUploader:
    brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
    contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
    allowUrls: true
    ,
    onDemand: true,
    discardSelector: ".discard-answer"
    ,immediatelyShowMarkdownHelp:true
    );



    );













    draft saved

    draft discarded


















    StackExchange.ready(
    function ()
    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f55497837%2fhow-to-tell-a-function-to-use-the-default-argument-values%23new-answer', 'question_page');

    );

    Post as a guest















    Required, but never shown

























    4 Answers
    4






    active

    oldest

    votes








    4 Answers
    4






    active

    oldest

    votes









    active

    oldest

    votes






    active

    oldest

    votes









    10














    One way to do it would be with variadic argument unpacking:



    def foo(..., **kwargs):
    ...
    if math.isclose(x, y, **kwargs):
    ...


    This would allow you to specify atol and rtol as keyword arguments to the main function foo, which it would then pass on unchanged to math.isclose.



    However, I would also say that it is idiomatic that arguments passed to kwargs modify the behaviour of a function in some way other than to be merely passed to sub-functions being called. Therefore, I would suggest that instead, a parameter is named such that it is clear that it will be unpacked and passed unchanged to a sub-function:



    def foo(..., isclose_kwargs=):
    ...
    if math.isclose(x, y, **isclose_kwargs):
    ...


    You can see an equivalent pattern in matplotlib (example: plt.subplots - subplot_kw and gridspec_kw, with all other keyword arguments being passed to the Figure constructor as **fig_kw) and seaborn (example: FacetGrid - subplot_kws, gridspec_kws).



    This is particularly apparent when there are mutiple sub-functions you might want to pass keyword arguments, but retain the default behaviour otherwise:



    def foo(..., f1_kwargs=, f2_kwargs=, f3_kwargs=):
    ...
    f1(**f1_kwargs)
    ...
    f2(**f2_kwargs)
    ...
    f3(**f3_kwargs)
    ...


    Caveat:



    Note that default arguments are only instantiated once, so you should not modify the empty dicts in your function. If there is a need to, you should instead use None as the default argument and instantiate a new empty dict each time the function is run:



    def foo(..., isclose_kwargs=None):
    if isclose_kwargs is None:
    isclose_kwargs =
    ...
    if math.isclose(x, y, **isclose_kwargs):
    ...


    My preference is to avoid this where you know what you're doing since it is more brief, and in general I don't like rebinding variables. However, it is definitely a valid idiom, and it can be safer.






    share|improve this answer

























    • Further, document that any additional keyword arguments to foo are passed directly to math.isclose.

      – chepner
      Apr 3 at 14:58











    • @chepner Yup, that's how the FacetGrid example does it. In addition, I think that in a case where keyword arguments are being passed on, an explicit isclose_kwargs or similar argument would be preferred to naked **kwargs.

      – gmds
      Apr 3 at 15:01












    • Don't use mutable default arguments, however. f1_kwargs=None is better.

      – Martijn Pieters
      Apr 3 at 15:10






    • 1





      @gmds: I can still use foo.__defaults__[0]['bar'] = 42 to mess with the defaults. And future refactorings of the function could easily miss that you defined these as defaults. Don't make it that easy to introduce errors.

      – Martijn Pieters
      Apr 3 at 15:25






    • 1





      @MartijnPieters Anyone with the knowledge to modify default arguments in that way is good enough with Python to know that default arguments are instantiated only once. As for the rest, again, a sufficiently skilled programmer will know when to break the rules for a good reason - in this case, brevity. I have edited my answer to note the default argument pitfall, but I think that is sufficient.

      – gmds
      Apr 3 at 15:33















    10














    One way to do it would be with variadic argument unpacking:



    def foo(..., **kwargs):
    ...
    if math.isclose(x, y, **kwargs):
    ...


    This would allow you to specify atol and rtol as keyword arguments to the main function foo, which it would then pass on unchanged to math.isclose.



    However, I would also say that it is idiomatic that arguments passed to kwargs modify the behaviour of a function in some way other than to be merely passed to sub-functions being called. Therefore, I would suggest that instead, a parameter is named such that it is clear that it will be unpacked and passed unchanged to a sub-function:



    def foo(..., isclose_kwargs=):
    ...
    if math.isclose(x, y, **isclose_kwargs):
    ...


    You can see an equivalent pattern in matplotlib (example: plt.subplots - subplot_kw and gridspec_kw, with all other keyword arguments being passed to the Figure constructor as **fig_kw) and seaborn (example: FacetGrid - subplot_kws, gridspec_kws).



    This is particularly apparent when there are mutiple sub-functions you might want to pass keyword arguments, but retain the default behaviour otherwise:



    def foo(..., f1_kwargs=, f2_kwargs=, f3_kwargs=):
    ...
    f1(**f1_kwargs)
    ...
    f2(**f2_kwargs)
    ...
    f3(**f3_kwargs)
    ...


    Caveat:



    Note that default arguments are only instantiated once, so you should not modify the empty dicts in your function. If there is a need to, you should instead use None as the default argument and instantiate a new empty dict each time the function is run:



    def foo(..., isclose_kwargs=None):
    if isclose_kwargs is None:
    isclose_kwargs =
    ...
    if math.isclose(x, y, **isclose_kwargs):
    ...


    My preference is to avoid this where you know what you're doing since it is more brief, and in general I don't like rebinding variables. However, it is definitely a valid idiom, and it can be safer.






    share|improve this answer

























    • Further, document that any additional keyword arguments to foo are passed directly to math.isclose.

      – chepner
      Apr 3 at 14:58











    • @chepner Yup, that's how the FacetGrid example does it. In addition, I think that in a case where keyword arguments are being passed on, an explicit isclose_kwargs or similar argument would be preferred to naked **kwargs.

      – gmds
      Apr 3 at 15:01












    • Don't use mutable default arguments, however. f1_kwargs=None is better.

      – Martijn Pieters
      Apr 3 at 15:10






    • 1





      @gmds: I can still use foo.__defaults__[0]['bar'] = 42 to mess with the defaults. And future refactorings of the function could easily miss that you defined these as defaults. Don't make it that easy to introduce errors.

      – Martijn Pieters
      Apr 3 at 15:25






    • 1





      @MartijnPieters Anyone with the knowledge to modify default arguments in that way is good enough with Python to know that default arguments are instantiated only once. As for the rest, again, a sufficiently skilled programmer will know when to break the rules for a good reason - in this case, brevity. I have edited my answer to note the default argument pitfall, but I think that is sufficient.

      – gmds
      Apr 3 at 15:33













    10












    10








    10







    One way to do it would be with variadic argument unpacking:



    def foo(..., **kwargs):
    ...
    if math.isclose(x, y, **kwargs):
    ...


    This would allow you to specify atol and rtol as keyword arguments to the main function foo, which it would then pass on unchanged to math.isclose.



    However, I would also say that it is idiomatic that arguments passed to kwargs modify the behaviour of a function in some way other than to be merely passed to sub-functions being called. Therefore, I would suggest that instead, a parameter is named such that it is clear that it will be unpacked and passed unchanged to a sub-function:



    def foo(..., isclose_kwargs=):
    ...
    if math.isclose(x, y, **isclose_kwargs):
    ...


    You can see an equivalent pattern in matplotlib (example: plt.subplots - subplot_kw and gridspec_kw, with all other keyword arguments being passed to the Figure constructor as **fig_kw) and seaborn (example: FacetGrid - subplot_kws, gridspec_kws).



    This is particularly apparent when there are mutiple sub-functions you might want to pass keyword arguments, but retain the default behaviour otherwise:



    def foo(..., f1_kwargs=, f2_kwargs=, f3_kwargs=):
    ...
    f1(**f1_kwargs)
    ...
    f2(**f2_kwargs)
    ...
    f3(**f3_kwargs)
    ...


    Caveat:



    Note that default arguments are only instantiated once, so you should not modify the empty dicts in your function. If there is a need to, you should instead use None as the default argument and instantiate a new empty dict each time the function is run:



    def foo(..., isclose_kwargs=None):
    if isclose_kwargs is None:
    isclose_kwargs =
    ...
    if math.isclose(x, y, **isclose_kwargs):
    ...


    My preference is to avoid this where you know what you're doing since it is more brief, and in general I don't like rebinding variables. However, it is definitely a valid idiom, and it can be safer.






    share|improve this answer















    One way to do it would be with variadic argument unpacking:



    def foo(..., **kwargs):
    ...
    if math.isclose(x, y, **kwargs):
    ...


    This would allow you to specify atol and rtol as keyword arguments to the main function foo, which it would then pass on unchanged to math.isclose.



    However, I would also say that it is idiomatic that arguments passed to kwargs modify the behaviour of a function in some way other than to be merely passed to sub-functions being called. Therefore, I would suggest that instead, a parameter is named such that it is clear that it will be unpacked and passed unchanged to a sub-function:



    def foo(..., isclose_kwargs=):
    ...
    if math.isclose(x, y, **isclose_kwargs):
    ...


    You can see an equivalent pattern in matplotlib (example: plt.subplots - subplot_kw and gridspec_kw, with all other keyword arguments being passed to the Figure constructor as **fig_kw) and seaborn (example: FacetGrid - subplot_kws, gridspec_kws).



    This is particularly apparent when there are mutiple sub-functions you might want to pass keyword arguments, but retain the default behaviour otherwise:



    def foo(..., f1_kwargs=, f2_kwargs=, f3_kwargs=):
    ...
    f1(**f1_kwargs)
    ...
    f2(**f2_kwargs)
    ...
    f3(**f3_kwargs)
    ...


    Caveat:



    Note that default arguments are only instantiated once, so you should not modify the empty dicts in your function. If there is a need to, you should instead use None as the default argument and instantiate a new empty dict each time the function is run:



    def foo(..., isclose_kwargs=None):
    if isclose_kwargs is None:
    isclose_kwargs =
    ...
    if math.isclose(x, y, **isclose_kwargs):
    ...


    My preference is to avoid this where you know what you're doing since it is more brief, and in general I don't like rebinding variables. However, it is definitely a valid idiom, and it can be safer.







    share|improve this answer














    share|improve this answer



    share|improve this answer








    edited Apr 3 at 22:15

























    answered Apr 3 at 14:54









    gmdsgmds

    4,235425




    4,235425












    • Further, document that any additional keyword arguments to foo are passed directly to math.isclose.

      – chepner
      Apr 3 at 14:58











    • @chepner Yup, that's how the FacetGrid example does it. In addition, I think that in a case where keyword arguments are being passed on, an explicit isclose_kwargs or similar argument would be preferred to naked **kwargs.

      – gmds
      Apr 3 at 15:01












    • Don't use mutable default arguments, however. f1_kwargs=None is better.

      – Martijn Pieters
      Apr 3 at 15:10






    • 1





      @gmds: I can still use foo.__defaults__[0]['bar'] = 42 to mess with the defaults. And future refactorings of the function could easily miss that you defined these as defaults. Don't make it that easy to introduce errors.

      – Martijn Pieters
      Apr 3 at 15:25






    • 1





      @MartijnPieters Anyone with the knowledge to modify default arguments in that way is good enough with Python to know that default arguments are instantiated only once. As for the rest, again, a sufficiently skilled programmer will know when to break the rules for a good reason - in this case, brevity. I have edited my answer to note the default argument pitfall, but I think that is sufficient.

      – gmds
      Apr 3 at 15:33

















    • Further, document that any additional keyword arguments to foo are passed directly to math.isclose.

      – chepner
      Apr 3 at 14:58











    • @chepner Yup, that's how the FacetGrid example does it. In addition, I think that in a case where keyword arguments are being passed on, an explicit isclose_kwargs or similar argument would be preferred to naked **kwargs.

      – gmds
      Apr 3 at 15:01












    • Don't use mutable default arguments, however. f1_kwargs=None is better.

      – Martijn Pieters
      Apr 3 at 15:10






    • 1





      @gmds: I can still use foo.__defaults__[0]['bar'] = 42 to mess with the defaults. And future refactorings of the function could easily miss that you defined these as defaults. Don't make it that easy to introduce errors.

      – Martijn Pieters
      Apr 3 at 15:25






    • 1





      @MartijnPieters Anyone with the knowledge to modify default arguments in that way is good enough with Python to know that default arguments are instantiated only once. As for the rest, again, a sufficiently skilled programmer will know when to break the rules for a good reason - in this case, brevity. I have edited my answer to note the default argument pitfall, but I think that is sufficient.

      – gmds
      Apr 3 at 15:33
















    Further, document that any additional keyword arguments to foo are passed directly to math.isclose.

    – chepner
    Apr 3 at 14:58





    Further, document that any additional keyword arguments to foo are passed directly to math.isclose.

    – chepner
    Apr 3 at 14:58













    @chepner Yup, that's how the FacetGrid example does it. In addition, I think that in a case where keyword arguments are being passed on, an explicit isclose_kwargs or similar argument would be preferred to naked **kwargs.

    – gmds
    Apr 3 at 15:01






    @chepner Yup, that's how the FacetGrid example does it. In addition, I think that in a case where keyword arguments are being passed on, an explicit isclose_kwargs or similar argument would be preferred to naked **kwargs.

    – gmds
    Apr 3 at 15:01














    Don't use mutable default arguments, however. f1_kwargs=None is better.

    – Martijn Pieters
    Apr 3 at 15:10





    Don't use mutable default arguments, however. f1_kwargs=None is better.

    – Martijn Pieters
    Apr 3 at 15:10




    1




    1





    @gmds: I can still use foo.__defaults__[0]['bar'] = 42 to mess with the defaults. And future refactorings of the function could easily miss that you defined these as defaults. Don't make it that easy to introduce errors.

    – Martijn Pieters
    Apr 3 at 15:25





    @gmds: I can still use foo.__defaults__[0]['bar'] = 42 to mess with the defaults. And future refactorings of the function could easily miss that you defined these as defaults. Don't make it that easy to introduce errors.

    – Martijn Pieters
    Apr 3 at 15:25




    1




    1





    @MartijnPieters Anyone with the knowledge to modify default arguments in that way is good enough with Python to know that default arguments are instantiated only once. As for the rest, again, a sufficiently skilled programmer will know when to break the rules for a good reason - in this case, brevity. I have edited my answer to note the default argument pitfall, but I think that is sufficient.

    – gmds
    Apr 3 at 15:33





    @MartijnPieters Anyone with the knowledge to modify default arguments in that way is good enough with Python to know that default arguments are instantiated only once. As for the rest, again, a sufficiently skilled programmer will know when to break the rules for a good reason - in this case, brevity. I have edited my answer to note the default argument pitfall, but I think that is sufficient.

    – gmds
    Apr 3 at 15:33













    9














    The correct solution would be to use the same defaults as math.isclose(). There is no need to hard-code them, as you can get the current defaults with the inspect.signature() function:



    import inspect
    import math

    _isclose_params = inspect.signature(math.isclose).parameters

    def foo(..., rtol=_isclose_params['rel_tol'].default, atol=_isclose_params['abs_tol'].default):
    # ...


    Quick demo:



    >>> import inspect
    >>> import math
    >>> params = inspect.signature(math.isclose).parameters
    >>> params['rel_tol'].default
    1e-09
    >>> params['abs_tol'].default
    0.0


    This works because math.isclose() defines its arguments using the Argument Clinic tool:




    [T]he original motivation for Argument Clinic was to provide introspection “signatures” for CPython builtins. It used to be, the introspection query functions would throw an exception if you passed in a builtin. With Argument Clinic, that’s a thing of the past!




    Under the hood, the math.isclose() signature is actually stored as a string:



    >>> math.isclose.__text_signature__
    '($module, /, a, b, *, rel_tol=1e-09, abs_tol=0.0)'


    This is parsed out by the inspect signature support to give you the actual values.



    Not all C-defined functions use Argument Clinic yet, the codebase is being converted on a case-by-case basis. math.isclose() was converted for Python 3.7.0.



    You could use the __doc__ string as a fallback, as in earlier versions this too contains the signature:



    >>> import math
    >>> import sys
    >>> sys.version_info
    sys.version_info(major=3, minor=6, micro=8, releaselevel='final', serial=0)
    >>> math.isclose.__doc__.splitlines()[0]
    'isclose(a, b, *, rel_tol=1e-09, abs_tol=0.0) -> bool'


    so a slightly more generic fallback could be:



    import inspect

    def func_defaults(f):
    try:
    params = inspect.signature(f).parameters
    except ValueError:
    # parse out the signature from the docstring
    doc = f.__doc__
    first = doc and doc.splitlines()[0]
    if first is None or f.__name__ not in first or '(' not in first:
    return
    sig = inspect._signature_fromstr(inspect.Signature, math.isclose, first)
    params = sig.parameters
    return
    name: p.default for name, p in params.items()
    if p.default is not inspect.Parameter.empty



    I'd see this as a stop-gap measure only needed to support older Python 3.x releases. The function produces a dictionary keyed on parameter name:



    >>> import sys
    >>> import math
    >>> sys.version_info
    sys.version_info(major=3, minor=6, micro=8, releaselevel='final', serial=0)
    >>> func_defaults(math.isclose)
    'rel_tol': 1e-09, 'abs_tol': 0.0


    Note that copying the Python defaults is very low risk; unless there is a bug, the values are not prone to change. So another option could be to hardcode the 3.5 / 3.6 known defaults as a fallback, and use the signature provided in 3.7 and newer:



    try:
    # Get defaults through introspection in newer releases
    _isclose_params = inspect.signature(math.isclose).parameters
    _isclose_rel_tol = _isclose_params['rel_tol'].default
    _isclose_abs_tol = _isclose_params['abs_tol'].default
    except ValueError:
    # Python 3.5 / 3.6 known defaults
    _isclose_rel_tol = 1e-09
    _isclose_abs_tol = 0.0


    Note however that you are at greater risk of not supporting future, additional parameters and defaults. At least the inspect.signature() approach would let you add an assertion about the number of parameters that your code expects there to be.






    share|improve this answer




















    • 1





      inspect.signature(math.isclose) does not work in Python 3.6

      – sds
      Apr 3 at 15:19











    • @sds: right, the Argument Clinic conversion was done for 3.7. Tough luck for you!

      – Martijn Pieters
      Apr 3 at 15:23







    • 1





      Also math.isclose.__text_signature__ contains None for me on Python3.6.6.

      – Sanyash
      Apr 3 at 15:25











    • @Sanyash: yes, that's what my answer is saying. That it requires 3.7 or newer for this to work.

      – Martijn Pieters
      Apr 3 at 15:27











    • And I personally am surprised that someone felt that the 3.7+ compatibility requirement for inspect.signature(math.isclose) was worthy of downvoting. Or is there some other aspect that I missed? In which case, please do leave me some feedback in a comment so I can improve my answer.

      – Martijn Pieters
      Apr 3 at 15:54















    9














    The correct solution would be to use the same defaults as math.isclose(). There is no need to hard-code them, as you can get the current defaults with the inspect.signature() function:



    import inspect
    import math

    _isclose_params = inspect.signature(math.isclose).parameters

    def foo(..., rtol=_isclose_params['rel_tol'].default, atol=_isclose_params['abs_tol'].default):
    # ...


    Quick demo:



    >>> import inspect
    >>> import math
    >>> params = inspect.signature(math.isclose).parameters
    >>> params['rel_tol'].default
    1e-09
    >>> params['abs_tol'].default
    0.0


    This works because math.isclose() defines its arguments using the Argument Clinic tool:




    [T]he original motivation for Argument Clinic was to provide introspection “signatures” for CPython builtins. It used to be, the introspection query functions would throw an exception if you passed in a builtin. With Argument Clinic, that’s a thing of the past!




    Under the hood, the math.isclose() signature is actually stored as a string:



    >>> math.isclose.__text_signature__
    '($module, /, a, b, *, rel_tol=1e-09, abs_tol=0.0)'


    This is parsed out by the inspect signature support to give you the actual values.



    Not all C-defined functions use Argument Clinic yet, the codebase is being converted on a case-by-case basis. math.isclose() was converted for Python 3.7.0.



    You could use the __doc__ string as a fallback, as in earlier versions this too contains the signature:



    >>> import math
    >>> import sys
    >>> sys.version_info
    sys.version_info(major=3, minor=6, micro=8, releaselevel='final', serial=0)
    >>> math.isclose.__doc__.splitlines()[0]
    'isclose(a, b, *, rel_tol=1e-09, abs_tol=0.0) -> bool'


    so a slightly more generic fallback could be:



    import inspect

    def func_defaults(f):
    try:
    params = inspect.signature(f).parameters
    except ValueError:
    # parse out the signature from the docstring
    doc = f.__doc__
    first = doc and doc.splitlines()[0]
    if first is None or f.__name__ not in first or '(' not in first:
    return
    sig = inspect._signature_fromstr(inspect.Signature, math.isclose, first)
    params = sig.parameters
    return
    name: p.default for name, p in params.items()
    if p.default is not inspect.Parameter.empty



    I'd see this as a stop-gap measure only needed to support older Python 3.x releases. The function produces a dictionary keyed on parameter name:



    >>> import sys
    >>> import math
    >>> sys.version_info
    sys.version_info(major=3, minor=6, micro=8, releaselevel='final', serial=0)
    >>> func_defaults(math.isclose)
    'rel_tol': 1e-09, 'abs_tol': 0.0


    Note that copying the Python defaults is very low risk; unless there is a bug, the values are not prone to change. So another option could be to hardcode the 3.5 / 3.6 known defaults as a fallback, and use the signature provided in 3.7 and newer:



    try:
    # Get defaults through introspection in newer releases
    _isclose_params = inspect.signature(math.isclose).parameters
    _isclose_rel_tol = _isclose_params['rel_tol'].default
    _isclose_abs_tol = _isclose_params['abs_tol'].default
    except ValueError:
    # Python 3.5 / 3.6 known defaults
    _isclose_rel_tol = 1e-09
    _isclose_abs_tol = 0.0


    Note however that you are at greater risk of not supporting future, additional parameters and defaults. At least the inspect.signature() approach would let you add an assertion about the number of parameters that your code expects there to be.






    share|improve this answer




















    • 1





      inspect.signature(math.isclose) does not work in Python 3.6

      – sds
      Apr 3 at 15:19











    • @sds: right, the Argument Clinic conversion was done for 3.7. Tough luck for you!

      – Martijn Pieters
      Apr 3 at 15:23







    • 1





      Also math.isclose.__text_signature__ contains None for me on Python3.6.6.

      – Sanyash
      Apr 3 at 15:25











    • @Sanyash: yes, that's what my answer is saying. That it requires 3.7 or newer for this to work.

      – Martijn Pieters
      Apr 3 at 15:27











    • And I personally am surprised that someone felt that the 3.7+ compatibility requirement for inspect.signature(math.isclose) was worthy of downvoting. Or is there some other aspect that I missed? In which case, please do leave me some feedback in a comment so I can improve my answer.

      – Martijn Pieters
      Apr 3 at 15:54













    9












    9








    9







    The correct solution would be to use the same defaults as math.isclose(). There is no need to hard-code them, as you can get the current defaults with the inspect.signature() function:



    import inspect
    import math

    _isclose_params = inspect.signature(math.isclose).parameters

    def foo(..., rtol=_isclose_params['rel_tol'].default, atol=_isclose_params['abs_tol'].default):
    # ...


    Quick demo:



    >>> import inspect
    >>> import math
    >>> params = inspect.signature(math.isclose).parameters
    >>> params['rel_tol'].default
    1e-09
    >>> params['abs_tol'].default
    0.0


    This works because math.isclose() defines its arguments using the Argument Clinic tool:




    [T]he original motivation for Argument Clinic was to provide introspection “signatures” for CPython builtins. It used to be, the introspection query functions would throw an exception if you passed in a builtin. With Argument Clinic, that’s a thing of the past!




    Under the hood, the math.isclose() signature is actually stored as a string:



    >>> math.isclose.__text_signature__
    '($module, /, a, b, *, rel_tol=1e-09, abs_tol=0.0)'


    This is parsed out by the inspect signature support to give you the actual values.



    Not all C-defined functions use Argument Clinic yet, the codebase is being converted on a case-by-case basis. math.isclose() was converted for Python 3.7.0.



    You could use the __doc__ string as a fallback, as in earlier versions this too contains the signature:



    >>> import math
    >>> import sys
    >>> sys.version_info
    sys.version_info(major=3, minor=6, micro=8, releaselevel='final', serial=0)
    >>> math.isclose.__doc__.splitlines()[0]
    'isclose(a, b, *, rel_tol=1e-09, abs_tol=0.0) -> bool'


    so a slightly more generic fallback could be:



    import inspect

    def func_defaults(f):
    try:
    params = inspect.signature(f).parameters
    except ValueError:
    # parse out the signature from the docstring
    doc = f.__doc__
    first = doc and doc.splitlines()[0]
    if first is None or f.__name__ not in first or '(' not in first:
    return
    sig = inspect._signature_fromstr(inspect.Signature, math.isclose, first)
    params = sig.parameters
    return
    name: p.default for name, p in params.items()
    if p.default is not inspect.Parameter.empty



    I'd see this as a stop-gap measure only needed to support older Python 3.x releases. The function produces a dictionary keyed on parameter name:



    >>> import sys
    >>> import math
    >>> sys.version_info
    sys.version_info(major=3, minor=6, micro=8, releaselevel='final', serial=0)
    >>> func_defaults(math.isclose)
    'rel_tol': 1e-09, 'abs_tol': 0.0


    Note that copying the Python defaults is very low risk; unless there is a bug, the values are not prone to change. So another option could be to hardcode the 3.5 / 3.6 known defaults as a fallback, and use the signature provided in 3.7 and newer:



    try:
    # Get defaults through introspection in newer releases
    _isclose_params = inspect.signature(math.isclose).parameters
    _isclose_rel_tol = _isclose_params['rel_tol'].default
    _isclose_abs_tol = _isclose_params['abs_tol'].default
    except ValueError:
    # Python 3.5 / 3.6 known defaults
    _isclose_rel_tol = 1e-09
    _isclose_abs_tol = 0.0


    Note however that you are at greater risk of not supporting future, additional parameters and defaults. At least the inspect.signature() approach would let you add an assertion about the number of parameters that your code expects there to be.






    share|improve this answer















    The correct solution would be to use the same defaults as math.isclose(). There is no need to hard-code them, as you can get the current defaults with the inspect.signature() function:



    import inspect
    import math

    _isclose_params = inspect.signature(math.isclose).parameters

    def foo(..., rtol=_isclose_params['rel_tol'].default, atol=_isclose_params['abs_tol'].default):
    # ...


    Quick demo:



    >>> import inspect
    >>> import math
    >>> params = inspect.signature(math.isclose).parameters
    >>> params['rel_tol'].default
    1e-09
    >>> params['abs_tol'].default
    0.0


    This works because math.isclose() defines its arguments using the Argument Clinic tool:




    [T]he original motivation for Argument Clinic was to provide introspection “signatures” for CPython builtins. It used to be, the introspection query functions would throw an exception if you passed in a builtin. With Argument Clinic, that’s a thing of the past!




    Under the hood, the math.isclose() signature is actually stored as a string:



    >>> math.isclose.__text_signature__
    '($module, /, a, b, *, rel_tol=1e-09, abs_tol=0.0)'


    This is parsed out by the inspect signature support to give you the actual values.



    Not all C-defined functions use Argument Clinic yet, the codebase is being converted on a case-by-case basis. math.isclose() was converted for Python 3.7.0.



    You could use the __doc__ string as a fallback, as in earlier versions this too contains the signature:



    >>> import math
    >>> import sys
    >>> sys.version_info
    sys.version_info(major=3, minor=6, micro=8, releaselevel='final', serial=0)
    >>> math.isclose.__doc__.splitlines()[0]
    'isclose(a, b, *, rel_tol=1e-09, abs_tol=0.0) -> bool'


    so a slightly more generic fallback could be:



    import inspect

    def func_defaults(f):
    try:
    params = inspect.signature(f).parameters
    except ValueError:
    # parse out the signature from the docstring
    doc = f.__doc__
    first = doc and doc.splitlines()[0]
    if first is None or f.__name__ not in first or '(' not in first:
    return
    sig = inspect._signature_fromstr(inspect.Signature, math.isclose, first)
    params = sig.parameters
    return
    name: p.default for name, p in params.items()
    if p.default is not inspect.Parameter.empty



    I'd see this as a stop-gap measure only needed to support older Python 3.x releases. The function produces a dictionary keyed on parameter name:



    >>> import sys
    >>> import math
    >>> sys.version_info
    sys.version_info(major=3, minor=6, micro=8, releaselevel='final', serial=0)
    >>> func_defaults(math.isclose)
    'rel_tol': 1e-09, 'abs_tol': 0.0


    Note that copying the Python defaults is very low risk; unless there is a bug, the values are not prone to change. So another option could be to hardcode the 3.5 / 3.6 known defaults as a fallback, and use the signature provided in 3.7 and newer:



    try:
    # Get defaults through introspection in newer releases
    _isclose_params = inspect.signature(math.isclose).parameters
    _isclose_rel_tol = _isclose_params['rel_tol'].default
    _isclose_abs_tol = _isclose_params['abs_tol'].default
    except ValueError:
    # Python 3.5 / 3.6 known defaults
    _isclose_rel_tol = 1e-09
    _isclose_abs_tol = 0.0


    Note however that you are at greater risk of not supporting future, additional parameters and defaults. At least the inspect.signature() approach would let you add an assertion about the number of parameters that your code expects there to be.







    share|improve this answer














    share|improve this answer



    share|improve this answer








    edited Apr 3 at 15:52

























    answered Apr 3 at 15:14









    Martijn PietersMartijn Pieters

    725k14325472348




    725k14325472348







    • 1





      inspect.signature(math.isclose) does not work in Python 3.6

      – sds
      Apr 3 at 15:19











    • @sds: right, the Argument Clinic conversion was done for 3.7. Tough luck for you!

      – Martijn Pieters
      Apr 3 at 15:23







    • 1





      Also math.isclose.__text_signature__ contains None for me on Python3.6.6.

      – Sanyash
      Apr 3 at 15:25











    • @Sanyash: yes, that's what my answer is saying. That it requires 3.7 or newer for this to work.

      – Martijn Pieters
      Apr 3 at 15:27











    • And I personally am surprised that someone felt that the 3.7+ compatibility requirement for inspect.signature(math.isclose) was worthy of downvoting. Or is there some other aspect that I missed? In which case, please do leave me some feedback in a comment so I can improve my answer.

      – Martijn Pieters
      Apr 3 at 15:54












    • 1





      inspect.signature(math.isclose) does not work in Python 3.6

      – sds
      Apr 3 at 15:19











    • @sds: right, the Argument Clinic conversion was done for 3.7. Tough luck for you!

      – Martijn Pieters
      Apr 3 at 15:23







    • 1





      Also math.isclose.__text_signature__ contains None for me on Python3.6.6.

      – Sanyash
      Apr 3 at 15:25











    • @Sanyash: yes, that's what my answer is saying. That it requires 3.7 or newer for this to work.

      – Martijn Pieters
      Apr 3 at 15:27











    • And I personally am surprised that someone felt that the 3.7+ compatibility requirement for inspect.signature(math.isclose) was worthy of downvoting. Or is there some other aspect that I missed? In which case, please do leave me some feedback in a comment so I can improve my answer.

      – Martijn Pieters
      Apr 3 at 15:54







    1




    1





    inspect.signature(math.isclose) does not work in Python 3.6

    – sds
    Apr 3 at 15:19





    inspect.signature(math.isclose) does not work in Python 3.6

    – sds
    Apr 3 at 15:19













    @sds: right, the Argument Clinic conversion was done for 3.7. Tough luck for you!

    – Martijn Pieters
    Apr 3 at 15:23






    @sds: right, the Argument Clinic conversion was done for 3.7. Tough luck for you!

    – Martijn Pieters
    Apr 3 at 15:23





    1




    1





    Also math.isclose.__text_signature__ contains None for me on Python3.6.6.

    – Sanyash
    Apr 3 at 15:25





    Also math.isclose.__text_signature__ contains None for me on Python3.6.6.

    – Sanyash
    Apr 3 at 15:25













    @Sanyash: yes, that's what my answer is saying. That it requires 3.7 or newer for this to work.

    – Martijn Pieters
    Apr 3 at 15:27





    @Sanyash: yes, that's what my answer is saying. That it requires 3.7 or newer for this to work.

    – Martijn Pieters
    Apr 3 at 15:27













    And I personally am surprised that someone felt that the 3.7+ compatibility requirement for inspect.signature(math.isclose) was worthy of downvoting. Or is there some other aspect that I missed? In which case, please do leave me some feedback in a comment so I can improve my answer.

    – Martijn Pieters
    Apr 3 at 15:54





    And I personally am surprised that someone felt that the 3.7+ compatibility requirement for inspect.signature(math.isclose) was worthy of downvoting. Or is there some other aspect that I missed? In which case, please do leave me some feedback in a comment so I can improve my answer.

    – Martijn Pieters
    Apr 3 at 15:54











    6














    There really aren't many ways to make a function use its default arguments... You only have two options:



    1. Pass the real default values

    2. Don't pass the arguments at all

    Since none of the options are great, I'm going to make an exhaustive list so you can compare them all.




    • Use **kwargs to pass through arguments



      Define your method using **kwargs and pass those to math.isclose:



      def foo(..., **kwargs):
      ...
      if math.isclose(x, y, **kwargs):


      Cons:



      • the parameter names of both functions have to match (e.g. foo(1, 2, rtol=3) won't work)



    • Manually construct a **kwargs dict



      def foo(..., rtol=None, atol=None):
      ...
      kwargs =
      if rtol is not None:
      kwargs["rel_tol"] = rtol
      if atol is not None:
      kwargs["abs_tol"] = atol
      if math.isclose(x, y, **kwargs):


      Cons:



      • ugly, a pain to code, and not fast



    • Hard-code the default values



      def foo(..., rtol=1e-09, atol=0.0):
      ...
      if math.isclose(x, y, rel_tol=rtol, abs_tol=atol):


      Cons:



      • hard-coded values



    • Use introspection to find the default values



      You can use the inspect module to determine the default values at run time:



      import inspect, math

      signature = inspect.signature(math.isclose)
      DEFAULT_RTOL = signature.parameters['rel_tol'].default
      DEFAULT_ATOL = signature.parameters['abs_tol'].default

      def foo(..., rtol=DEFAULT_RTOL, atol=DEFAULT_ATOL):
      ...
      if math.isclose(x, y, rel_tol=rtol, abs_tol=atol):


      Cons:




      • inspect.signature may fail on builtin functions or other functions defined in C






    share|improve this answer























    • inspect.signature(math.isclose) does not work in Python 3.6

      – sds
      Apr 3 at 15:18















    6














    There really aren't many ways to make a function use its default arguments... You only have two options:



    1. Pass the real default values

    2. Don't pass the arguments at all

    Since none of the options are great, I'm going to make an exhaustive list so you can compare them all.




    • Use **kwargs to pass through arguments



      Define your method using **kwargs and pass those to math.isclose:



      def foo(..., **kwargs):
      ...
      if math.isclose(x, y, **kwargs):


      Cons:



      • the parameter names of both functions have to match (e.g. foo(1, 2, rtol=3) won't work)



    • Manually construct a **kwargs dict



      def foo(..., rtol=None, atol=None):
      ...
      kwargs =
      if rtol is not None:
      kwargs["rel_tol"] = rtol
      if atol is not None:
      kwargs["abs_tol"] = atol
      if math.isclose(x, y, **kwargs):


      Cons:



      • ugly, a pain to code, and not fast



    • Hard-code the default values



      def foo(..., rtol=1e-09, atol=0.0):
      ...
      if math.isclose(x, y, rel_tol=rtol, abs_tol=atol):


      Cons:



      • hard-coded values



    • Use introspection to find the default values



      You can use the inspect module to determine the default values at run time:



      import inspect, math

      signature = inspect.signature(math.isclose)
      DEFAULT_RTOL = signature.parameters['rel_tol'].default
      DEFAULT_ATOL = signature.parameters['abs_tol'].default

      def foo(..., rtol=DEFAULT_RTOL, atol=DEFAULT_ATOL):
      ...
      if math.isclose(x, y, rel_tol=rtol, abs_tol=atol):


      Cons:




      • inspect.signature may fail on builtin functions or other functions defined in C






    share|improve this answer























    • inspect.signature(math.isclose) does not work in Python 3.6

      – sds
      Apr 3 at 15:18













    6












    6








    6







    There really aren't many ways to make a function use its default arguments... You only have two options:



    1. Pass the real default values

    2. Don't pass the arguments at all

    Since none of the options are great, I'm going to make an exhaustive list so you can compare them all.




    • Use **kwargs to pass through arguments



      Define your method using **kwargs and pass those to math.isclose:



      def foo(..., **kwargs):
      ...
      if math.isclose(x, y, **kwargs):


      Cons:



      • the parameter names of both functions have to match (e.g. foo(1, 2, rtol=3) won't work)



    • Manually construct a **kwargs dict



      def foo(..., rtol=None, atol=None):
      ...
      kwargs =
      if rtol is not None:
      kwargs["rel_tol"] = rtol
      if atol is not None:
      kwargs["abs_tol"] = atol
      if math.isclose(x, y, **kwargs):


      Cons:



      • ugly, a pain to code, and not fast



    • Hard-code the default values



      def foo(..., rtol=1e-09, atol=0.0):
      ...
      if math.isclose(x, y, rel_tol=rtol, abs_tol=atol):


      Cons:



      • hard-coded values



    • Use introspection to find the default values



      You can use the inspect module to determine the default values at run time:



      import inspect, math

      signature = inspect.signature(math.isclose)
      DEFAULT_RTOL = signature.parameters['rel_tol'].default
      DEFAULT_ATOL = signature.parameters['abs_tol'].default

      def foo(..., rtol=DEFAULT_RTOL, atol=DEFAULT_ATOL):
      ...
      if math.isclose(x, y, rel_tol=rtol, abs_tol=atol):


      Cons:




      • inspect.signature may fail on builtin functions or other functions defined in C






    share|improve this answer













    There really aren't many ways to make a function use its default arguments... You only have two options:



    1. Pass the real default values

    2. Don't pass the arguments at all

    Since none of the options are great, I'm going to make an exhaustive list so you can compare them all.




    • Use **kwargs to pass through arguments



      Define your method using **kwargs and pass those to math.isclose:



      def foo(..., **kwargs):
      ...
      if math.isclose(x, y, **kwargs):


      Cons:



      • the parameter names of both functions have to match (e.g. foo(1, 2, rtol=3) won't work)



    • Manually construct a **kwargs dict



      def foo(..., rtol=None, atol=None):
      ...
      kwargs =
      if rtol is not None:
      kwargs["rel_tol"] = rtol
      if atol is not None:
      kwargs["abs_tol"] = atol
      if math.isclose(x, y, **kwargs):


      Cons:



      • ugly, a pain to code, and not fast



    • Hard-code the default values



      def foo(..., rtol=1e-09, atol=0.0):
      ...
      if math.isclose(x, y, rel_tol=rtol, abs_tol=atol):


      Cons:



      • hard-coded values



    • Use introspection to find the default values



      You can use the inspect module to determine the default values at run time:



      import inspect, math

      signature = inspect.signature(math.isclose)
      DEFAULT_RTOL = signature.parameters['rel_tol'].default
      DEFAULT_ATOL = signature.parameters['abs_tol'].default

      def foo(..., rtol=DEFAULT_RTOL, atol=DEFAULT_ATOL):
      ...
      if math.isclose(x, y, rel_tol=rtol, abs_tol=atol):


      Cons:




      • inspect.signature may fail on builtin functions or other functions defined in C







    share|improve this answer












    share|improve this answer



    share|improve this answer










    answered Apr 3 at 15:13









    Aran-FeyAran-Fey

    20.1k53972




    20.1k53972












    • inspect.signature(math.isclose) does not work in Python 3.6

      – sds
      Apr 3 at 15:18

















    • inspect.signature(math.isclose) does not work in Python 3.6

      – sds
      Apr 3 at 15:18
















    inspect.signature(math.isclose) does not work in Python 3.6

    – sds
    Apr 3 at 15:18





    inspect.signature(math.isclose) does not work in Python 3.6

    – sds
    Apr 3 at 15:18











    2














    Delegate the recursion to a helper function so that you only need to build the dict once.



    import math
    def foo_helper(..., **kwargs):
    ...
    if math.isclose(x, y, **kwargs):
    ...
    ...


    def foo(..., rtol=None, atol=None):
    tols =
    if rtol is not None:
    tols["rel_tol"] = rtol
    if atol is not None:
    tols["abs_tol"] = atol

    return foo_helper(x, y, **tols)


    Or, instead of passing tolerances to foo, pass a function which already incorporates the desired tolerances.



    from functools import partial

    # f can take a default value if there is a common set of tolerances
    # to use.
    def foo(x, y, f):
    ...
    if f(x,y):
    ...
    ...

    # Use the defaults
    foo(x, y, f=math.isclose)
    # Use some other tolerances
    foo(x, y, f=partial(math.isclose, rtol=my_rtel))
    foo(x, y, f=partial(math.isclose, atol=my_atol))
    foo(x, y, f=partial(math.isclose, rtol=my_rtel, atol=my_atol))





    share|improve this answer





























      2














      Delegate the recursion to a helper function so that you only need to build the dict once.



      import math
      def foo_helper(..., **kwargs):
      ...
      if math.isclose(x, y, **kwargs):
      ...
      ...


      def foo(..., rtol=None, atol=None):
      tols =
      if rtol is not None:
      tols["rel_tol"] = rtol
      if atol is not None:
      tols["abs_tol"] = atol

      return foo_helper(x, y, **tols)


      Or, instead of passing tolerances to foo, pass a function which already incorporates the desired tolerances.



      from functools import partial

      # f can take a default value if there is a common set of tolerances
      # to use.
      def foo(x, y, f):
      ...
      if f(x,y):
      ...
      ...

      # Use the defaults
      foo(x, y, f=math.isclose)
      # Use some other tolerances
      foo(x, y, f=partial(math.isclose, rtol=my_rtel))
      foo(x, y, f=partial(math.isclose, atol=my_atol))
      foo(x, y, f=partial(math.isclose, rtol=my_rtel, atol=my_atol))





      share|improve this answer



























        2












        2








        2







        Delegate the recursion to a helper function so that you only need to build the dict once.



        import math
        def foo_helper(..., **kwargs):
        ...
        if math.isclose(x, y, **kwargs):
        ...
        ...


        def foo(..., rtol=None, atol=None):
        tols =
        if rtol is not None:
        tols["rel_tol"] = rtol
        if atol is not None:
        tols["abs_tol"] = atol

        return foo_helper(x, y, **tols)


        Or, instead of passing tolerances to foo, pass a function which already incorporates the desired tolerances.



        from functools import partial

        # f can take a default value if there is a common set of tolerances
        # to use.
        def foo(x, y, f):
        ...
        if f(x,y):
        ...
        ...

        # Use the defaults
        foo(x, y, f=math.isclose)
        # Use some other tolerances
        foo(x, y, f=partial(math.isclose, rtol=my_rtel))
        foo(x, y, f=partial(math.isclose, atol=my_atol))
        foo(x, y, f=partial(math.isclose, rtol=my_rtel, atol=my_atol))





        share|improve this answer















        Delegate the recursion to a helper function so that you only need to build the dict once.



        import math
        def foo_helper(..., **kwargs):
        ...
        if math.isclose(x, y, **kwargs):
        ...
        ...


        def foo(..., rtol=None, atol=None):
        tols =
        if rtol is not None:
        tols["rel_tol"] = rtol
        if atol is not None:
        tols["abs_tol"] = atol

        return foo_helper(x, y, **tols)


        Or, instead of passing tolerances to foo, pass a function which already incorporates the desired tolerances.



        from functools import partial

        # f can take a default value if there is a common set of tolerances
        # to use.
        def foo(x, y, f):
        ...
        if f(x,y):
        ...
        ...

        # Use the defaults
        foo(x, y, f=math.isclose)
        # Use some other tolerances
        foo(x, y, f=partial(math.isclose, rtol=my_rtel))
        foo(x, y, f=partial(math.isclose, atol=my_atol))
        foo(x, y, f=partial(math.isclose, rtol=my_rtel, atol=my_atol))






        share|improve this answer














        share|improve this answer



        share|improve this answer








        edited Apr 3 at 15:11

























        answered Apr 3 at 15:04









        chepnerchepner

        261k35251344




        261k35251344



























            draft saved

            draft discarded
















































            Thanks for contributing an answer to Stack Overflow!


            • Please be sure to answer the question. Provide details and share your research!

            But avoid


            • Asking for help, clarification, or responding to other answers.

            • Making statements based on opinion; back them up with references or personal experience.

            To learn more, see our tips on writing great answers.




            draft saved


            draft discarded














            StackExchange.ready(
            function ()
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f55497837%2fhow-to-tell-a-function-to-use-the-default-argument-values%23new-answer', 'question_page');

            );

            Post as a guest















            Required, but never shown





















































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown

































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown







            Popular posts from this blog

            How to write a 12-bar blues melodyI-IV-V blues progressionHow to play the bridges in a standard blues progressionHow does Gdim7 fit in C# minor?question on a certain chord progressionMusicology of Melody12 bar blues, spread rhythm: alternative to 6th chord to avoid finger stretchChord progressions/ Root key/ MelodiesHow to put chords (POP-EDM) under a given lead vocal melody (starting from a good knowledge in music theory)Are there “rules” for improvising with the minor pentatonic scale over 12-bar shuffle?Confusion about blues scale and chords

            What if the end-user didn't have the required library?What is setup.py?What is a clean, pythonic way to have multiple constructors in Python?What does Ruby have that Python doesn't, and vice versa?What is the reason for having '//' in Python?How do I create a namespace package in Python?How to package shared objects that python modules depend on?setuptools vs. distutils: why is distutils still a thing?Navigation in Windows 10 vs code not going to virtualenv library when the same library is installed at user levelPython create package for local usePackaging a project that uses multiple python versionsWhy is permission denied on pip install except for when “--user” is included at end of command?

            Why did Thanos need his ship to help him in the battle scene?Which actor plays Thanos in the Avengers mid-credits scene?Are there economic implications portrayed in comics where the buildings and cities are ruined almost daily?Old X-Men comic where team travels to alien world with a ring-like sun that needs recharging?Why does Ego need help sleeping?Is there an objective answer to who “the strongest Avenger” is?How did Banner get unstuck?Why did Thanos get hit?How did Thanos (or anyone) know the Infinity Stones would give him this power?Did Thanos leave Eitri alive for his after-sales service?In Avengers 1, why does Thanos need Loki?