How to make dynamic stub functions on the fly?

时间:2016-06-18 20:19:25

标签: python

I have a class with about 200 stub functions, they look something like this:

def name1(self, **kwargs):
    return self.run('name-1', kwargs)

def name2(self, ** kwargs):
    return self.run('name-2', kwargs)

So on and so forth.

There is a run function that takes care of the real work.

I want to reduce the 200 stub function to just 1 dynamic function that gets called when you make a call to the class with a method name that doesn't exist.

How do I do this?

2 个答案:

答案 0 :(得分:4)

您可以在类中使用__getattr__方法返回run周围的包装函数。您只需要在方法名称(例如name1)和应传递给run方法的字符串(例如"name-1")之间进行转换的方法。如果没有任何改变,这当然是最简单的。这是一个使用字典进行翻译的快速实现。如果您需要它来处理许多名称,您可能希望使用字符串操作和正则表达式来完成它。

name_translation_dict = {"name1": "name-1", "name2": "name-2"}

def __getattr__(self, name):
    if name not in self.name_translation_dict:
        raise AttributeError()
    translated_name = self.name_translation_dict[name]
    def method(**kwargs):
        return self.run(translated_name, kwargs)
    return method

答案 1 :(得分:1)

这是@ Blckknight优秀且相对简洁answer的更为通用且可能更有效的版本。

它更长,因为它更通用,因为它使用正则表达式将调用存根函数的名称转换为name参数以传递类的run()方法,并且可能是整体效率更高,因为它可以保存它在运行中创建的存根函数,有效地缓存它们,这样如果再次使用它们就不需要重新创建它们。那是因为只有在通常的地方找不到属性时才会调用__getattr__(),但是在第一次之后它会被调用(在类的__dict__中)。

import re

class MyClass(object):
    _STUB_PREFIX = 'name'
    _STUB_PATTERN = _STUB_PREFIX + r'''(\d+)'''

    def run(self, name, **kwargs):
        print('run({!r}, {})'.format(name, kwargs))

    def __getattr__(self, name):
        matches = re.search(self._STUB_PATTERN, name)
        if not matches:
            raise AttributeError(
                "'{}' object has no attribute '{}'".format(
                    self.__class__.__name__, name))
        num = matches.group(1)
        translated_name = '-'.join((self._STUB_PREFIX, num))
        def stub(**kwargs):
            return self.run(translated_name, **kwargs)
        setattr(type(self), name, stub)  # add stub to class
        return stub

obj = MyClass()
obj.name1()  # -> run('name-1', {})
obj.name2()  # -> run('name-2', {})
obj.name1()  # -> run('name-1', {}), but __getattr__(self, 'name1') not called
obj.foo42()  # -> AttributeError: 'MyClass' object has no attribute 'foo42'