Я пытаюсь управлять версиями продукта в скриптах 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')
Однако мне не удалось понять, как ... похоже, что декоратор не может выполнять такую операцию, или я просто не понимаю, как это сделать.
Есть ли элегантный способ сделать это?
Я забыл сказать: под «стандартным решением» я имею в виду: это то, что вы бы сделали на большинстве языков программирования, где вы не можете использовать такие вещи, как, например, декораторы. Также: если у вас есть большие классы, использование декораторов делает ваш класс довольно большим и трудным для работы. Проще полностью разделить реализации, зависящие от версии.
Не могли бы вы разделить свой класс 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()
На самом деле, я использовал аналогичный способ управления версиями раньше, создав несколько файлов py (например, v1, v2, v3), что проще всего, но эти файлы содержат слишком много "дублированного содержимого" ... поэтому я пытаюсь объединить их в один КЛАСС. Спасибо за совет!
@TimmyLin В этом случае у вас может быть базовый класс, скажем, Product
, который хранит весь дублированный контент, а затем есть Productv1
и Productv2
, которые наследуют его и просто содержат то, что отличается. Таким образом, не будет дублированного кода.
И ГЛАВНАЯ причина в том, что ... слишком много версий (но с небольшими изменениями) в этом продукте, и он все еще растет, поэтому, если я сделаю это, структура папок / файлов в конечном итоге станет некрасивой: |
Ах, в таком случае мое предложение не может идти дальше. Удачи.
Собственно, план Б - использовать наследование от основного класса. Но я все еще хочу знать, есть ли какой-нибудь крутой и элегантный способ сделать это. Если нет, отмечу лучший ответ! благодарю вас!
Вы можете использовать для этого декораторы
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 '
Извините, я не уверен, правильно ли я это понимаю. v_decorate
должен добавить дополнительный аргумент name
к исходной функции? не понимаю, как совместить это с моими одноименными функциями .. Орз
Это ничем не отличается от первого примера, который я написал в описании. Передача версии в v_decorate
и вставка в вызываемую функцию - это вроде дополнительных шагов. Измените мою функцию на function(self): print(f'for version {self.version}')
, можно сделать то же самое без декоратора ..
Я не понимаю, что на самом деле делает этот декоратор?
Наследование, вероятно, лучший способ сделать это, но поскольку вы спросили конкретно о декораторах, я хотел показать, что вы можете сделать это с помощью декораторов.
Вам нужно будет использовать словарь для хранения ваших функций по версиям, а затем искать, какую версию использовать во время выполнения. Вот пример.
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()
Вероятно, вы хотите, чтобы f.__qualname__
избегал конфликта между Product.function
и Ambassadorial.function
...
Python не жалуется, что в классе есть два метода с одинаковым именем? Какой из них он использует? (Да, я понимаю, что декоратор все равно возвращает ту же функцию, но Python не знает)
@Amadan Можно ли вместо использования полного имени создать отдельный version_store
для каждого класса?
Методы @Bergi - это просто члены класса. Каждое объявление заменяет последнюю версию, но это не имеет значения, потому что, как вы сказали, они все одинаковые :)
Проблема с «одним хранилищем версий на класс» в том, что его некуда поместить. В идеале вы могли бы подумать, что было бы лучше создать секретный атрибут на Products
и поместить туда все методы, связанные с версией продукта, но на момент разрешения аннотации Product
еще не существует.
В качестве другого варианта вы можете использовать какую-нибудь фабрику для создания своего класса.
Создайте свои версионные функции (обратите внимание на параметр 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>>
Круто! хотя это немного сложно, оно могло бы помочь. однако я ищу способ, которым мне не нужно разделять функции на 2 переменные p1, p2
, но это абсолютно хороший ответ. Спасибо!
@TimmyLin На самом деле функции не разделены на две переменные. p1
и p2
- это отдельные экземпляры подклассов Product
, каждый со своей собственной реализацией function
на основе version
(который должен быть вашим client.version
). По сути, в этом примере создается класс с реализацией function
, которая требуется для данной версии, вместо того, чтобы иметь один класс, реализующий все возможные «версии» function
. Однако я думаю, что вы выбрали лучший ответ для своего требования, это было добавлено для полноты картины :)
Кстати: вы можете заменить ProductFactor
классом Product
и определить метод __new__
, чтобы делать то, что делает get_product_class
. Таким образом, код имеет меньше шаблонов.
В общем, не надо.Перегрузка метода не рекомендуется в 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
Обратите внимание:
Или:
class Product:
def method_old(self):
# transform arguments to v2 method:
return self.method()
def method(self):
# implementation
Я понимаю, что мой первый метод больше подходит для решения вашей проблемы, но я хотел включить второй для потомков. Если вы отредактируете свой код через 10 лет, он сделает вас счастливее. Если завтра вы отредактируете код, используя текущий код, первый способ сделает вас счастливее.
Спасибо за честный совет! Все здесь мне очень помогли, сложно сказать, чье решение лучше. Скажем ... проект, над которым я работаю, ... чрезвычайно огромен, мы хотим, чтобы разница в версиях была указана очень ЧЕТКО и легко поддерживалась. Вот почему я настаиваю на использовании для этого декоратора, потому что он самый ясный и красивый. Честно говоря, если я работаю над другим меньшим проектом, я обязательно выберу ваш второй вариант, а также вариант @ Loocid.
В то время мы можем смотреть только на небольшую часть - в огромном проекте только вы можете выбрать правильный вариант из-за стиля остальной части проекта. Лучшее, что мы можем сделать, это перечислить возможности. Удачи с этим!
Спроектировать новый фреймворк для разработки сложно, мне очень нравится ваше первое предложение, и я обсудил его с командой, может быть, есть шанс, что мы пойдем по тому же пути или по пути @ BartoszKP. Оба отличные.
Я не разработчик Python, но не могу не задаться вопросом, почему вы просто не делаете что-то вроде этого:
class Product(object):
def __init__(self, version):
self.version = version
def function(self):
print('for version ' + self.version)
Потому что это всего лишь пример, который помогает людям понять, что я пытался сделать. Эта функция не только печатает, но и выполняет множество операций. На самом деле мне даже не нужно, чтобы self.version передавался в метод функции, он мне просто нужен, чтобы определить, какую функцию я хочу загрузить во время работы программы.
Или вы можете сделать 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
«Стандартный» способ решить эту проблему - иметь
ProductV1
иProductV2
, тогда ваш классProduct
просто имеет атрибут_impl
, который назначаетсяProductV<version>
, и все методы передаются какdef function(self): return self._impl.function()
. В python вы даже можете избежать их определения с помощью__getattr__
. Также:ProductVX
просто определил бы базовые операции, и вы можете поместить вProduct
фасадные методы, которые вы можете построить поверх базовых методов.