5

以前は、"thisattr.thatattr.blaattar" IE のように連鎖的に属性を呼び出すためにreduce 関数とgetattr 関数を使用していました。

reduce(getattr, 'xattr.yattr.zattr'.split('.'), myobject)

完全に正常に動作しますが、新しい要件があります。文字列は、「thisattr.thatattr[2].blaattar」などの特定の番号の属性を呼び出すことができます。

reduce(getattr, 'xattr.yattr[2].zattr'.split('.'), myobject)

今は機能しませんxattr object has no attribute 'yattr[2]'。エラーが発生します。

どちらの方法でも機能する、これに対するエレガントなソリューションは何でしょうか?

よろしく

4

6 に答える 6

1

そして後で、属性を取得するのではなく、何らかのメソッドを呼び出したいと思うかもしれません。Pythonアプローチの一部をすばやく再実装すると、悪夢になります。getattr / getitemサポートの現在の要件でさえ、ワンライナーとして解決することはできません。

代わりに、Python自体を使用してPythonを解釈することができます。

# Create some object for testing
>>> class A(object):
...     b = None
... 
>>> a = A()
>>> a.b = A()
>>> a.b.b = A()
>>> a.b.b.b = [A(), A(), A(), A()]
>>> a.b.b.b[1].b
>>> a.b.b.b[1].b = "Some result"
>>> 
>>> ctx = {'obj':a, 'val':None}
>>> exec("val = obj.{0}".format('b.b.b[1].b')) in ctx
>>> ctx['val']
'Some result'
于 2010-07-30T14:55:07.590 に答える
1

あなたは試すことができます:

import re
extended_split = re.compile(r'''\[\d+\]|[^\[.]+''').findall

def extended_getattr(obj, comp):
    if comp[0] == '[':
        return obj[int(comp[1:-1])]
    else:
        return getattr(obj, comp)

reduce(extended_getattr, extended_split('xattr.yattr[2].zattr'), myobject)

[…]内部のものは非負の10進数であると想定していることに注意してください。


パフォーマンスが気になる場合はeval、私のテストよりも高速です。

~:491$ python -m timeit -s 'from z import f1, f3, f, rs' 'f3(rs, "f")'   # eval
100 loops, best of 3: 5.62 msec per loop

~:492$ python -m timeit -s 'from z import f1, f3, f, rs' 'f1(rs, f)'     # my method
100 loops, best of 3: 4.69 msec per loop

の内容z.py:

import re
import random
from functools import reduce

extended_split = re.compile(r'''\[\d+\]|[^\[.]+''').findall

def extended_getattr(obj, comp):
    if comp[0] == '[':
        return obj[int(comp[1:-1])]
    else:
        return getattr(obj, comp)

class Foo(object):
    def __init__(self):
        self.foo = self

    def __getitem__(self, i):
        return self

def construct_random_string():
    yield 'foo'
    for i in range(2000):
        if random.randrange(2):
            yield '.foo'
        else:
            yield '[0]'


random.seed(0)  # to ensure fair comparison
rs = ''.join(construct_random_string())

f = Foo()

def f1(names, obj):
    return reduce(extended_getattr, extended_split(names), obj)

def f3(attrstring, objname) :
    return eval( '%s.%s' % (objname, attrstring) )
于 2010-07-30T15:40:37.207 に答える
0

あなたはする必要があります

  1. xattr.yattrを取得する
  2. その2番目のアイテムを取得します
  3. 2番目のアイテムのうち、zattrを取得します

ご覧のとおり、これには2つの異なる操作が含まれていました。reduceそれはできません(エレガントに)。両方で機能するソリューションでは、文字列を解析して、インデックス付きアクセスが必要な場所を検出する必要があります。単純だが壊れやすい(つまり、BSを供給した場合、未定義の動作をする)ソリューションは次のようになります。

def extended_chain_getattr(names, obj):
    import re
    result = obj        
    for name in names.split('.'):
        name_match = re.match(r'([a-zA-Z_][a-zA-Z0-9_]*)(\[\d\])?', name)
        assert name_match is not None
        result = getattr(result, name_match.group(1))
        if len(name_match.groups()) == 2:
            index = int(name_match.group(2))
            result = result[index]
    return result

頭のてっぺんから、したがってテストされていません。

于 2010-07-30T14:49:44.703 に答える
0

私はこれを使います

reduce(lambda i, j: getattr(i, j), 'xattr.yattr.zattr'.split('.'), myobject)

于 2015-09-18T06:01:14.297 に答える
0

スライスとネストされたリスト表記を処理する小さなパーサーを次に示します。

# define class that we can just add attributes to
class Bag(object): pass

z = Bag()
z.xattr = Bag()
z.xattr.yattr = [Bag(), Bag(), Bag()]
z.xattr.yattr[2].zattr = 100
z.xattr.yattr[1] = [0,1,2,3,4,5]

from pyparsing import *

LBRACK,RBRACK = map(Suppress,'[]')
ident = Word(alphas+"_", alphanums+"_")
integer = Word(nums+'-',nums).setParseAction(lambda t:int(t[0]))
NONE = Literal("None").setParseAction(replaceWith(None))
indexref = LBRACK + Group(delimitedList((Optional(integer|NONE,None)), delim=':')) + RBRACK
compoundAttr = delimitedList(Group(ident("name") + ZeroOrMore(indexref)("index")), delim='.')

def lookup(ob, attr):
    try:
        attrParts = compoundAttr.parseString(attr)
    except ParseException:
        raise AttributeError("could not resolve compound attribute '%s'" % attr)

    # remaining code will raise AttributeError or IndexError as appropriate

    ret = ob
    for a in attrParts:
        ret = getattr(ret, a.name)
        if a.index:
            for i in a.index:
                if len(i) == 1:
                    ret = ret[i[0]]
                else:
                    ret = ret[slice(*i.asList())]
    return ret


print len(lookup(z, 'xattr.yattr'))
print len(lookup(z, 'xattr.yattr[1:3]'))
print len(lookup(z, 'xattr.yattr[None:3]'))
print lookup(z, 'xattr.yattr[1][None:4]')
print sum(lookup(z, 'xattr.yattr[1][:4]'))
print lookup(z, 'xattr.yattr[2].zattr')
于 2010-07-30T18:29:18.643 に答える
0

属性の選択とメソッド呼び出しを組み合わせたいので、あなたが求めていることは非常に難しいようです(インデックスは呼び出しの単なる砂糖であるため)。関数の呼び出しは、getattr を使用してバインドされたメソッドを提供するだけで簡単に実行できますが、引数を含む文字列の部分を実際の引数に変換する必要があります。

とにかく引数を計算するために eval() が必要になることを考えると、すべてを単に eval しないのはなぜですか?

def proc(objname, attrstring ) :
  return eval( '%s.%s' % (objname,attrstring) )

あなたの例は次のとおりです。

proc("myobject", "xattr.yattr[2].zattr")
于 2010-07-30T15:05:36.597 に答える