Функции с одинаковыми именами в одном и том же классе, элегантный способ определить, какой из них вызывать? - PullRequest
0 голосов
/ 10 сентября 2018

Я пытаюсь сделать контроль версий продукта в скриптах 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')

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

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

Ответы [ 7 ]

0 голосов
/ 11 сентября 2018

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

class Product(object):
    def __init__(self, version):
        self.version = version
    def function(self):
        print('for version ' + self.version)
0 голосов
/ 11 сентября 2018

Или вы можете, 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
0 голосов
/ 10 сентября 2018

В общем, не надо. Перегрузка метода не рекомендуется в 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 лет, он сделает вас счастливее. Если завтра вы редактируете код, используя текущий код, первый метод сделает вас счастливее.

0 голосов
/ 10 сентября 2018

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

Создайте свои версионные функции (обратите внимание на параметр 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>>
0 голосов
/ 10 сентября 2018

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

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

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()
0 голосов
/ 10 сентября 2018

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

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 '
0 голосов
/ 10 сентября 2018

Не могли бы вы поместить класс 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()
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...