如何获取传递给函数调用的关键字参数的原始顺序?

 sir栖云_888 发布于 2023-02-13 15:48

在我正在研究的特定项目中,检索通过** kwargs传递的关键字参数的顺序将非常有用。这是关于制作一种具有有意义尺寸的nd numpy数组(现在称为dimarray),对地球物理数据处理特别有用。

现在说我们有:

import numpy as np
from dimarray import Dimarray   # the handy class I am programming

def make_data(nlat, nlon):
    """ generate some example data
    """
    values = np.random.randn(nlat, nlon)
    lon = np.linspace(-180,180,nlon)
    lat = np.linspace(-90,90,nlat)
    return lon, lat, values

什么有效:

>>> lon, lat, values = make_data(180,360)
>>> a = Dimarray(values, lat=lat, lon=lon)
>>> print a.lon[0], a.lat[0]
-180.0 -90.0

什么不是:

>>> lon, lat, data = make_data(180,180) # square, no shape checking possible !
>>> a = Dimarray(values, lat=lat, lon=lon)
>>> print a.lon[0], a.lat[0] # is random 
-90.0, -180.0  # could be (actually I raise an error in such ambiguous cases)

原因是Dimarray的__init__方法的签名是,(values, **kwargs)并且由于kwargs是无序字典(dict),所以它能做的最好的事情就是对照的形状进行检查values

当然,我希望它适用于任何尺寸:

a = Dimarray(values, x1=.., x2=...,x3=...)

因此,必须使用进行硬编码**kwargs 。发生歧义情况的机会会随着维度的数量而增加。可以通过以下方法解决,例如使用签名(values, axes, names, **kwargs)可以:

a = Dimarray(values, [lat, lon], ["lat","lon"]) 

但是这种语法对于交互式使用(ipython)来说很麻烦,因为我希望此软件包确实成为我(和其他!!)日常使用python的一部分,作为地球物理学中numpy数组的实际替代。

我将对此感兴趣。我现在能想到的最好的方法是使用检查模块的堆栈方法来解析调用者的语句:

import inspect
def f(**kwargs):
    print inspect.stack()[1][4]
    return tuple([kwargs[k] for k in kwargs])

>>> print f(lon=360, lat=180)
[u'print f(lon=360, lat=180)\n']
(180, 360)

>>> print f(lat=180, lon=360)
[u'print f(lat=180, lon=360)\n']
(180, 360)

一个可以解决的问题,但是由于stack()捕获了所有内容,因此存在一些无法解决的问题:

>>> print (f(lon=360, lat=180), f(lat=180, lon=360))
[u'print (f(lon=360, lat=180), f(lat=180, lon=360))\n']
[u'print (f(lon=360, lat=180), f(lat=180, lon=360))\n']
((180, 360), (180, 360))

还有我不知道的其他检查技巧可以解决此问题吗?(我对这个模块不熟悉)我可以想象得到括号之间正确的代码lon=360, lat=180应该是可行的,不是吗?

因此,我第一次感觉到python在根据所有可用信息(用户提供的订购是有价值的信息!)进行理论上可行的操作方面遇到了困难。

我在那儿读过尼克的有趣建议:https : //mail.python.org/pipermail/python-ideas/2011-January/009054.html ,想知道这个想法是否已经向前发展了?

我明白了为什么一般不要求有序的** kwarg,但是针对这些罕见情况的补丁会很整洁。任何人都知道可靠的骇客吗?

注意:这与熊猫无关,我实际上是在尝试为它开发一种轻量级的替代品,其用法仍然非常接近numpy。即将发布gitHub链接。

编辑:注意,这与dimarray的交互使用有关。无论如何都需要双重语法。

EDIT2:我还看到一些反论点,即知道数据没有排序的情况也可以视为有价值的信息,因为它使Dimarray可以自由检查values形状并自动调整顺序。甚至可能是不记得数据的维度比两个维度具有相同的大小更经常发生。因此,现在,我想在模棱两可的情况下引发错误,要求用户提供names参数就可以了。尽管如此,自由地做出这样的选择(Dimarray类应该如何表现)而不是受到python缺少功能的约束是很巧妙的。

编辑3,解决方案:在kazagistar建议之后:

我没有提到其他可选属性参数,例如name=""units="",以及与切片相关的其他几个参数,因此该*args构造需要在上进行关键字名称测试kwargs

总之,有很多可能性:

*选择a:保留当前语法

a = Dimarray(values, lon=mylon, lat=mylat, name="myarray")
a = Dimarray(values, [mylat, mylon], ["lat", "lon"], name="myarray")

*选择b:kazagistar的第二个建议,通过删除轴定义 **kwargs

a = Dimarray(values, ("lat", mylat), ("lon",mylon), name="myarray")

*选择c:kazagistar的第二个建议,通过可选的轴定义**kwargs (请注意,这涉及names=从中提取**kwargs,请参见下面的背景)

a = Dimarray(values, lon=mylon, lat=mylat, name="myarray")
a = Dimarray(values, ("lat", mylat), ("lon",mylon), name="myarray")

*选择d:kazagistar的第3条建议,并通过 **kwargs

a = Dimarray(values, lon=mylon, lat=mylat, name="myarray")
a = Dimarray(values, [("lat", mylat), ("lon",mylon)], name="myarray")

嗯,这归结为美学和一些设计问题(在交互模式下,惰性订购是一项重要功能吗?)。我在b)和c)之间犹豫。我不确定**杂货真的带来了什么。具有讽刺意味的是,当我开始思考时,我开始批评的东西成了一个功能

非常感谢您的回答。我会将问题标记为已回答,但是非常欢迎您投票给a),b)c)或d)!

=====================

