Як викликати той самий метод для списку об'єктів?


79

Припустимо, такий код:

class Base:
    def start(self):
        pass
    def stop(self)
        pass

class A(Base):
    def start(self):
        ... do something for A
    def stop(self)
        .... do something for A

class B(Base):
    def start(self):

    def stop(self):

a1 = A(); a2 = A()
b1 = B(); b2 = B()

all = [a1, b1, b2, a2,.....]

Тепер я хочу викликати методи start і stop (можливо, також інші) для кожного об'єкта у списку all. Чи є якийсь елегантний спосіб зробити це, крім написання групи таких функцій, як

def start_all(all):
    for item in all:
        item.start()

def stop_all(all):

1
купу функцій - це дві функції?
SilentGhost

4
Це не пов’язано з вашим запитанням, але у вашому прикладі немає очевидної причини, чому вам потрібен цей базовий клас. Python буде дуже радий надати вам список непов’язаних об’єктів, і поки у всіх них є методи „start” та „stop”, ви все одно можете переглядати їх, викликаючи ці методи.
Дункан,

1
Ваше визначення Baseмарними методами, а потім визначення поведінки Aта Bвідображає поганий дизайн у Python. Замість того щоб використовувати абстрактний базовий клас, ви можете просто визначити Aі Bта використовувати їх як взаємозамінні , оскільки вони мають інтерфейс. Ваш теперішній спосіб робити речі створює марний клас, який є просто додатковими матеріалами, які вам не потрібні.
Mike Graham

Відповіді:


7

Функції * _all () настільки прості, що для кількох методів я б просто написав їх. Якщо у вас багато однакових функцій, ви можете написати загальну функцію:

def apply_on_all(seq, method, *args, **kwargs):
    for obj in seq:
         getattr(obj, method)(*args, **kwargs)

Або створіть фабрику функцій:

def create_all_applier(method, doc=None):
    def on_all(seq, *args, **kwargs):
        for obj in seq:
            getattr(obj, method)(*args, **kwargs)
    on_all.__doc__ = doc
    return on_all

start_all = create_all_applier('start', "Start all instances")
stop_all = create_all_applier('stop', "Stop all instances")
...

56
Це не здається мені простішим, ніж безпосереднє написання циклу for.
Mike Graham

178

Це спрацює

all = [a1, b1, b2, a2,.....]

map(lambda x: x.start(),all)    

простий приклад

all = ["MILK","BREAD","EGGS"]
map(lambda x:x.lower(),all)
>>>['milk','bread','eggs']

та в python3

all = ["MILK","BREAD","EGGS"]
list(map(lambda x:x.lower(),all))
>>>['milk','bread','eggs']

10
У Python 3 map повертає об'єкт карти, а не список, як у Python 2. Я не впевнений у стандартній процедурі, але я подумав, що повинен це виправити, тому подав редагування. Отже, код тепер повинен працювати належним чином і належним чином як в Python 2, так і в Python 3.
leetNightshade

1
Крім того, це схоже на більш пітонічну відповідь, вона повинна бути прийнятою. map - хороший вибір, і я ніколи раніше не використовував лямбди, тож спасибі.
leetNightshade

6
У Python 3 map()виклику функції буде недостатньо. Він створить об’єкт генератора, але не застосовуватиме лямбда-сигнали до кожного елемента списку. Лямбда буде застосована до ітерації результату map (). list(map(...))вирішить проблему.
Алекс Мусаєв

24
@dreikanter або просто робити [x.start() for x in all]?
ендоліт

6
Використання списку, а потім його викидання здається мені непитонічним. Оскільки викликаний метод є імперативним кодом з побічними ефектами, явний forцикл є більш ідіоматичним і менш дивовижним для читача. Якщо ви дійсно повинні, ви можете написати def foreach(fun, gen): for i in gen: fun(gen).
Томас

32

Здається, був би більш пітонічний спосіб зробити це, але я ще не знайшов цього.

Я іноді використовую "map", якщо викликаю одну і ту ж функцію (не метод) для групи об'єктів:

map(do_something, a_list_of_objects)

Це замінює купу коду, який виглядає так:

 do_something(a)
 do_something(b)
 do_something(c)
 ...

Але це також можна досягти за допомогою пішохідної петлі "за":

  for obj in a_list_of_objects:
       do_something(obj)

Недоліком є ​​те, що а) ви створюєте список як повернене значення з "map", яке просто викидають, і b) це може бути більш заплутаним, ніж просто варіант цикла.

Ви також можете скористатися розумінням списку, але це теж трохи образливо (ще раз, створення списку, що викидається):

  [ do_something(x) for x in a_list_of_objects ]

Для методів, я припускаю, що будь-який з них буде працювати (з однаковими застереженнями):

map(lambda x: x.method_call(), a_list_of_objects)

або

[ x.method_call() for x in a_list_of_objects ]

Отже, насправді, я думаю, що пішохідна (проте ефективна) петля "за" - це, мабуть, найкращий вибір.


4
Моєю першою реакцією була карта (лямбда x: x.member (), список). Це було схоже на досить чіткий лайнер
Марк Ессель

Хороший висновок про "пішохода" для петлі. Якби це був CL, ви мали б mapcarі mapcрозрізняти випадки "хочу список / викинути список". Але на жаль ...
Жоао

21

Підхід

for item in all:
    item.start()

проста, легка, читабельна та стисла. Це основний підхід, який Python забезпечує для цієї операції. Ви, звичайно, можете інкапсулювати його у функції, якщо це щось допомагає. Визначення спеціальної функції для цього для загального користування, швидше за все, буде менш зрозумілим, ніж просто виписування циклу for.


7

можливо map, але оскільки ви не хочете складати список, ви можете написати свій власний ...

def call_for_all(f, seq):
    for i in seq:
        f(i)

тоді ви можете зробити:

call_for_all(lamda x: x.start(), all)
call_for_all(lamda x: x.stop(), all)

до речі, все - це вбудована функція, не перезаписуйте її ;-)


6

Починаючи з Python 2.6, є operator.methodcaller функція .

Тож ви можете отримати щось більш елегантне (і швидке):

from operator import methodcaller

map(methodcaller('method_name'), list_of_objects)

1
Який найкращий підхід для python3?
Ніл

@Neil, це працює в python3, різне лише те, що mapповертає ітератор у 3, а не список
notbad.jpeg

3

Зробивши @Ants Aasmas відповідь на крок далі, ви можете створити обгортку, яка приймає будь-який виклик методу і пересилає його до всіх елементів заданого списку:

class AllOf:
    def __init__(self, elements):
        self.elements = elements
    def __getattr__(self, attr):
        def on_all(*args, **kwargs):
            for obj in self.elements:
                getattr(obj, attr)(*args, **kwargs)
        return on_all

Тоді цей клас можна використовувати так:

class Foo:
    def __init__(self, val="quux!"):
        self.val = val
    def foo(self):
        print "foo: " + self.val

a = [ Foo("foo"), Foo("bar"), Foo()]
AllOf(a).foo()

Що дає такий результат:

foo: foo
foo: бар
foo: quux!

З деякою працею та винахідливістю, можливо, це може бути покращено для обробки атрибутів (повернення списку значень атрибутів).

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.