web-dev-qa-db-ja.com

同じ名前が同じクラスで機能し、どちらを呼び出すかを決定するエレガントな方法ですか?

特定の理由で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')

これを達成するためにdecoratorを使用することを考えていました:

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

しかし、どうやって...デコレーターがこの種の操作をできないのか、どうすればいいのかわからないようです。

これを行うエレガントな方法はありますか?

35
Timmy Lin

これを行うにはおそらく継承が最善の方法ですが、デコレータについて具体的に尋ねたので、デコレータを使用してこれを実行できることを示したいと思いました。

辞書を使用してバージョンごとに関数を保存し、実行時に使用するバージョンを検索する必要があります。以下に例を示します。

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()
33
Bi Rico

Productクラスを2つのモジュール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()
9
Loocid

別のオプションとして、何らかのファクトリを使用してクラスを作成できます。

バージョン管理された関数を作成します(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>>
7
shmee

一般的にはしないでください。メソッドのオーバーロード は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. 誰もあなたのコードを使用していない場合、おそらくより良いソリューションです。

私の最初の方法はあなたの問題にもっと適していると思いますが、後世のために2番目の方法を含めたいと思いました。 10年後のコードを編集すれば、それがあなたを幸せにします。明日現在のコードを使用してコードを編集する場合、最初の方法はあなたを幸せにします。

6
Jacco van Dorp

または、できます、dict.get関数を呼び出して、何も正しくない場合はprintlambdaを実行します。

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
2
U10-Forward

私はpython開発者ではありませんが、どうしてこのようなことをしないのか疑問に思います。

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

このためにデコレータを使用できます

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 '
1
Richard Rublev