我正在制作一些遗留代码(由爱上意大利面条代码的人创建),有超过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名称,因为它们在其他程序中使用,因为这个讨厌的脚本被用在许多其他遗留代码中。
答案 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
输出显示长度限制功能有效。
另见: