Как сделать экземпляр, специфицирующий c методы в python - PullRequest
0 голосов
/ 14 марта 2020

Итак, я столкнулся с этой проблемой, это сложно объяснить, поэтому я попробую провести аналогию с пиццей:

У нас есть следующие классы:

class Storage:
    # this seems like i should use a dict, but let's assume there is more functionality to it
    def __init__(self, **kwargs):
        self.storage = kwargs
        # use like: Storage(tomato_cans=50, mozzarella_slices=200, ready_dough=20)

    def new_item(self, item_name: str, number: int):
        self.storage[item_name] = number

    def use(self, item_name: str, number: int):
        self.storage[item_name] = self.storage.get(item_name) - number

    def buy(self, item_name: str, number: int):
        self.storage[item_name] = self.storage.get(item_name) + number

class Oven:
    def __init__(self, number_parallel):
        # number of parallel pizzas possible
        self.timers = [0] * number_parallel

    def ready(self):
        return 0 in self.timers

    def use(for_mins):
        for i, timer in enumerate(self.timers):
            if timer == 0:
                self.timers[i] = for_mins
                break

    def pass_time(mins):
        for i in range(len(self.timers)):
            self.timers[i] = max(0, self.timers[i]-mins)

class Pizza:
     def __init__(self, minutes=6, dough=1, tomato_cans=1, mozzarella_slices=8, **kwargs):
         self.ingredients = kwargs
         self.ingredients['dough'] = dough
         self.ingredients['tomato_cans'] = tomato_cans
         self.ingredients['mozzarella_slices'] = mozzarella_slices
         self.minutes = minutes

     def possible(self, oven, storage):
         if not oven.ready():
             return False
         for key, number in self.ingredients:
             if number > storage.storage.get(key, 0):
                 return False
         return True

     def put_in_oven(self, oven, storage):
         oven.use(self.minutes)
         for key, number in self.ingredients:
             storage.use(key, number)

Мы могу сделать пиццу сейчас, например:

storage = Storage()
oven = Oven(2)
margherita = Pizza()
prosciutto = Pizza(ham_slices=7)
if margherita.possible():
    margherita.put_in_oven()
storage.new_item('ham_slices', 20)
if prosciutto.possible():
    prosciutto.put_in_oven()

А теперь мой вопрос (извините, если это было слишком подробно):

Могу ли я создать экземпляр Pizza и изменить его метод put_in_oven?

Как, например, пицца, где вам нужно сначала приготовить немного овощей или проверить, подходит ли это время для возможного метода.

Я представляю что-то вроде:

vegetariana = Pizza(paprika=1, arugula=5) # something like that i'm not a pizzaiolo
def vegetariana.put_in_oven(self, oven, storage):
    cook_vegetables()
    super().put_in_oven() # call Pizza.put_in_oven

Надеюсь, этот вопрос не слишком громоздкий!

Редактировать:

Итак, давайте предположим, что мы будем использовать наследование :

class VeggiePizza(Pizza):
     def put_in_oven(self, oven, storage):
         self.cut_veggies()
         super().put_in_oven(oven, storage)

     def cut_veggies(self):
         # serves purpose of explaining
         # analogy has its limits
         pass

class SeasonalPizza(Pizza):
     def __init__(self, season_months, minutes=6, dough=1, tomato_cans=1, mozzarella_slices=8, **kwargs):
         self.season_months # list of month integers (1 - 12)
         super().__init__()

     def possible(self, oven, storage):
         return super().possible(oven, storage) and datetime.datetime.now().month in self.season_months

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

Например, для PizzaAmericano (с картофелем фри сверху) я бы использовал подкласс, такой как VeggiePizza и pu t fry_french_fries() перед super().put_in_oven(), и я бы определенно не использовал этот подкласс для любого другого экземпляра, кроме pizza_americano (в отличие от VeggiePizza, где вы можете сделать различную вегетарианскую пиццу).

Это нормально? Мне кажется, что это противоречит принципу классов.

РЕДАКТИРОВАТЬ:

Хорошо, благодаря вашим ответам и этот рекомендуемый вопрос Я сейчас знать, как добавить / изменить метод экземпляра. Но прежде чем закрыть этот вопрос как дубликат; Это вообще что-то, что совершенно нормально или, скорее, рекомендуется против ? Я имею в виду, что это кажется неестественным из-за простоты его природы, имея метод, специфицирующий экземпляр c, точно так же, как переменные, специфицирующие c переменные.

