检索通过** kwargs传递的关键字参数的顺序在我正在处理的特定项目中非常有用。它是关于制作一种具有有意义尺寸的n-d 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阵列。
我会非常感兴趣。我现在能想到的最好的方法是使用inspect module的堆栈方法来解析调用者的语句:
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 并且想知道这个想法是否已经以某种方式向前推进了?
我明白为什么一般来说有一个有序的** kwargs是不可取的,但这些罕见情况的补丁将是整洁的。谁知道可靠的黑客?
注意:这不是关于熊猫的,我实际上是在尝试开发一种轻量级的替代品,它的用法仍然非常接近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)之间犹豫不决。我不确定** kwargs真的带来了什么。具有讽刺意味的是,当我更多地考虑它时,我开始批评的内容变成了特征 ......
非常感谢您的回答。我会将问题标记为已回答,但欢迎您投票支持a),b)c)或d)!
=====================
编辑4 :更好的解决方案:选择a)!!,但添加了from_tuples类方法。其原因是允许一个更大的自由度。如果未提供轴名称,它们将自动生成为" x0"," x1"等...真的像熊猫一样使用,但是用轴命名。这也避免了将轴和属性混合到** kwargs中,并将其仅留给轴。一旦我完成了文档,就会很快就会有更多。
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解决方案?:类似于上面的EDIT 4用户api,但是通过包装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)仅),以防万一你感兴趣:
*选择a涉及:
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。
答案 0 :(得分:4)
不,您无法知道将项目添加到字典的顺序,因为这样做会显着增加实施指南针的复杂性。 (当你真的需要这个的时候,collections.OrderedDict已经覆盖了你。)
但是,您是否考虑过一些基本的替代语法?例如:
a = Dimarray(values, 'lat', lat, 'lon', lon)
或(可能是最好的选择)
a = Dimarray(values, ('lat', lat), ('lon', lon))
或(最明确的)
a = Dimarray(values, [('lat', lat), ('lon', lon)])
但在某种程度上,需要排序本质上是位置性的。 ** kwargs经常被滥用于标记,但参数名称通常不应该是“数据”,因为以编程方式设置是一种痛苦。只需使用元组清楚关联的数据的两个部分,并使用列表来保持排序,并提供强大的断言+错误消息,以便在输入无效时清除它们以及原因。
答案 1 :(得分:1)
有专门用来处理这个问题的模块:
https://github.com/claylabs/ordered-keyword-args
def multiple_kwarguments(first , **lotsofothers):
print first
for i,other in lotsofothers.items():
print other
return True
multiple_kwarguments("first", second="second", third="third" ,fourth="fourth" ,fifth="fifth")
输出:
first
second
fifth
fourth
third
from orderedkwargs import ordered kwargs
@orderedkwargs
def mutliple_kwarguments(first , *lotsofothers):
print first
for i, other in lotsofothers:
print other
return True
mutliple_kwarguments("first", second="second", third="third" ,fourth="fourth" ,fifth="fifth")
输出:
first
second
third
fourth
fifth
注意:在函数上方使用带有装饰器的模块时需要单个星号。