9

私は、150 以上の getter と 150 以上の setter を持つレガシー コード (スパゲッティ コードが大好きな人によって作成された) に取り組んでいます。ゲッターは次のようになります。

def GetLoadFee(self):
    r_str = ""
    if len(self._LoadFee) > 20:
        r_str = self._LoadFee[:20]
    else:
        r_str = self._LoadFee.strip()
    return r_str.strip()

def GetCurrency(self):
    r_str = ""
    if len(self._Currency) > 3:
        r_str = self._Currency[:3]
    else:
        r_str = self._Currency.strip()
    return r_str.strip()

これらの各 Getter の内容を取得し、decorator /closure またはその他のメソッドに配置して、このコードを保守しやすくしたいと考えています。セッターはすべて 1 つのライナーなので、それほど重要ではありません。しかし、それらは基本的にすべて同じです。この痛みを軽減するためのアイデアはありますか?

注:この厄介なスクリプトは他の多くのレガシーコードで使用されているため、他のプログラムで使用されているため、元のGetter名が必要です。

4

4 に答える 4

11
def make_generic_getter(name, maxlen):
    def getter(self):
        value = getattr(self, name)
        r_str = ""
        if len(value) > maxlen:
            r_str = value[:maxlen]
        else:
            r_str = value.strip()
        return r_str.strip()
    return getter

今、あなたはこれを行うことができます:

class Foo(object):
    def __init__(self):
        self._Bar = 'abc'
        self._Baz = 'def'
    GetBar = make_generic_getter('_Bar', 5)
    GetBaz = make_generic_getter('_Baz', 2)

それで:

>>> f = Foo()
>>> f.GetBar()
'abc'
>>> f.GetBaz()
'de'

明らかに、元の関数には反復的で不要なものもたくさんあります。(そして、プロパティにPEP8スタイルの名前を使用する方がはるかに優れています。)しかし、明らかに、最初にリファクタリングしてから改善する方が、その逆よりもはるかに簡単です。(つまり、ここから始めますが、ここで止まらないでください。)

コメントから:

メソッドメーカーはどのようにして「自己」参照を取得しますか?

メソッドメーカーは実際にはself参照を取得しません。selfメソッドメーカーが呼び出されているときに取得する参照はありません。selfただし、クラスが定義されているときに通常のメソッドを取得するための参照もありません。どちらの場合も、最初のパラメーターとして受け取る関数を定義しているだけであり、それを呼び出すとself、魔法のように適切な関数が取得されます。self

これが実際にどのように機能するかを実際に理解するには、記述子について知る必要があります。記述子の実装と記述子の呼び出し(または3.3バージョン)を参照して、それを数回読み、デコレータがどのように@property実装されているかを確認し、インタラクティブインタプリタで遊んで、あきらめて、スリープ状態になり、明日もう一度やり直してください。すべてクリックします。しかし、最初に魔法のバージョンを学ぶ方が簡単なので、もっと簡単な例を使ってそれをやってみましょう:

>>> def func(self): pass
>>> class C(object):
...     def meth(self): pass
...     fake1 = func
>>> C.fake2 = func
>>> func, C.meth, C.fake1, C.fake2
(<function __main__.func>, <unbound method C.meth>, <unbound method C.func>, <unbound method C.func>)

アンバウンドメソッドは、im_classそのクラスをim_func保持し、正規関数を保持し、をim_self保持するものNoneです。そしてfake1 = func、クラス定義で行う場合、またはC.fake2 = func事後に、実際にはorfuncの値としてそれ自体で終わるのではなく、バインドされていないメソッドがラップされ、そのポイントが。になります。fake1fake2funcim_classC

>>> c = C()
>>> c.meth, c.fake1
(<bound method C.meth of <__main__.C object at 0x111ebb0d0>>, <bound method C.meth of <__main__.C object at 0x111ebb0d0>>)

クラスのインスタンスを取得すると、そのバインドされていないメソッドはすべてバインドされたメソッドになります。バインドされたメソッドの属性を見ると、それがの代わりになっていることを除いて、バインドされていないメソッドとim_self同じcですNone。そして、を呼び出すとc.fake1()、それが機能します。Pythonは、それc.fake1がバインドされたメソッドであると見なすため、実際には、を呼び出しますc.fake1.im_func(c.fake1.im_self)。そして、それfakeが自己パラメータを取得する方法です。

(Python 3では、バインドされていないメソッドなどがなくなったため、これはすべて簡単になりますが、レガシーコードの膨大な混乱を扱っていることを考えると、Python 2にもっと関心があると思います。)

于 2013-01-11T22:55:09.820 に答える
3

クラスの作成時に、必ずしも getter/setter メソッドを作成する必要はありません。必要に応じて呼び出し可能オブジェクトを作成することもできます:

class MyClass(object):
    # get/set properties for this class: {'Name':length}
    __properties = {'LoadFee':20, 'Currency':3}

    def __init__(self):
        self._Currency = '01 34'
        self._LoadFee = 'lorem ipsum dolor sit amet consecuti'

    def __getattr__(self, name):
        basename = name[3:]
        attrname = '_'+basename
        if basename in self.__properties:
            if name.startswith('Get'):
                return lambda : getattr(self, attrname)[:self.__properties[basename]].strip()
            elif name.startswith('Set'):
                return lambda value: setattr(self, attrname, value)
        raise AttributeError(name)

m = MyClass()