Ответы [ 2 ]

1 голос
/ 14 марта 2020

Вы можете определить для каждого экземпляра "методы" действительно (nb: пример py3) - python "методы" в основном просто функции - единственный способ убедиться, что функция имеет доступ к текущий экземпляр. Здесь возможны два решения: использовать замыкание или явно вызывать протокол дескриптора функции.

1 /: с замыканием

class Foo:
    def __init__(self, x):
        self.x = x

    def foo(self, bar):
        return bar * self.x


def somefunc():
    f = Foo(42)

    def myfoo(bar):
        # myfoo will keep a reference to `f`
        return bar * (f.x % 2)

    f.foo = myfoo
    return f

2 / с протоколом дескриптора

# same definition of class Foo   

def somefunc()
    f = Foo()
    def myfoo(self, bar):
        return bar * (self.x % 2)

    # cf the link above
    f.foo = myfoo.__get__(f, type(f))
    return f

но более общим решением вашей проблемы являются шаблон стратегии и, возможно, шаблон состояния для случая SeasonalPizza.possible()

Так как ваш пример Это игрушечный пример. Я не стану приводить пример с этими решениями, но их очень просто реализовать в Python.

Также отметим, что, поскольку основная цель заключается в том, чтобы инкапсулировать эти детали, чтобы клиентский код не нужно беспокоиться о том, с какой пиццей она имеет дело, вам понадобится какой-то [творческий паттерн], чтобы справиться с этим. Обратите внимание, что классы python уже являются фабриками из-за двухэтапного процесса создания экземпляров - конструктор __new__() создает пустой неинициализированный экземпляр, который затем передается инициализатору __init__(). Это означает, что вы можете переопределить __new__(), чтобы вернуть все, что вы хотите ... А так как классы Python сами являются объектами, вы можете расширить это, используя пользовательский метакласс

Последнее замечание: просто убедитесь, что вы сохраняете совместимые подписи и типы возврата для всех ваших методов, иначе вы нарушите принцип подстановки Лискова и потеряете первое и основное преимущество ОО, заключающееся в замене условных выражений. с помощью polymorphi c dispatch (IOW: если вы нарушите LSP, ваш клиентский код больше не сможет обрабатывать все типы пицц одинаково и в конечном итоге будет заполнен проверками типов и условными кодами, чего именно пытается избежать ОО).

0 голосов
/ 14 марта 2020

2 возможности:

либо создайте структуру, похожую на регистр, используя dicts:

def put_in_oven1(self, *args):
    # implementation 1
    print('method 1')

def put_in_oven2(self, *args):
    # implementation 2
    print('method 2')

class pizza:
    def __init__(self, method, *args):
        self.method = method
        pass

    def put_in_oven(self, *args):
        handles = {
                1: put_in_oven1,
                2: put_in_oven2}
        handles[self.method](self, *args)


my_pizza1 = pizza(1)  # uses put_in_oven1
my_pizza1.put_in_oven()

my_pizza2 = pizza(2)  # uses put_in_oven2
my_pizza1.put_in_oven()
my_pizza2.put_in_oven()

Или вы можете динамически изменять методы с помощью setattr

, например, :

from functools import partial

def put_in_oven1(self, *args):
    # implementation 1
    print('method 1')

def put_in_oven2(self, *args):
    # implementation 2
    print('method 2')

class pizza:
    def __init__(self, *args, **kwargs):
        # init
        pass 

    def put_in_oven(self, *args):
        # default method
        print('default')

pizza1 = pizza()
setattr(pizza1, 'put_in_oven', partial(put_in_oven, self=pizza1))

pizza2 = pizza()
setattr(pizza2, 'put_in_oven', partial(put_in_oven, self=pizza2))

pizza1.put_in_oven()
pizza2.put_in_oven()

или без использования частичного и определения методов внутри класса пиццы

#!/usr/bin/env python
# -*- coding: utf-8 -*-

class pizza:
    def put_in_oven1(self, *args):
        # implementation 1
        print('method 1')


    def put_in_oven2(self, *args):
        # implementation 2
        print('method 2')

    def __init__(self, *args, **kwargs):
        pass

    def put_in_oven(self, *args):
        # default
        print('default')


pizza1 = pizza()
setattr(pizza1, 'put_in_oven', pizza1.put_in_oven1)

pizza1.put_in_oven()

pizza2 = pizza()
setattr(pizza2, 'put_in_oven', pizza2.put_in_oven2)
pizza2.put_in_oven()
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...