2010-07-30 17 views
5

Solía ​​usar reduce and getattr funciones para llamar atributos de una manera cadena como "thisattr.thatattr.blaattar" IE:solución a mi problema Pythonic reducir getattr

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

funciona perfectamente bien, sin embargo, ahora tengo una nuevo requisito, mis cuerdas pueden llamar a un número específico de un atributo como por ejemplo: "thisattr.thatattr [2] .blaattar"

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

Ahora no funciona, me sale xattr object has no attribute 'yattr[2]' error.

¿Cuál sería una solución elegent a esto, que funciona de cualquier manera?

Saludos

Respuesta

0

Tendrá que

  1. Obtener xattr.yattr
  2. conseguir el segundo elemento de esa
  3. Del segundo artículo, obtener zattr

Como puede ver, esto involucró dos operaciones diferentes. reduce no puede hacer eso (elegantemente). Una solución que funcione para ambos debería analizar la cadena para detectar dónde se necesita el acceso indexado. Una solución simple pero frágil (es decir, se comporta indefinido si BS alimentado) se vería así:

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 

De la parte superior de la cabeza, por lo tanto, no probado.

+0

no parece funcionar. – kennytm

+0

¿Qué tal un poco más de información? – delnan

+0

Cuando intento obtener 'extended_chain_getattr ('foo', Foo_obj)', dice 'AttributeError: 'Foo' objeto no tiene ningún atributo 'fo''. – kennytm

1

Y más tarde puede que desee llamar a algún método en lugar de obtener el atributo. Volver a implementar partes del enfoque de Python rápidamente se convertirá en una pesadilla. Incluso el requisito actual del soporte getattr/getitem no se puede resolver como un solo liner.

En cambio, es posible que utilices pitón interpretar por sí mismo pitón,

# 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

Lo que está pidiendo parece bastante difícil, ya que se quiere mezclar selección de atributos con las llamadas de método (como el índice es sólo azúcar para una llamada). Llamar a funciones es bastante fácil de hacer usando getattr para darle un método vinculado, pero luego necesita convertir la parte de la cadena que contiene los argumentos en los argumentos reales.

Dado que necesitará una evaluación() para calcular los argumentos de todos modos, ¿por qué no simplemente evaluar todo?

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

Su ejemplo es entonces:

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

Usted podría intentar:

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) 

Nota que asume las cosas dentro […] es un número decimal no negativo.


En caso de que la preocupación por el rendimiento, todavía es más rápido que eval en mi prueba:

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

contenido 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

Aquí es un pequeño analizador de manejar rebanada y notación de lista anidada:

# 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

Yo uso este

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

+0

Considere editar su publicación para agregar más explicaciones sobre lo que hace su código y por qué lo resolverá. Una respuesta que solo contenga código (incluso si funciona) generalmente no ayudará al OP a entender su problema. – SuperBiasedMan

+0

¿Por qué estás usando una lambda? Simplemente puede pasar 'getattr' como primer argumento de' reduce'. –

Cuestiones relacionadas