print m.GetCurrency()
print m.GetLoadFee()

このアプローチは理解しやすく、ブードゥー教のメタプログラミングを一切使用していませんが、遅く、イントロスペクションを無効にします。

メソッドを呼び出すときにメソッドを「具体化」することで、これを高速化できます。つまり、instancemethodクラスのインスタンスの属性がアクセスされるときにクラスに をアタッチします。

# MethodType is not actually necessary because
# everything it does can be done with normal Python
# but it will make our dynamic methods look as "normal"
# and not-dynamic as possible to introspection
from types import MethodType

class MyClass(object):
    # get/set properties for this class: {'Name':length}
    __properties = {'LoadFee':20, 'Currency':3}

    def __init__(self, **args):
        props = self.__properties
        emptystr = ''
        for k in props:
            setattr(self, '_'+k, args.get(k, emptystr))

    def __getattr__(self, name):
        print '__getattr__(%s)' % name
        # we can cache accesses by "reifying" our getters/setters as they are accessed
        cls = self.__class__
        basename = name[3:]
        attrname = '_'+basename
        # nested lambdas are just for delayed evaluation
        # they cache property size lookup--assumes __properties is class-constant!
        def getsize():
            return cls.__properties[basename]
        methodfactories = {
            'Get': lambda size: lambda self: getattr(self, attrname)[:size].strip(),
            'Set': lambda size: lambda self, value: setattr(self, attrname, value),
        }
        try:
            print '  creating', name
            callable = methodfactories[name[:3]](getsize())
        except (KeyError, AttributeError) as e:
            raise AttributeError("'{}' object has no attribute '{}'".format(cls.__name__, name))
        callable.__name__ = name #cosmetics
        unboundmethod = MethodType(callable, None, cls)
        setattr(cls, name, unboundmethod) # add unbound method to the class
        # magically get bound method on the instance!
        # this works because MethodType creates a descriptor that
        # returns a bound callable in an instance context
        # and an unbound one in a class context
        return getattr(self, name) # not an infinite loop!

次に、次のコードを実行すると:

m = MyClass(Currency='01', LoadFee='lorem ipsum dolor sit')
n = MyClass(Currency='02', LoadFee='amet consecuti')
try:
    # AttributeError because it hasn't been used by an instance
    MyClass.GetCurrency 
except AttributeError, e:
    print ' 7:', e
print ' 8:', m.GetCurrency()
print ' 9:', MyClass.GetCurrency
print '10:', m.GetCurrency
print '11:', n.GetCurrency
print '12:', m.GetCurrency is n.GetCurrency
print '13:', n.GetCurrency()
print '14:', m.GetLoadFee()
print '15:', m.__dict__ # no per-instance callable!

次の結果が得られます。

 7: type object 'MyClass' has no attribute 'GetCurrency'
 8: __getattr__(GetCurrency)
  creating GetCurrency
01
 9: <unbound method MyClass.GetCurrency>
10: <bound method MyClass.GetCurrency of <__main__.MyClass object at 0x106f87b90>>
11: <bound method MyClass.GetCurrency of <__main__.MyClass object at 0x106f87f10>>
12: False
13: 02
14: __getattr__(GetLoadFee)
  creating GetLoadFee
lorem ipsum dolor si
15: {'_Currency': '01', '_LoadFee': 'lorem ipsum dolor sit'}

getattrは、インスタンスが特別プロパティに最初にアクセスしたときにのみ呼び出されることに注意してください。その後、バインドされたメソッドが動的に作成され、インスタンスのクラスにアタッチされて返されます。属性に最初にアクセスした後、クラスとインスタンスは、「通常の」方法で作成したメソッドとほとんど区別がつかなくなり、実行速度もまったく同じになります。instancemethod

于 2013-01-11T23:31:53.333 に答える
1

次のようなことを試すことができます:

def getter(attr, length):
    def wrapper(self):
        value = getattr(self, attr)
        return value[:length].strip()
    return wrapper

GetCurrency = getter("_Currency", 3)

スライスするときに文字列の端をオーバーシュートできないため、長さのテストは不要になりました。

于 2013-01-11T22:53:59.753 に答える
0

同じコードを共有する文字通り何百ものゲッターがある場合。メタクラスを使用して、ゲッターの作成を自動化できます。

def length_limiting_getter(name, maxlen):
    g = lambda self: getattr(self, "_"+name)[:maxlen].strip()
    g.__name__ = name
    return g

def add_attrs(attr_maxlens):
    def meta(class_name, base_classes, attrs):
        attrs.update((name, length_limiting_getter(name, maxlen))
                     for name, maxlen in attr_maxlens.items())
        return type(class_name, base_classes, attrs)
    return meta

Meta = add_attrs({n: maxlen for n, maxlen in zip("a b c".split(),
                                                 [1, 10, 50])})
class ClassWithManyGetters(object): # On Python 3 use: `(metaclass=Meta)` syntax
    __metaclass__ = Meta
    def __init__(self):
        for name in "abc":
            setattr(self, "_" + name, "X"*20)

c = ClassWithManyGetters()
print(c.a())
print(c.b())
print(c.c())

出力

X
XXXXXXXXXX
XXXXXXXXXXXXXXXXXXXX

出力は、長さ制限機能が機能していることを示しています。

以下も参照してください。

この Python コードの要約を手伝ってくれる人はいますか?

Python のメタクラスとは何ですか?

于 2013-01-12T00:50:21.257 に答える