Bienvenue sur IndexError.

Ici vous pouvez poser des questions sur Python et le Framework Django.

Mais aussi sur les technos front comme React, Angular, Typescript et Javascript en général.

Consultez la FAQ pour améliorer vos chances d'avoir des réponses à vos questions.

Appliquer la méthode d'un container à tous les éléments qu'il contient: ça existe en standard?

+2 votes

La classe suivante accepte n'importe quelle méthode, et l'applique aux éléments qu'elle contient.
Existe-t-il un object standard en python avec ce comportement?

 class Container:
    def __init__(self, items=[]): self.items = list(items)
    def add(self, item): self.items.append(item)
    def __getattr__(self, name):
        def on_all(*args, **kwargs):
            for item in self.items:
                getattr(item, name)(*args, **kwargs)
        return on_all
demandé 24-Oct-2015 par anonyme

2 Réponses

+2 votes

Je ne pense pas que ça existe en effet.

La raison qu'on invoquera souvent, c'est que la syntaxe pour le faire soi-même est assez légère pour se passer d'un objet standard dédié :

for elem in seq:
    elem.method(arg1, arg2)

Ou encore avec une liste en intension si on veut récupérer le réultat :

result = [e.method(arg1, arg2) for e in seq]
répondu 24-Oct-2015 par bubulle (2,226 points)
+3 votes

C'est plus ou moins le comportement de la builtin map (enfin, pas vraiment puisque map retourne les valeurs calculées, et retourne un itérateur depuis python3, mais c'est en gros la même idée), à ceci près qu'un code utilisant ce genre de container sera totalement contre-intuitif et incompréhensible pour celui qui ne connait pas le fonctionnement du container en question.

Bref, le genre de truc à éviter AMHA. Je préfère de loin un code explicite comme map(foo, lst), qu'une écriture lst.foo() à base de magie noire.

répondu 24-Oct-2015 par yoch (2,510 points)

Sauf que, pour chaque item de lst ou container,

  • map(foo, lst) effectue foo(item)
  • alors que container.foo(params) effectue item.foo(params)

Pour obtenir la même chose avec map(), sauf à créer une fonction spécifique pour chaque item.méthode(params) qu'on veut appeler, ou peut-être avec des décorateurs, je ne vois pas trop comment faire, et ça devient vite lourd.
En fait, si on veut rester dans le standard, c'est encore la liste en intention proposée par bubulle qui semble le plus simple pour la fonctionnalité demandée:

result = [e.method(arg1, arg2) for e in seq]

Rien n'empêche de faire (même si une liste en intension est sans doute plus lisible). :

result = map(lambda e: e.method(arg1, arg2), seq)

Et si ta méthode n'a pas d'arguments, il y a encore plus simple, par exemple :

map(str.upper, lst)  # puisque s.upper() == str.upper(s)

Quoi qu'il en soit, je trouve que le container dans le post original est une fausse bonne idée. D'ailleurs, si le but est d'appliquer un effet de bord (print, log, etc) et non de récupérer une liste de nouvelles valeurs, il n'existe rien de mieux qu'une boucle for (il n'y a pas de réel équivalent au forEach de JS).

...