防止方法重复

时间:2016-03-22 19:25:38

标签: python python-2.7

我为SOAP请求创建了一个类。我当时看到我做同样的事情,所以我用不同的逻辑和值编写相同的方法。类似的例子如下:

class Client(object):

    def get_identity(self, identity):
        context = {'identity': identity}
        return self.post_request('get_identity', context)

    def get_degree(self, degree, date):
        context = {
            'degree': degree,
            'date': date
        }
        return self.post_request('get_degree', context)

    def send_response(self, status, degree):
        context = {
            'degree': degree,
            'status': status,
            'reason': 'Lorem ipsum dolor sit amet.'
        }
        return self.post_request('send_response', context)

    def post_request(self, method, args):
        headers = "Creating headers"
        data = "Mapping hear with method and args arguments"

        response = requests.post(self.wsdl, data=data.encode('UTF-8'), headers=headers)

        root = objectify.fromstring(response.content)
        objectify.deannotate(root, cleanup_namespaces=True)
        return root

然后我调用一个方法:

client = Client()
self.client.get_identity(identity)

当我想要实现新的SOAP方法时,我将写下这个:

def get_status(self, id):
    context = {'id': id}
    return self.post_request('get_status', context)

正如我上面提到的,我的目标是防止这种重复,我不知道该怎么做。

3 个答案:

答案 0 :(得分:2)

您可以使用装饰器来实现此目的。这样,您可以以与您一直非常相似的方式指定方法,但是在保留文档字符串和参数规范的同时消除冗余。

要构建您始终创建的context字典,使用等于参数名称的键,我们会dict(zip(argspec, args))

我们使用一个包装器来装饰SOAP方法,该包装器在post_request上调用self,方法名称为第一个参数,context为第二个参数。

因为您还需要始终添加的静态参数,我们会处理装饰器的关键字参数,如this blog post by Carson Myers中所述。

from decorator import decorator
from inspect import getargspec

def soap_method(f = None, **options):
  if f:
    @decorator
    def wrapper(method, client, *args):
      argspec = getargspec(method).args[1:]
      context = dict(zip(argspec, args))
      context.update(options)
      return client.post_request(method.__name__, context)
    return wrapper(f)
  else:
    return lambda f: soap_method(f, **options)

class Client:

  @soap_method
  def get_identity(self, identity):
    """Documentation works."""
    pass

  @soap_method
  def get_degree(self, degree, date):
    pass

  @soap_method(reason='Lorem ipsum dolor sit amet.')
  def send_response(self, status, degree):
    pass

  def post_request(self, method, context):
    print method, context

AFAIK,您无法使用docstrings和kindall的解决方案,而在此处:

>>> help(Client().get_identity)
Help on method get_identity in module foo:

get_identity(self, identity) method of foo.Client instance
    Documentation works.

使用这种方法的另一个好处是,如果某些方法需要动态修改上下文,可以将这两行添加到wrapper

dynamic = method(client, *args)
if dynamic: context.update(dynamic)

您可以定义根据参数计算事物的方法,而不是返回静态值:

@soap_method
def get_identity(self, identity):
  return {'hash': hash(identity)}

最后,如果你没有decorator模块,那么你可以改用它:

from functools import wraps
from inspect import getargspec

def soap_method(f = None, **options):
  if f:
    @wraps(f)
    def wrapper(client, *args):
      argspec = getargspec(f).args[1:]
      context = dict(zip(argspec, args))
      context.update(options)
      return client.post_request(f.__name__, context)
    return wrapper
  else:
    return lambda f: soap_method(f, **options)

这仍然会保留文档字符串,但不会像decorator那样保留argspec。

答案 1 :(得分:1)

编写一个为您编写函数的函数。

def req(methname, *names, **extra):

    def do(self, *values):
        context = dict(zip(names, values))
        context.update(extra)
        return self.post_request(methname, context)

    # create wrapper with proper signature
    f = eval("lambda self, %(p)s: do(self, %(p)s)" % {"p": ",".join(names)}, {"do": do})
    f.__name__ = methname

    return f

class Soapy(object):

    def post_request(self, method, args):
        headers = "Creating headers"
        data = "Mapping hear with method and args arguments"
        response = requests.post(self.wsdl, data=data.encode('UTF-8'), headers=headers)

        root = objectify.fromstring(response.content)
        objectify.deannotate(root, cleanup_namespaces=True)
        return root

class Client(Soapy):

    get_identity  = req("get_identity", "identity")
    get_degree    = req("get_degree", "degree", "date")
    send_response = req("send_response", "status", "degree", reason="Lorem ipsum")

这是唯一一个非常棘手的部分,如果你对它们help()进行签名是很有用的,我通过评估包含包装函数的字符串来做到这一点。在Python 3.3+中,您可以构建一个签名对象,而不使用包装器。

答案 2 :(得分:1)

此答案使用partial method of functools并实施__getattr__。当您说client.get_identity之类的内容时,会返回一个函数,然后使用任何其他参数对其进行评估,例如(status, degree)

from functools import partial
class Client(object):
    def __getattr__(self, attr):
        # This is called after normal attribute lookup fails
        # For this example, assume that means 'attr' should be a SOAP request
        return partial(self.post_request, attr)

    def post_request(self, method, **context):
        # Fill in with actual values
        headers = "Method %s"%method
        data = "arguments",context
        print "Header: %s\nData: %s"%(headers, data)

        response = requests.post(self.wsdl, data=data.encode('UTF-8'), headers=headers)
        root = objectify.fromstring(response.content)
        objectify.deannotate(root, cleanup_namespaces=True)
        return root

这可以像:

一样使用
client = Client()
client.get_identity(id="id", status="status")

输出:

Header: Method get_identity
Data: ('arguments', {'status': 'status', 'id': 'id'})