现在说我们有:
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__方法的签名是(值,** kwargs),因为kwargs是无序词典(dict),它能做的最好的就是检查值的形状.
当然,我希望它适用于任何类型的维度:
a = Dimarray(values, x1=.., x2=...,x3=...)
所以它必须用** kwargs进行硬编码
出现模糊情况的可能性随着维度的增加而增加.
有很多方法,例如签名(值,轴,名称,** kwargs)可以做到:
a = Dimarray(values, [lat, lon], ["lat","lon"])
但是这种语法对于交互式使用(ipython)来说很麻烦,因为我希望这个包真的成为我(以及其他人)日常使用python的一部分,作为地球物理学中numpy数组的实际替代品.
我对这方面非常感兴趣.我现在能想到的最好的方法是使用inspect module的stack方法来解析调用者的语句:
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自由地检查值的形状并自动调整顺序.甚至可能不记得数据的维度比两个维度具有相同的大小更常见.所以现在,我想可以为不明确的情况引发错误,要求用户提供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可选择轴定义
(注意这涉及名称=从** 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”等…要像pandas一样使用,但使用轴命名.这也避免了将轴和属性混合到** 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)仅),以防万一你感兴趣:
*选择涉及:
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)
但确实有点冗长.
==========
EDIT,FYI:我最终使用了轴参数的元组列表,或者参数dims =和labels =分别用于轴名称和轴值.相关项目dimarray在github上.再次感谢kazagistar.
不,您无法知道将项目添加到字典中的顺序,因为这样做会显着增加实施指南针的复杂性. (因为当你真的需要这个时, collections.OrderedDict你有保障).但是,您是否考虑过一些基本的替代语法?例如:
a = Dimarray(values, 'lat', lat, 'lon', lon)
或(可能是最好的选择)
a = Dimarray(values, ('lat', lat), ('lon', lon))
或(最明确的)
a = Dimarray(values, [('lat', lat), ('lon', lon)])
但在某种程度上,需要排序本质上是位置的. ** kwargs经常被滥用于标记,但参数名称通常不应该是“数据”,因为以编程方式设置是一种痛苦.只需使用元组清楚关联的数据的两个部分,并使用列表来保持排序,并提供强大的断言错误消息,以便在输入无效时清楚说明原因.