2016-05-10 5 views
0

У меня есть некоторые расчеты по биологическим данным. Каждая функция вычисляет общие, средние, минимальные значения для одного списка объектов. Идея состоит в том, что у меня много разных списков, каждый из которых предназначен для другого типа объекта. Я не хочу повторять свой код для каждой функции, просто меняя линию «за» и вызов метода объекта!Объединение подобных функций в один

Например: функция

Объем:

def calculate_volume(self): 
    total = 0 
    min = sys.maxint 
    max = -1 
    compartments_counter = 0 

    for n in self.nodes: 

     compartments_counter += 1 
     current = n.get_compartment_volume() 
     if min > current: 
      min = current 
     if max < current: 
      max = current 

     total += current 

    avg = float(total)/compartments_counter 
    return total, avg, min, max 

Сократительная функция:

def get_contraction(self): 
    total = 0 
    min = sys.maxint 
    max = -1 
    branches_count = self.branches.__len__() 

    for branch in self.branches: 

     current = branch.get_contraction() 
     if min > current: 
      min = current 
     if max < current: 
      max = current 

     total += current 

    avg = float(total)/branches_count 

    return total, avg, min, max 

Обе функции выглядят почти одинаково, только немного модификация!

Я знаю, что могу использовать сумму, min, max, ... и т. Д., Но когда я применяю их для своих значений, они занимают больше времени, чем делают их в цикле, потому что их невозможно вызвать сразу.

Я просто хочу знать, если это правильный способ написать функцию для каждого расчета? (т. е. профессиональный способ?) Или, может быть, я могу написать одну функцию и передать список, тип объекта и метод вызова.

+0

Я думаю, теперь я понимаю, что вы имеете в виду под «нельзя назвать сразу», но я d быть крайне скептически, если вы говорите, что перетаскивание 4 вычислений в одну функцию python происходит быстрее, чем 4 встроенных вызова C по тем же данным. Да: он будет перебирать данные 4 раза, НО каждый раз оптимизирован. Мои тесты показывают, что встроенные команды выигрывают каждый раз (см. Мой отредактированный ответ ниже). – smassey

+0

Это должно быть, вероятно, на http: //codereview.stackexchange.com –

ответ

2

Трудно сказать, не видя остальной код, но из ограниченного представления, которое я считаю, вы не должны иметь эти функции в методах вообще. Я также действительно не понимаю ваши аргументы в пользу того, что вы не используете встроенные функции («их нельзя сразу вызвать?»). Если вы подразумеваете, что внедрение 4 статистических методов за один проход в python происходит быстрее, чем 4 прохода в builtin (C), то я боюсь, что у вас есть очень неправильное предположение.

Это говорит, вот мой взгляд на проблему:

def get_stats(l): 
    s = sum(l) 
    return (
     s, 
     float(s)/len(l), 
     min(l), 
     max(l)) 

# then create numeric lists from your data and send 'em through: 

node_volumes = [n.get_compartment_volume() for n in self.nodes] 
branches = [b.get_contraction() for b in self.branches] 

# ... 

total_1, avg_1, min_1, max_1 = get_stats(node_volumes) 
total_2, avg_2, min_2, max_2 = get_stats(branches) 

EDIT

Некоторые тесты, чтобы доказать, что это выиграть встроенный:

MINE.py

import sys 

def get_stats(l): 
    s = sum(l) 
    return (
     s, 
     float(s)/len(l), 
     min(l), 
     max(l) 
    ) 


branches = [i for i in xrange(10000000)] 

print get_stats(branches) 

Versus YOURS.py

import sys 

branches = [i for i in xrange(10000000)] 

total = 0 
min = sys.maxint 
max = -1 
branches_count = branches.__len__() 

for current in branches: 
    if min > current: 
     min = current 
    if max < current: 
     max = current 

    total += current 

avg = float(total)/branches_count 

print total, avg, min, max 

И, наконец, с некоторыми таймерами:

[email protected]:/tmp $ time python mine.py 
(49999995000000, 4999999.5, 0, 9999999) 

real 0m1.225s 
user 0m0.996s 
sys 0m0.228s 
[email protected]:/tmp $ time python yours.py 
49999995000000 4999999.5 0 9999999 

real 0m2.369s 
user 0m2.180s 
sys 0m0.180s 

Приветствиях

+0

Отличный ответ. Большое спасибо.^_^ –

1

первых, обратите внимание, что в то время как это, вероятно, более эффективным вызвать len(self.branches) (не называйте __len__ напрямую), более общий, чтобы увеличить счетчик в цикле, как вы делаете с calculate_volume.С учетом этого изменения, вы можете реорганизовать следующим образом:

def _stats(self, iterable, get_current): 
    total = 0.0 
    min_value = None # Slightly better 
    max_value = -1 
    counter = 0 
    for n in iterable: 
     counter += 1 
     current = get_current(n) 
     if min_value is None or min_value > current: 
      min_value = current 
     if max_value < current: 
      max_value = current 
     total += current 
    avg = total/denom 
    return total, avg, min_value, max_value 

