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 的内容放入装饰器/闭包或其他方法中,以使此代码更易于维护。二传手都是一个班轮,所以他们不那么重要。但它们也基本相同。有什么想法可以减轻这种痛苦吗?

注意:我仍然需要原始的 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参考 get 。但是在定义类时也没有self获取普通方法的参考。在任何一种情况下,您都只是定义了一个将self其作为第一个参数的函数,self当您调用它时,它会以某种方式神奇地得到适当的参数。

要真正了解这实际上是如何工作的,您需要了解描述符。见Implementing Descriptors和Invoking Descriptors(或3.3版本),多读几遍,看看@propertydecorator是怎么实现的,在交互式解释器里玩转,放弃,睡觉,明天再试,应该全部点击。但是如果你先学习魔法版本会更容易,所以让我们用一个更简单的例子来做吧:

>>> 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事后这样做时,你实际上并没有最终将func它自己作为fake1or的值fake2,而是用一个未绑定的方法包裹着func,它im_class指向C.

>>> 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_selfc不是None. 当你调用 时c.fake1(),它就是这样工作的——Python 认为这c.fake1是一个绑定方法,所以实际上它调用了c.fake1.im_func(c.fake1.im_self). 这就是如何fake获取它的 self 参数。

(这一切在 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

如果确实有数百个 getter 共享相同的代码;您可以使用元类来自动创建 getter:

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 回答