2010-07-30 26 views
5

je l'habitude d'utiliser les fonctions reduce and getattr pour appeler les attributs d'une manière de la chaîne comme « thisattr.thatattr.blaattar » IE:solution Pythonic à mon problème de réduire getattr

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

fonctionne parfaitement bien, mais maintenant j'ai nouvelle exigence, mes chaînes peuvent faire appel à un nombre spécifique d'un attribut en tant que tel: « thisattr.thatattr [2] .blaattar »

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

maintenant, il ne fonctionne pas, j'obtiens l'erreur xattr object has no attribute 'yattr[2]'.

Quelle serait une solution élégante à cela, qui fonctionne dans les deux cas?

Cordialement

Répondre

0

Vous devrez

  1. Get xattr.yattr
  2. Obtenez le deuxième élément de cette
  3. de la deuxième question, obtenir zattr

Comme vous peut voir, cela impliquait deux opérations différentes. reduce ne peut pas le faire (avec élégance). Une solution fonctionnant pour les deux devrait analyser la chaîne pour détecter où l'accès indexé est nécessaire. Une solution simple mais fragile (à savoir si BS se comporte de manière indéfinie ou non alimenté) ressemblerait à ceci:

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 

Du haut de ma tête, donc non testé.

+0

ne semble pas fonctionner. – kennytm

+0

Que diriez-vous d'un peu plus d'informations? – delnan

+0

Lorsque j'essaie d'obtenir 'extended_chain_getattr ('foo', Foo_obj)', il est dit 'AttributeError:' L'objet 'Foo' n'a pas d'attribut 'fo'. – kennytm

1

Et plus tard, vous pourriez souhaiter appeler une méthode plutôt que d'obtenir un attribut. Ré-implémenter des parties de l'approche python rapidement deviendra un cauchemar. Même les besoins actuels de support getattr/getitem ne peuvent pas être résolus en un seul recouvrement.

Au lieu de cela, vous pouvez simplement utiliser python lui-même pour interpréter 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' 
0

Ce que vous demandez semble assez difficile que vous voulez mélanger la sélection d'attributs avec les appels de méthode (comme l'indice est juste du sucre pour une appel). L'appel de fonctions est assez facile à faire en utilisant getattr pour vous donner une méthode liée, mais vous devez ensuite convertir la partie de la chaîne qui contient les arguments en arguments réels.

Étant donné que vous aurez besoin d'un eval() pour calculer les arguments de toute façon, pourquoi ne pas simplement évaluer le tout?

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

Votre exemple est alors:

proc("myobject", "xattr.yattr[2].zattr") 
1

Vous pouvez essayer:

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) 

Notez qu'il prend les choses à l'intérieur […] est un nombre décimal non négatif.


Si vous préoccupé par la performance, il est encore plus rapide que eval dans mon test:

~: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 

Contenu de 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)) 
0

Voici un petit analyseur pour gérer tranche et notation de liste imbriquée:

# 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') 
0

J'utilise cette

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

+0

S'il vous plaît envisager d'éditer votre message pour ajouter plus d'explications sur ce que fait votre code et pourquoi il va résoudre le problème. Une réponse qui contient pour la plupart du code (même si cela fonctionne) ne permet généralement pas au PO de comprendre son problème. – SuperBiasedMan

+0

Pourquoi utilisez-vous même un lambda? Vous pouvez simplement passer 'getattr' comme premier argument de' reduce'. –