Python инвентаризация объектов

Я хочу иметь возможность выполнять "математические" операции над экземплярами объектов. давайте предположим, что у меня есть Fruit класс, а затем дополнительный Orange(Fruit) а также Apple(Fruit) классы.

AppleУ него есть цветовой атрибут, и класс знает, что "красные яблоки" отличаются от "зеленых" или "желтых" яблок.

теперь я хочу иметь возможность:

1) представляют количества конкретного фруктового экземпляра. например, 3 красных яблока или 2 апельсина или 1 банан (да, для этого, вероятно, потребуется новый класс бананов).

2) представляют "Фруктовые мешки". например, 1 красное яблоко и 2 апельсина. или 1 красное яблоко и 2 апельсина

3) работать с "Фруктовыми пакетами" и "Фруктовыми количествами", как и следовало ожидать. т.е. "2 красных яблока" + "1 красное яблоко и 1 апельсин" => "3 красных яблока и 1 апельсин" и, возможно, даже "2 красных яблока" * 2 => "4 красных яблока" и так далее.

Теперь, в некоторых аспектах это выглядит похоже на то, что это Counter класс делает, но я не уверен, как я должен идти о реализации этого.

Моя основная дилемма заключается в том, что кажется, что Counter class определяет, являются ли два объекта одинаковыми, основываясь на их хэше, и группирует их на основе одного и того же хэша, и не дает мне возможности решить, что ключом к группе "3 красных яблока" является "красное яблоко".

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

Реальное применение, конечно, более сложное, но я думаю, что решение проблемы, как я описал здесь, было бы легко распространить на мои реальные требования.

Какой подход вы предложите мне? Можно ли использовать Counter как или мне нужно сделать мою полную реализацию?

РЕДАКТИРОВАТЬ 1: еще несколько мыслей, мне действительно нравится комментарий @jbndlr о "Вы говорите целое число с значением 3, а не с 3 целыми числами". Но там есть разница...

Как бы вы посчитали вместе целое число w/ значение 1, целое число w/ значение 1 и третье целое число w/ значение 4? три целых числа были бы правильным ответом? или, может быть, "2 целых числа целых с значением 1 и 1 целое число с значением 4?

Подсчет не похож на суммирование...

С некоторого абстрактного уровня это заставило бы send посчитать вещи в зависимости от их типа, что заставило бы вас провести четкое различие между "1 red_apple" и "1 apple, который красный" в том смысле, что "1 red_apple" + "1 green_apple 'это просто'1 red_apple + 1 green_apple ' (потому что green_apple отличается от red_apple), в то время как' 1 яблоко, которое является красным ' + '1 яблоко, которое является зеленым ', может быть как'2 яблока ' (потому что яблоко по любому другому цвету будет как яблочный)

Проблема в том, что если ваш домен приложения требует, чтобы вы группировали яблоки по цвету, вы все равно не хотите заставлять создавать 3 разных класса яблок, вы просто хотите различать экземпляры яблок по цвету.

Было бы то, что я иду по этому пути неправильно, и что правильный способ решить, как считать, будет тот, кто выполняет фактический подсчет, чтобы вы могли предоставить схему хеширования как часть вызова некоторой функции CounterFactory, которая возвращает счетчик, который знает, что от него ожидается относительно любого предоставленного экземпляра объекта некоторого типа.

другая альтернатива состоит в том, чтобы предположить, что каждый тип может быть подсчитан только определенным образом, и класс должен знать, как правильно подсчитать его экземпляры, и предоставить что-то вроде __counting_key__ для поддержки обратно совместимого способа управления поведение класса Counter.

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

3 ответа

На самом деле вы можете настроить свои собственные классы довольно легко. Я думаю, что повторное использование чего-либо вроде Counter (или даже расширение Python с использованием нового types) будет слишком много усилий, если вы просто хотите разрешить регулярную арифметику для новых типов (здесь: классы).

В этом примере показано, как можно реализовать компараторы и перегрузку операторов; Посмотрите, как классы используются в конце этого примера сценария:

class FruitKind(object):
    def __init__(self, count=1):
        self.count = count


class Apple(FruitKind):
    def __init__(self, color, count=1):
        super(Apple, self).__init__(count)
        if isinstance(color, basestring):
            self.color = color
        else:
            raise TypeError('Color must be string-like.')

    def __str__(self):
        return '{} {} Apple(s)'.format(self.count, self.color)

    def __eq__(self, other):
        if all([
                type(self) is type(other),
                self.color == other.color
                ]):
            return True
        return False

    def __ne__(self, other):
        return not self.__eq__(other)

    def __add__(self, other):
        if self == other:
            self.count += other.count
            return self
        else:
            raise TypeError('Cannot add different Fruits.')
    def __sub__(self, other):
        if self == other:
            self.count += other.count
            return self
        else:
            raise TypeError('Cannot subtract different Fruits.')


class FruitBag(object):
    def __init__(self, items=[]):
        self.items = items

    def __add__(self, other):
        if isinstance(other, FruitBag):
            # Merge self.items and other.items
            pass
        elif isinstance(other, FruitKind):
            # Merge other into self.items
            pass
        else:
            raise NotImplementedError(
                'Cannot add instance of {} to Fruitbag.'
                .format(str(type(other))))


if __name__ == '__main__':
    a1 = Apple('red')
    a2 = Apple('red', count=3)
    a1 += a2
    print(a1)

    a3 = Apple('green')
    try:
        a1 += a3
    except TypeError as te:
        print(te.message)

Выполнение этого приводит к следующему выводу:

4 red Apple(s)
Cannot add different Fruits.

Однако в этом примере я думаю о FruitKind как описание фрукта вместе с его атрибутами, которые делают его уникальным среди других (два яблока могут быть двумя яблоками, но здесь, цвет также используется, чтобы различать их). В результате класс, наследующий от FruitKind, такие как Apple делает, всегда также несет количество предметов.

Вы можете переопределить __hash__(self) из объектов, поэтому он рассчитывается на основе атрибутов, которые вы хотите выделить; Вы также можете переопределить __eq__ метод и другие методы сравнения.

например:

class Apple(Fruit):
    def __init__(self, ...):
        self.color = 'red'     # maybe create an Enum for the colors?

    def __hash__(self):
        return hash(('apple', self.color))   # Thanks @Jon Clements in the comments

    def __eq__(self, other):   # recommended when overwriting __hash__ - thanks @Jon Clements
        return True if self equals other, False otherwise

Возможно, вы захотите сделать этот хэш более общим... Вместо apple вы можете использовать self.__class__.__name__ или что-то еще (из комментариев @Jon Clements)

Редактировать - использовать со счетчиком ():

class FruitBasket:
    def __init__(self):
        self.fruits = []
    def add_fruit(self, fruit):
        self.fruit.append(fruit)
    def __iter__(self):
        return iterable over self.fruits

counter = Counter(FruitBasket)

Это ответ Джона Клементса, опубликованный здесь:
Я публикую его ответ как ответ сообщества вики.

class Fruit:
    def __init__(self, colour):
        self.colour = colour

    def __hash__(self):
        return hash((self.__class__.__name__, self.colour))

    def __eq__(self, other):
        return type(self) == type(other) and self.colour == other.colour

    def __repr__(self):
        return '{} ({})'.format(self.__class__.__name__, self.colour)

class Apple(Fruit):
    pass

class Berry(Fruit):
    pass

from collections import Counter

fruits = [
    Apple('red'), Apple('green'), Berry('black'), Berry('red'),
    Berry('black'), Apple('red'), Berry('red'), Apple('green'),
    Berry('blue'), Apple('pink')
]
counts = Counter(fruits)
#Counter({Apple (green): 2,
#         Apple (pink): 1,
#         Apple (red): 2,
#         Berry (black): 2,
#         Berry (blue): 1,
#         Berry (red): 2})
Другие вопросы по тегам