Теперь, каждый из двух может быть реализован в терминах _stats:

import operator 

def calculate_volume(self): 
    return self._stats(self.nodes, operator.methodcaller('get_compartment_volume')) 

def get_contraction(self): 
    return self.refactor(self.branches, operator.methodcaller('get_contraction')) 

methodcaller обеспечивает функцию f таким образом, что f('method_name')(x) эквивалентно x.method_name() , что позволяет вам отменить вызов метода.

+0

Как используется 'methodcaller', отличный от' getattr'? – DeepSpace

+1

@DeepSpace Я спрашивал себя то же самое, никогда раньше этого не видел. Чтобы ответить на ваш вопрос: это не так. См. Здесь: https://docs.python.org/2/library/operator.html#operator.methodcaller – smassey

+1

'methodcaller' - это в основном удобство, чтобы избежать необходимости писать, например,' lambda obj: getattr (obj, 'get_contraction «)()'. (Это также немного более эффективно, поскольку оно реализовано на C, а не как пользовательская функция.) – chepner

1

Возможно, я могу написать одну функцию и передать список, тип объекта и метод вызова.

Наверное, вы можете определенно передать функцию, чтобы функционировать, и на самом деле это очень распространенный способ избежать повторения себя, в этом случае вы не можете, потому что каждый объект в списке имеет свой собственный метод. Поэтому вместо этого я передаю имя функции в виде строки, затем используя getattr, чтобы получить фактический вызываемый метод от объекта. Также обратите внимание, что я использую len() вместо явного вызова __len()__.

def handle_list(items_list, func_to_call): 
    total = 0 
    min = sys.maxint 
    max = -1 
    count = len(items_list) 

    for item in items_list: 

     current = getattr(item, func_to_call)() 
     if min > current: 
      min = current 
     if max < current: 
      max = current 

     total += current 

    avg = float(total)/count 

    return total, avg, min, max 
+0

Да, ваш ответ подходит к моему вопросу отлично, но выбранный быстрее в исполнении. Большое спасибо, я узнал новую технику: D –

1

Вы можете использовать getattr(instance, methodname) написать функцию для обработки списков произвольных объектов.

def averager(things, methodname): 
    count,total,min,max = 0,0,sys.maxint,-1 
    for thing in things: 
     current = getattr(thing, methodname)() 

     count += 1 
     if min > current: 
      min = current 
     if max < current: 
      max = current 
     total += current 

    avg = float(total)/branches_count 
    return total, avg, min, max 

Тогда в определениях классов нужно просто

def calculate_volume(self): return averager(self.nodes, 'get_compartment_volume') 

    def get_contraction(self): return averager(self.branches, 'get_contraction') 
+0

Да, ваш ответ подходит к моему вопросу отлично, но выбранный был быстрее в исполнении. Большое спасибо Я узнал новую технику: D –

1

Дать функцию, которая принимает другую функцию, которая знает, как извлечь значения из списка является очень распространенным явлением. Фактически, min и max оба аргумента приводят к такому и результату.

например.

items = [1, 0, -2] 
print(max(items, key=abs)) # prints -2 

Таким образом, совершенно правильно написать свою собственную функцию, которая делает то же самое. Обычно я просто создавал новый список всех значений, которые вы хотите изучить, а затем работал с ними (например, [branch.get_contraction() for branch in branches]). Но, возможно, пространство для вас - проблема, поэтому вот пример использования генератора.

def sum_avg_min_max(iterable, key=None): 
    if key is not None: 
     iter_ = (key(item) for item in iterable) 
    else: 
     # if there is no key, just use the iterable itself 
     iter_ = iter(iterable) 

    try: 
     # We don't know sensible starting values for total, min or max. So use 
     # the first value. 
     total = min_ = max_ = next(iter_) 
    except StopIteration: 
     # can't have a min or max if we have no items in the iterable... 
     raise ValueError("empty iterable") from None 
    count = 1 

    for item in iter_: 
     total += item 
     min_ = min(min_, item) 
     max_ = max(max_, item) 
     count += 1 

    return total, float(total)/count, min_, max_ 

Тогда вы могли бы использовать его как это:

class MyClass(int): 
    def square(self): 
     return self ** 2 

items = [MyClass(i) for i in range(10)] 
print(sum_avg_min_max(items, key=MyClass.square)) # prints (285, 28.5, 0, 81) 

Это работает, потому что, когда вы запрашиваете метод экземпляра из класса он дает саму вашу основную функцию (без self связанно). Поэтому мы можем использовать его в качестве ключа. например.

str.upper("hello world") == "hello world".upper() 

С более конкретным примером (предполагающие элементами в branches являются экземплярами Branch):

def get_contraction(self): 
    result = sum_avg_min_max(self.branches, key=Branch.get_contraction) 
    return result 
+0

Спасибо, что ваш ответ был очень полезным, и я узнал о новых вещах: D –

Смежные вопросы