То же имя функционирует в том же классе, элегантный способ определить, на какой вызов?

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

В настоящее время я делаю что-то вроде ниже. Однако при сохранении содержимого версии сценарии трудно поддерживать.

class Product(object):

    def __init__(client):
        self.version = client.version  # get client version from another module

    def function():
        if self.version == '1.0':
            print('for version 1.0')
        elif self.version == '2.0':
            print('for version 2.0')
        else:
            print(f'function not support {self.version}')

Поэтому я хочу сделать что-то вроде ниже, чтобы отделить функции с тем же именем.

class Product(object):

    def __init__(client):
        self.version = client.version  # get client version from another module

    def function():
        print('for version 1.0')

    def function():
        print('for version 2.0')

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

class Product(object):

    def __init__(client):
        self.version = client.version  # get client version from another module

    @version(1.0)
    def function():
        print('for version 1.0')

    @version(2.0)
    def function():
        print('for version 2.0')

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

Есть ли элегантный способ сделать это?

Ответы

Ответ 1

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

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

version_store = {}

def version(v):
    def dec(f):
        name = f.__qualname__
        version_store[(name, v)] = f
        def method(self, *args, **kwargs):
            f = version_store[(name, self.version)]
            return f(self, *args, **kwargs)
        return method
    return dec

class Product(object):
    def __init__(self, version):
        self.version = version

    @version("1.0")
    def function(self):
        print("1.0")

    @version("2.0")
    def function(self):
        print("2.0")

Product("1.0").function()
Product("2.0").function()

Ответ 2

Не могли бы вы поместить свой класс Product в два модуля, v1 и v2, а затем импортировать их условно?

Например:

Productv1.py

class Product(object):
    def function():
        print('for version 1.0')

Productv2.py

class Product(object):
    def function():
        print('for version 2.0')

Затем в вашем основном файле:

main.py

if client.version == '1.0':
    from Productv1 import Product
elif client.version == '2.0':
    from Productv2 import Product
else:
    print(f'function not support {self.version}')

p = Product
p.function()

Ответ 3

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

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

def func_10(self):
    print('for version 1.0')

def func_20(self):
    print('for version 2.0')

funcs = {"1.0": func_10,
         "2.0": func_20}

Добавьте базовый класс, содержащий статические части вашей реализации, и класс утилиты для создания ваших экземпляров в:

class Product:
    def __init__(self, version):
        self.version = version

class ProductFactory(type):
    @classmethod
    def get_product_class(mcs, version):
        # this will return an instance right away, due to the (version) in the end
        return type.__new__(mcs, "Product_{}".format(version.replace(".","")), (Product,), {"function": funcs.get(version)})(version)
        # if you want to return a class object to instantiate in your code omit the (version) in the end

Используя это:

p1 = ProductFactory.get_product_class("1.0")
p2 = ProductFactory.get_product_class("2.0")
print(p1.__class__.__name__) # Product_10
p1.function() # for version 1.0
print(p1.function) # <bound method func_10 of <__main__.Product_10 object at 0x0000000002A157F0>>
print(p2.__class__.__name__) # Product_20
p2.function() # for version 2.0 
print(p2.function) # <bound method func_20 of <__main__.Product_20 object at 0x0000000002A15860>>

Ответ 4

В общем, не надо. Перегрузка метода не рекомендуется в Python. Если вам нужно дифференцироваться на уровне класса, прочитайте @Loocid ответ. Я не буду повторять его отличный пост.

Если вы хотите на уровне метода, потому что разница достаточно мала для этого, попробуйте что-то вроде этого:

class Product:

    def method(self):
        if self.version == "1.0":
            return self._methodv1()
        elif self.version == "2.0":
            return self._methodv2()
        else:
            raise ValueError("No appropriate method for version {}".format(self.version))

    def _methodv1(self):
        # implementation

    def _methodv2(self):
        # implementation

Обратите внимание:

  1. Использование методов, начинающихся с подчеркивания, чтобы указать, что это реализация.
  2. Хранение методов хорошо и аккуратно, без загрязнения между версиями
  3. Принятие ошибки для неожиданных версий (сбой рано и сложно).
  4. По моему не очень скромному мнению, это будет намного яснее для других людей, читающих ваш пост, чем с помощью декораторов.

Или же:

class Product:

    def method_old(self):
        # transform arguments to v2 method:
        return self.method()

    def method(self):
        # implementation
  1. Если вы хотите полностью отказаться от предыдущего использования и хотите отказаться от поддержки версии 1.0 в будущем.
  2. Обязательно дайте предупреждения об устаревании, чтобы не удивлять пользователей библиотеки с внезапными изменениями.
  3. Возможно, лучшее решение, если никто не использует ваш код.

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

Ответ 5

Вы можете использовать декораторы для этого

def v_decorate(func):
   def func_wrapper(name):
       return func(name)
   return func_wrapper

А также

@v_decorate
def get_version(name):
   return "for version {0} ".format(name)

Вы можете назвать это

get_version(1.0)

   'for version 1.0 '

get_version(2.0)
'for version 2.0 '

Ответ 6

Я не разработчик python, но я не могу не задаться вопросом, почему вы не просто делаете что-то вроде этого:

class Product(object):
    def __init__(self, version):
        self.version = version
    def function(self):
        print('for version ' + self.version)

Ответ 7

Или вы можете сделать, dict.get вызвать функцию и print в lambda если ничего не будет правильно:

def func_1(self):
        print('for version 1.0')

    def func_2(self):
        print('for version 2.0')
    def function(self):
       funcs = {"1.0": self.func_1,
                "2.0": self.func_2}
       funcs.get(self.version,lambda: print(f'function not support {self.version}'))()

Пример:

class Product(object):

    def __init__(self,version):
        self.version = version

    def func_1(self):
        print('for version 1.0')

    def func_2(self):
        print('for version 2.0')
    def function(self):
       funcs = {"1.0": self.func_1,
                "2.0": self.func_2}
       funcs.get(self.version,lambda: print(f'function not support {self.version}'))()
Product('1.0').function()
Product('2.0').function()
Product('3.0').function()

Выход:

for version 1.0
for version 2.0
function not support 3.0