编辑4更好的解决方案:选择a)!!,但是添加了from_tuples类方法。这样做的原因是允许更多自由度。如果未提供轴名称,则会自动将其生成为“ x0”,“ x1”等。要像熊猫一样使用,但要使用轴命名。这也避免了将轴属性混合到** kwarg中,而仅将其留给轴使用。一旦我完成了文档,就会有更多内容。

a = Dimarray(values, lon=mylon, lat=mylat, name="myarray")
a = Dimarray(values, [mylat, mylon], ["lat", "lon"], name="myarray")
a = Dimarray.from_tuples(values, ("lat", mylat), ("lon",mylon), name="myarray")

编辑5更多的pythonic解决方案?:在用户api方面类似于上面的EDIT 4,但通过包装dimarray,但对如何实例化Dimarray却非常严格。这也符合kazagistar提出的精神。

 from dimarray import dimarray, Dimarray 

 a = dimarray(values, lon=mylon, lat=mylat, name="myarray") # error if lon and lat have same size
 b = dimarray(values, [("lat", mylat), ("lon",mylon)], name="myarray")
 c = dimarray(values, [mylat, mylon, ...], ['lat','lon',...], name="myarray")
 d = dimarray(values, [mylat, mylon, ...], name="myarray2")

从类本身:

 e = Dimarray.from_dict(values, lon=mylon, lat=mylat) # error if lon and lat have same size
 e.set(name="myarray", inplace=True)
 f = Dimarray.from_tuples(values, ("lat", mylat), ("lon",mylon), name="myarray")
 g = Dimarray.from_list(values, [mylat, mylon, ...], ['lat','lon',...], name="myarray")
 h = Dimarray.from_list(values, [mylat, mylon, ...], name="myarray")

在d)和h)情况下,轴会自动命名为“ x0”,“ x1”,依此类推,除非mylat,mylon实际上属于Axis类(我在本文中未提及,但是Axes和Axis会这样做)工作,以建立轴并处理分度)。

说明:

class Dimarray(object):
    """ ndarray with meaningful dimensions and clean interface
    """
    def __init__(self, values, axes, **kwargs):
        assert isinstance(axes, Axes), "axes must be an instance of Axes"
        self.values = values
        self.axes = axes
        self.__dict__.update(kwargs)

    @classmethod
    def from_tuples(cls, values, *args, **kwargs):
        axes = Axes.from_tuples(*args)
        return cls(values, axes)

    @classmethod
    def from_list(cls, values, axes, names=None, **kwargs):
        if names is None:
            names = ["x{}".format(i) for i in range(len(axes))]
        return cls.from_tuples(values, *zip(axes, names), **kwargs)

    @classmethod
    def from_dict(cls, values, names=None,**kwargs):
        axes = Axes.from_dict(shape=values.shape, names=names, **kwargs)
        # with necessary assert statements in the above
        return cls(values, axes)

这是技巧(示意上):

def dimarray(values, axes=None, names=None, name=..,units=..., **kwargs):
    """ my wrapper with all fancy options
    """
    if len(kwargs) > 0:
        new = Dimarray.from_dict(values, axes, **kwargs) 

    elif axes[0] is tuple:
        new = Dimarray.from_tuples(values, *axes, **kwargs) 

    else:
        new = Dimarray.from_list(values, axes, names=names, **kwargs) 

    # reserved attributes
    new.set(name=name, units=units, ..., inplace=True) 

    return new

实际上,我们松散的唯一一件事就是* args语法,它不能容纳这么多的选项。但这很好。

而且它也使子类化变得容易。这里的python专家感觉如何?

(整个讨论实际上可以分为两部分)

=====================

一点背景知识(仅在情况a),b),c),d)下编辑(部分过时),以防万一您感兴趣:

*选择涉及:

def __init__(self, values, axes=None, names=None, units="",name="",..., **kwargs):
    """ schematic representation of Dimarray's init method
    """
    # automatic ordering according to values' shape (unless names is also provided)
    # the user is allowed to forget about the exact shape of the array
    if len(kwargs) > 0:
        axes = Axes.from_dict(shape=values.shape, names=names, **kwargs)

    # otherwise initialize from list
    # exact ordering + more freedom in axis naming 
    else:
        axes = Axes.from_list(axes, names)

    ...  # check consistency

    self.values = values
    self.axes = axes
    self.name = name
    self.units = units         

*选择b)和c)施加:

def __init__(self, values, *args, **kwargs):
    ...

b)所有属性都自然通过kwargs传递self.__dict__.update(kwargs)。这很干净。

c)需要过滤关键字参数:

def __init__(self, values, *args, **kwargs):
   """ most flexible for interactive use
   """
   # filter out known attributes
   default_attrs = {'name':'', 'units':'', ...} 
   for k in kwargs:
       if k in 'name', 'units', ...:
           setattr(self, k) = kwargs.pop(k)
       else:
           setattr(self, k) = default_attrs[k]

   # same as before
   if len(kwargs) > 0:
       axes = Axes.from_dict(shape=values.shape, names=names, **kwargs)

   # same, just unzip
   else:
       names, numpy_axes = zip(*args)
       axes = Axes.from_list(numpy_axes, names)

实际上,这非常好用,唯一(次要)的缺点是name =“”,units =“”的默认参数以及其他一些更相关的参数无法通过检查或完成来访问。

*选择d:清除 __init__

def __init__(self, values, axes, name="", units="", ..., **kwaxes)

但是确实有些冗长。

==========

编辑,仅供参考:我最终使用了一个元组列表作为axes参数,或者分别使用参数dims=以及labels=轴名称和轴值。相关项目dimarray在github上。再次感谢kazagistar。

撰写答案
今天,你开发时遇到什么问题呢?
立即提问
热门标签
PHP1.CN | 中国最专业的PHP中文社区 | PNG素材下载 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有