简体   繁体   English

如何将空参数传递给python函数?

[英]How to pass an empty parameter to a python function?

Here is the working code: 这是工作代码:

def g(y=10):
    return y**2

def f(x,y=10):
    return x*g(y)

print(f(5)) #->500

However, let's suppose we don't want to remember and copy a default value of keyword parameter y to the definition of external function (especially if there are several layers of external functions). 但是,我们假设我们不想记住并将关键字参数y的默认值复制到外部函数的定义中(特别是如果有多个外部函数层)。 In the above example it means that we want to use parameter, already defined in g . 在上面的例子中,它意味着我们想要使用已在g定义的参数。

One way to do that: 一种方法:

def f(x,y=None):
    if y==None: return x*g()
    else: return x*g(y)

But is there a cleaner way to do the same? 但有没有更清洁的方法来做同样的事情? Something like: 就像是:

def f(x,y=empty()):
    return x*g(y)

This is possible : 这是可能的

def g(y=10):
    return y**2

def f(x, y=g.__defaults__[0]):
    return x * g(y)

But it is arguably less clear than what you had originally (defaulting y to None ). 但它可以说比你原来的不那么清楚 (将y默认为None )。

An option which doesn't restrict the definition order of f and g , and should remain working if the function default of g gets changed dynamically: 一个不限制fg的定义顺序的选项,如果动态更改g的函数默认值,它应该保持工作:

def f(x, y=None):
    kwargs = {}
    if y is None:
        kwargs['y'] = y
    return x * g(**kwargs)

Interesting question! 有趣的问题! Here's another possibility, however this requires handing in the second parameter as a named parameter. 这是另一种可能性,但是这需要将第二个参数作为命名参数。

>>> def g(y=10):
...     return y**2
... 
>>> def f(x, **kwargs):
...     return x * g(**kwargs)
... 
>>> f(5)
500
>>> f(5, y=0)
0

A limitation of signatures such as def f(x, y=None) or def f(x, **kwargs) is that readers have to dig into source code or documentation to find out what's going on with y . def f(x, y=None)def f(x, **kwargs)等签名的限制是读者必须深入研究源代码或文档以找出y发生了什么。 Stick to something simple and straightforward: 坚持简单明了的事情:

DEFAULT_Y = 10
def g(y=DEFAULT_Y): ...
def f(x, y=DEFAULT_Y): ...

I'd like to start by saying if the arguments were keyword only this would be so easy: 我想首先说一下,如果参数只是关键字, 那么这将是如此简单:

def f(*, x="x", y= "y",z="z"):
    print(x,y,z)

def g(*, x,y,z):
    print(x,y,z,"from g!!")

if g.__kwdefaults__ is None: #completely override defaults
    g.__kwdefaults__ = f.__kwdefaults__
else: #if there are already some defaults then update
    g.__kwdefaults__.update(f.__kedefaults__)

g()

if you are using positional arguments it isn't quite as easy although your example is one of the specific cases that works the same way: 如果你使用位置参数,它并不是那么容易,尽管你的例子是以相同方式工作的特定情况之一:

def g(y=10): #last argument is y
    return y**2

def f(x,y): #last argument is y
    return x*g(y)
f.__defaults__ = g.__defaults__ #copies the end of the defaults to f

print(f(5)) #->500

But this is a very specific case: 但这是一个非常具体的案例:

  1. The arguments to inherit the defaults must be in the same order as the original. 继承默认值的参数必须与原始参数的顺序相同。
  2. There must not be any positional arguments after the ones with inherited defaults 在具有继承默认值的参数之后,不得有任何位置参数
  3. There must not be any other arguments with default values (or they get overridden) 一定不能有任何其他参数的默认值(或它们被覆盖)

The generic solution requires quite a bit of code but allows any signature to be merged into another, for example: 通用解决方案需要相当多的代码,但允许任何签名合并到另一个,例如:

def f(x,y,z=0,reverse=True):
    pass

@copy_defaults(f)
def g(a,b, #arguments for g
      x,y,z, #arguments to forward to f
      c=None, d="test", #some optional arguments for g
      *,reverse): #only take reverse as a keyword
    pass

>>> inspect.signature(g)
<Signature (a, b, x, y, z=0, c=None, d='test', *, reverse=True)>

This can be achieved with the following code (I can't find a simpler way to do it that works with above case) 这可以通过以下代码实现(我无法找到一种更简单的方法来处理上述情况)

import inspect

def copy_defaults(original_f):
    "creates wrapper for DefaultArgs(original_f).copy_defaults(dest_f)"
    def wrapper(dest_f):
        return DefaultArgs(original_f).copy_defaults(dest_f)
    return wrapper

class DefaultArgs(dict):
    def __init__(self,func):
        spec = inspect.getfullargspec(func)
        if spec.defaults:
            dict.__init__(self,
                          zip(reversed(spec.args),
                              reversed(spec.defaults)
                          ))
        else:
            dict.__init__(self) #I'm not sure this is necessary
        if spec.kwonlydefaults:
            self.update(spec.kwonlydefaults)

    def get_kwdefaults(self,keywords):
        return {k:v for k,v in self.items() if k in keywords}

    def gen_pos_defaults(self,args,defaults=None):
        if defaults is None:
            defaults = ()
        found_default = False
        for i,arg in enumerate(args,start=len(defaults)-len(args)):
            if arg in self:
                yield self[arg]
                found_default = True
            elif i>=0:
                yield defaults[i]
            elif found_default: #if an argument does not have a default but is after one that does
                raise TypeError("non-default argument %r follows default argument"%arg)

    def copy_defaults(self,func):
        spec = inspect.getfullargspec(func)
        new_kwargs = self.get_kwdefaults(spec.kwonlyargs)
        if func.__kwdefaults__ is not None:
            func.__kwdefaults__.update(new_kwargs)
        else:
            func.__kwdefaults__ = new_kwargs

        func.__defaults__ = tuple(self.gen_pos_defaults(spec.args,spec.defaults))
        return func

If you can modify g , then this works: 如果你可以修改g ,那么这有效:

def g(y=None):
    if y is None:
        y = 10
    return y**2

def f(x,y=None):
    return x*g(y)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM