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})