2011-07-18 12 views
9

Tengo métodos que aceptan dicts u otros objetos y los nombres de "campos" para obtener de esos objetos. Si el objeto es un dict, el método usa __getitem__ para recuperar la clave con nombre, o bien usa getattr para recuperar el atributo nombrado. Esto es bastante común en lenguajes de plantillas web. Por ejemplo, en una plantilla Chameleon es posible que tenga:¿Por qué Python no tiene un getattr híbrido + __getitem__ incorporado?

<p tal:content="foo.keyname">Stuff goes here</p> 

Si pasa en foo como dict como {'keyname':'bar'}, entonces foo.keyname Obtiene la tecla 'nombre de clave' para obtener 'bar'. Si foo es una instancia de una clase como:

class Foo(object): 
    keyname = 'baz' 

continuación foo.keyname recoge el valor del atributo keyname. sí camaleón implementa esa función (en el módulo chameleon.py26) así:

def lookup_attr(obj, key): 
    try: 
     return getattr(obj, key) 
    except AttributeError as exc: 
     try: 
      get = obj.__getitem__ 
     except AttributeError: 
      raise exc 
     try: 
      return get(key) 
     except KeyError: 
      raise exc 

vez implementado en my own package como:

try: 
    value = obj[attribute] 
except (KeyError, TypeError): 
    value = getattr(obj, attribute) 

Lo que pasa es que es un patrón bastante común. He visto ese método o uno muy similar en muchos módulos. Entonces, ¿por qué no es algo así en el núcleo del lenguaje, o al menos en uno de los módulos centrales? En su defecto, ¿hay una forma definitiva de cómo se podría escribir ?

+2

Me opongo enérgicamente a que "El problema es que ese es un patrón bastante común". Explícito es mejor que explícito. El acceso de atributo es algo completamente accesible por clave ... –

+1

Sí, pero todavía es bastante común en ciertos contextos, como los lenguajes de plantilla de página. Lo he visto (y necesito implementarlo) con la frecuencia suficiente como para desear que se haya refabricado en el núcleo o en un módulo común. –

+2

No hay nada como eso, probablemente porque el desarrollador de Python no quiere imponer un mal estilo de programación ... punto ... si escribe código incorrecto, entonces tiene que escribirlo explícitamente ... explícito es mejor que implícito . –

Respuesta

15

De alguna forma medio leí tu pregunta, escribí la siguiente, y luego volví a leer tu pregunta y me di cuenta de que había respondido una pregunta sutilmente diferente. Pero creo que a continuación todavía se da una respuesta después de un tipo. Si no lo cree, imagine en cambio que ha hecho esta pregunta más general, que creo que incluye la suya como una subpregunta:

"¿Por qué Python no proporciona ninguna manera incorporada de para tratar atributos y elementos intercambiables? "


He pensado bastante sobre esta cuestión, y creo que la respuesta es muy simple. Cuando crea un tipo de contenedor, es muy importante distinguir entre los atributos y artículos. Cualquier tipo de contenedor razonablemente bien desarrollado tendrá una serie de atributos, a menudo aunque no siempre métodos, que le permitan administrar sus contenidos de manera elegante. Entonces, por ejemplo, un dict tiene items, values, keys, iterkeys y así sucesivamente. Se accede a todos estos atributos utilizando la notación .. Los elementos, por otro lado, se accede usando la notación []. Entonces no puede haber colisiones.

¿Qué ocurre cuando se habilita el acceso a elementos utilizando la notación .? De repente tiene espacios de nombres superpuestos. ¿Cómo manejas las colisiones ahora? Si subclasifica un dict y le da esta funcionalidad, o bien no puede usar claves como items como regla, o tiene que crear algún tipo de jerarquía de espacio de nombres. La primera opción crea una regla onerosa, difícil de seguir y difícil de aplicar. La segunda opción crea una cantidad molesta de complejidad, sin resolver completamente el problema de colisión, ya que aún debe tener una interfaz alternativa para especificar si desea items el elemento o items el atributo.

Ahora, para ciertos tipos de tipos muy primitivos, esto es aceptable. Esa es probablemente la razón por la que hay namedtuple en la biblioteca estándar, por ejemplo. (Sin embargo, tenga en cuenta que namedtuple está sujeto a estos mismos problemas, que es probablemente por qué se implementa como una función de la fábrica (previene la herencia) y utiliza extraños, nombres de los métodos privados como _asdict.)

También es muy, muy, muy fácil para crear una subclase de object sin atributos (públicos) y use setattr en ella. Es incluso bastante fácil de anular __getitem__, __setitem__ y __delitem__ para invocar __getattribute__, __setattr__ y __delattr__, por lo que el acceso artículo apenas se convierte en azúcar sintáctica para getattr(), setattr(), etc. (Aunque eso es un poco más cuestionable, ya que crea un comportamiento algo inesperado.)

Pero para cualquier tipo de clase de contenedor bien desarrollada de la que desee poder expandirse y heredarse, agregando atributos nuevos y útiles, un híbrido __getattr__ + __getitem__ sería, francamente, un enorme PITA.

4

Puede escribir fácilmente su propia subclase dict que se comporta de esta manera de forma nativa. Una implementación mínima, lo que me gusta llamar una "pila" de atributos, es de esta manera:

class Pile(dict): 
    def __getattr__(self, key): 
     return self[key] 
    def __setattr__(self, key, value): 
     self[key] = value 

Desafortunadamente, si usted necesita para ser capaz de hacer frente a cualquiera de los diccionarios o los objetos de atributos cargados pasados ​​a usted, en lugar de tener control del objeto desde el principio, esto no ayudará.

En su situación, probablemente usaría algo muy parecido a lo que tiene, excepto dividirlo en una función para que no tenga que repetirlo todo el tiempo.

+2

Eso es justo; Realmente no tengo control sobre lo que pasa y quiero que sea lo más fácil posible para los usuarios de mi paquete, de preferencia usando la misma semántica que todos los demás que ya lo han resuelto. :-) Gracias por la respuesta, sin embargo. Perdón por quien lo rechazó. : - / –

5

Lo más parecido en la biblioteca estándar Python es un namedtuple(), http://docs.python.org/dev/library/collections.html#collections.namedtuple

Foo = namedtuple('Foo', ['key', 'attribute']) 
foo = Foo(5, attribute=13) 
print foo[1] 
print foo.key 

o puede definir fácilmente su propio tipo que siempre se almacena realmente en él es dict pero permite la aparición de la configuración de atributos y conseguir :

class MyDict(dict): 
    def __getattr__(self, attr): 
     return self[attr] 
    def __setattr__(self, attr, value): 
     self[attr] = value 

d = MyDict() 

d.a = 3 
d[3] = 'a' 
print(d['a']) # 3 
print(d[3]) # 'a' 
print(d['b']) # Returns a keyerror 

Pero no haga d.3 porque eso es un error de sintaxis. Por supuesto, existen maneras más complicadas de crear un tipo de almacenamiento híbrido como este, busque en la web muchos ejemplos.

En cuanto a cómo comprobar ambos, la forma de camaleón se ve completa. Cuando se trata de "por qué no hay una manera de hacer ambas cosas en la biblioteca estándar" es porque la ambigüedad es MALA. Sí, tenemos tipo de pato y otros tipos de disfrazados en python, y las clases son realmente solo diccionarios de todos modos, pero en algún momento queremos diferentes funcionalidades de un contenedor como un dict o una lista de las que queremos de una clase, con su orden de resolución de métodos , anulación, etc.

Cuestiones relacionadas