如何在Python中重构100个类方法?

时间:2013-01-11 22:50:01

标签: python

我正在制作一些遗留代码(由爱上意大利面条代码的人创建),有超过150个吸气剂和超过150个安装者。吸气剂看起来像这样:

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的内容并将它们放入装饰器/闭包或其他方法中,以使这些代码更易于维护。 Setters都是一个衬垫,因此它们并不重要。但它们基本上都是一样的。有什么想法可以减轻痛苦吗?

注意:我仍然需要原始的Getter名称,因为它们在其他程序中使用,因为这个讨厌的脚本被用在许多其他遗留代码中。

4 个答案:

答案 0 :(得分: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

要真正理解实际的工作方式,您需要了解描述符。请参阅Implementing Descriptors和调用描述符(或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时,您实际上不会将func本身作为fake1或{{的值1}},但未绑定的方法缠绕在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_self而不是c。当你调用None时,它是如何工作的 - Python认为c.fake1()是一个绑定方法,因此,它实际上调用了c.fake1。这就是c.fake1.im_func(c.fake1.im_self)获取其自身参数的方式。

(这在Python 3中变得更加简单,因为不再有未绑定的方法了,但我认为你更关心Python 2,因为你正在处理大量的遗留代码。)

答案 1 :(得分:3)

您不一定需要在创建类时创建getter / setter方法。您还可以根据需要创建callables:

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

虽然这种方法很容易理解,并且不使用任何元编程伏都教,但它很慢并且无法反省。

您可以在调用方法时通过“reifying”方法加快速度,例如,在访问类的实例属性时将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返回绑定方法并将其附加到实例的类。在第一次访问属性之后,类和实例几乎与我们创建“正常”方式的方法无法区分,并且具有完全相同的运行时速度。

答案 2 :(得分:1)

您可以尝试这样的事情:

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

GetCurrency = getter("_Currency", 3)

因为切片时不能超过字符串的结尾,所以不再需要进行长度测试。

答案 3 :(得分: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

输出显示长度限制功能有效。

另见:

Can anyone help condense this Python code?

What is a metaclass in Python?