以编程方式为类生成方法

发布于 2024-12-18 12:52:41 字数 833 浏览 0 评论 0原文

我有大约 20 个方法可以重定向到采用原始方法的包装方法,以及其余参数:

class my_socket(parent):

    def _in(self, method, *args, **kwargs):
        # do funky stuff

    def recv(self, *args, **kwargs):
        return self._in(super().recv, *args, **kwargs)

    def recv_into(self, *args, **kwargs):
        return self._in(super().recv_into, *args, **kwargs)

    # and so on...

如何以编程方式添加更多这些方法?这就是我在一切开始看起来错误之前所得到的信息:

for method in 'recv', 'recvfrom', 'recvfrom_into', 'recv_into', ...:
    setattr(my_socket, method, ???)

我可以通过在类定义中分配来做到这一点,还是其他感觉更自然的方法吗?

class my_socket(parent):

    def makes_recv_methods(name):
        # wraps call to name

    def recv_meh = makes_recv_methods('recv_meh')

如果可能的话,我更愿意使用 __get__ 和朋友,而不是来自 types 的魔术函数。

I have about 20 methods to redirect to a wrapper method that takes the original method, and the rest of the arguments:

class my_socket(parent):

    def _in(self, method, *args, **kwargs):
        # do funky stuff

    def recv(self, *args, **kwargs):
        return self._in(super().recv, *args, **kwargs)

    def recv_into(self, *args, **kwargs):
        return self._in(super().recv_into, *args, **kwargs)

    # and so on...

How can I add more of these methods programmatically? This is about as far as I get before everything starts to look wrong:

for method in 'recv', 'recvfrom', 'recvfrom_into', 'recv_into', ...:
    setattr(my_socket, method, ???)

Can I do this by assigning within the class definition, or something else that feels more natural?

class my_socket(parent):

    def makes_recv_methods(name):
        # wraps call to name

    def recv_meh = makes_recv_methods('recv_meh')

I'd prefer to use __get__ and friends when possible over magic functions from types.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

绝對不後悔。 2024-12-25 12:52:41

我会通过在定义类后运行一些代码从列表中生成方法来实现这一点 - 您可以将其放入装饰器中。

import functools

def wrap_method(cls, name):
    # This unbound method will be pulled from the superclass.
    wrapped = getattr(cls, name)
    @functools.wraps(wrapped)
    def wrapper(self, *args, **kwargs):
        return self._in(wrapped.__get__(self, cls), *args, **kwargs)
    return wrapper

def wrap_methods(cls):
    for name in cls.WRAP_ATTRS:
        setattr(cls, name, wrap_method(cls, name))
    return cls

@wrap_methods
class my_socket(parent_class):
    WRAP_ATTRS = ['recv', 'recvfrom'] # ... + more method names

    def _in(self, method, *args, **kwargs):
        # do funky stuff

I'd do it by running some code to generate the methods from a list after the class is defined - you could put this into a decorator.

import functools

def wrap_method(cls, name):
    # This unbound method will be pulled from the superclass.
    wrapped = getattr(cls, name)
    @functools.wraps(wrapped)
    def wrapper(self, *args, **kwargs):
        return self._in(wrapped.__get__(self, cls), *args, **kwargs)
    return wrapper

def wrap_methods(cls):
    for name in cls.WRAP_ATTRS:
        setattr(cls, name, wrap_method(cls, name))
    return cls

@wrap_methods
class my_socket(parent_class):
    WRAP_ATTRS = ['recv', 'recvfrom'] # ... + more method names

    def _in(self, method, *args, **kwargs):
        # do funky stuff
遥远的她 2024-12-25 12:52:41

wilberforce 提案有效,但有一种仅使用 OOP 的更简单的方法:

def wrap_method(wrapped):
    @functools.wraps(wrapped)
    def wrapper(self, *args, **kwargs):
        return self._in(wrapped.__get__(self, cls), *args, **kwargs)
    return wrapper

class Parent:

    def _in(self, method, *args, **kwargs):
        return method(*args, **kwargs)


    @wrap_method
    def recv(self, *args, **kwargs):
        return # whatever

    @wrap_method
    def recv_into(self, *args, **kwargs):
        return # whatever

class MySocket(Parent):

    def _in(self, method, *args, **kwargs):
        # do funky stuff

wilberforce proposal works, but there is a simpler way using OOP only:

def wrap_method(wrapped):
    @functools.wraps(wrapped)
    def wrapper(self, *args, **kwargs):
        return self._in(wrapped.__get__(self, cls), *args, **kwargs)
    return wrapper

class Parent:

    def _in(self, method, *args, **kwargs):
        return method(*args, **kwargs)


    @wrap_method
    def recv(self, *args, **kwargs):
        return # whatever

    @wrap_method
    def recv_into(self, *args, **kwargs):
        return # whatever

class MySocket(Parent):

    def _in(self, method, *args, **kwargs):
        # do funky stuff
哭了丶谁疼 2024-12-25 12:52:41

我想扩展已接受的答案。我希望可能有一个非常长的装饰器方法列表应用于一个非常长的方法列表。

import functools


def wrap_method(cls, name, wrapper_method_name):
    # This unbound method will be pulled from the superclass.
    wrapped = getattr(cls, name, wrapper_method_name)

    @functools.wraps(wrapped)
    def wrapper(self, *args, **kwargs):
        wrapper_method = getattr(self, wrapper_method_name)
        return wrapper_method(wrapped.__get__(self, cls), *args, **kwargs)

    return wrapper


def wrap_methods(cls):
    for wrapper_method_name in cls.WRAPPER_METHOD_NAMES:
        for name in cls.WRAPPED_METHODS:
            setattr(cls, name, wrap_method(cls, name, wrapper_method_name))
    return cls

这是包装原始类的类

@wrap_methods
class WrappedConnection(BaseConnection):
    """
    This class adds some quality-of-life improvements to the BaseConnection class.
    -WRAPPED_METHODS are wrapped by WRAPPER_METHOD_NAMES
    -wrappers can be toggled on and off.

    example:
    connection = WrappedConnection(show_messages=True, log_errors=False, keep_authenticated=False)

    default:
    connection = WrappedConnection(show_messages=False, log_errors=True, keep_authenticated=True)
    """
    WRAPPER_METHOD_NAMES = ['log_errors', 'keep_authenticated', 'show_messages']
    WRAPPED_METHODS = ['a_method', 'b_method', 'c_method', 'd_method']
    MESSAGE_OVERRIDE_MAP = {"a_method": "a_method_message_override_attribute", 
                            "b_method": "b_method_message_override_attribute"}

    def keep_authenticated(self, method, *args, **kwargs):
        """
        If the session has expired, the session is re-authenticated. The incident is logged by the default logger.
        This option can be turned off by setting keep_authenticated during initialization of a WrappedConnection object.
        - connection = WrappedConnection(keep_authenticated=False) # why would you ever do this


        :param method: (method) method to be wrapped
        :param args: (args) passed args
        :param kwargs: (kwargs) passed kwargs
        :return: (method) method wrapped by @keep_authenticated
        """
        response, expired_session = method(*args, **kwargs), None
        if response["errors"] and self._keep_authenticated:
            expired_session = list(filter(lambda x: 'expired session' in x, response["errors"]))
        if expired_session:
            self.__init__()
            logging.info('Session has been re-authenticated.')
            response = method(*args, **kwargs)
        return response

    def log_errors(self, method, *args, **kwargs):
        """
        If there is an error the incident is logged. This option can be turned off by setting log_errors
        during initialization of a WrappedConnection object.
        - connection = WrappedConnection(log_errors=False)

        :param method: (method) method to be wrapped
        :param args: (args) passed args
        :param kwargs: (kwargs) passed kwargs
        :return: (method) method wrapped by @log_errors
        """
        response = method(*args, **kwargs)
        if response["errors"] and self._log_errors:
            errors = response["errors"]
            logging.error(errors)
        return response

    def show_messages(self, method, *args, **kwargs):
        """
        Shows the xml that is sent during the request. This option can be turned on by setting show_messages during
        initialization of a WrappedConnection object.
        - connection = WrappedConnection(show_messages=True)

        :param method: (method) method to be wrapped
        :param args: (args) passed args
        :param kwargs: (kwargs) passed kwargs
        :return: (method) method wrapped by @show_messages
        """
        response = method(*args, **kwargs)
        if self._show_messages:
            message_override_attr = WrappedConnection.MESSAGE_OVERRIDE_MAP.get(method.__name__)
            if message_override_attr:
                message_override = getattr(self, message_override_attr)
                print(BeautifulSoup(message_override, "xml").prettify())
            else:
                self._show_message(method.__name__, *args, **kwargs)
        return response

    def __init__(self, *args, keep_authenticated=True, log_errors=True, show_messages=False, **kwargs):
        super(WrappedConnection, self).__init__(*args, **kwargs)
        self._keep_authenticated = keep_authenticated
        self._log_errors = log_errors
        self._show_messages = show_messages

I'd like to expand on the accepted answer. I wanted to potentially have a very long list of decorator methods applied to a very long list of methods.

import functools


def wrap_method(cls, name, wrapper_method_name):
    # This unbound method will be pulled from the superclass.
    wrapped = getattr(cls, name, wrapper_method_name)

    @functools.wraps(wrapped)
    def wrapper(self, *args, **kwargs):
        wrapper_method = getattr(self, wrapper_method_name)
        return wrapper_method(wrapped.__get__(self, cls), *args, **kwargs)

    return wrapper


def wrap_methods(cls):
    for wrapper_method_name in cls.WRAPPER_METHOD_NAMES:
        for name in cls.WRAPPED_METHODS:
            setattr(cls, name, wrap_method(cls, name, wrapper_method_name))
    return cls

And here is the class that wraps the original

@wrap_methods
class WrappedConnection(BaseConnection):
    """
    This class adds some quality-of-life improvements to the BaseConnection class.
    -WRAPPED_METHODS are wrapped by WRAPPER_METHOD_NAMES
    -wrappers can be toggled on and off.

    example:
    connection = WrappedConnection(show_messages=True, log_errors=False, keep_authenticated=False)

    default:
    connection = WrappedConnection(show_messages=False, log_errors=True, keep_authenticated=True)
    """
    WRAPPER_METHOD_NAMES = ['log_errors', 'keep_authenticated', 'show_messages']
    WRAPPED_METHODS = ['a_method', 'b_method', 'c_method', 'd_method']
    MESSAGE_OVERRIDE_MAP = {"a_method": "a_method_message_override_attribute", 
                            "b_method": "b_method_message_override_attribute"}

    def keep_authenticated(self, method, *args, **kwargs):
        """
        If the session has expired, the session is re-authenticated. The incident is logged by the default logger.
        This option can be turned off by setting keep_authenticated during initialization of a WrappedConnection object.
        - connection = WrappedConnection(keep_authenticated=False) # why would you ever do this


        :param method: (method) method to be wrapped
        :param args: (args) passed args
        :param kwargs: (kwargs) passed kwargs
        :return: (method) method wrapped by @keep_authenticated
        """
        response, expired_session = method(*args, **kwargs), None
        if response["errors"] and self._keep_authenticated:
            expired_session = list(filter(lambda x: 'expired session' in x, response["errors"]))
        if expired_session:
            self.__init__()
            logging.info('Session has been re-authenticated.')
            response = method(*args, **kwargs)
        return response

    def log_errors(self, method, *args, **kwargs):
        """
        If there is an error the incident is logged. This option can be turned off by setting log_errors
        during initialization of a WrappedConnection object.
        - connection = WrappedConnection(log_errors=False)

        :param method: (method) method to be wrapped
        :param args: (args) passed args
        :param kwargs: (kwargs) passed kwargs
        :return: (method) method wrapped by @log_errors
        """
        response = method(*args, **kwargs)
        if response["errors"] and self._log_errors:
            errors = response["errors"]
            logging.error(errors)
        return response

    def show_messages(self, method, *args, **kwargs):
        """
        Shows the xml that is sent during the request. This option can be turned on by setting show_messages during
        initialization of a WrappedConnection object.
        - connection = WrappedConnection(show_messages=True)

        :param method: (method) method to be wrapped
        :param args: (args) passed args
        :param kwargs: (kwargs) passed kwargs
        :return: (method) method wrapped by @show_messages
        """
        response = method(*args, **kwargs)
        if self._show_messages:
            message_override_attr = WrappedConnection.MESSAGE_OVERRIDE_MAP.get(method.__name__)
            if message_override_attr:
                message_override = getattr(self, message_override_attr)
                print(BeautifulSoup(message_override, "xml").prettify())
            else:
                self._show_message(method.__name__, *args, **kwargs)
        return response

    def __init__(self, *args, keep_authenticated=True, log_errors=True, show_messages=False, **kwargs):
        super(WrappedConnection, self).__init__(*args, **kwargs)
        self._keep_authenticated = keep_authenticated
        self._log_errors = log_errors
        self._show_messages = show_messages
月光色 2024-12-25 12:52:41

您可以使用cog

class MySocket(Parent):
"""[[[cog
import cog
l = ['in','out']
for item in l:
    cog.outl("def _{0}(self, method, *args, **kwargs):".format(item))

]]]"""
#[[[end]]]

这样做的额外优点是易于更新,不会触及结束注释之外的代码,并且如果需要,您可以调整生成的代码。

我已经成功地使用 cog 在另一个项目上生成样板文件,并与非生成的代码混合在一起。它首先将指令输入文件读入字典。然后,对于样板文件的每个部分,它都使用字典的那一部分来知道要写什么。

我在一个位置编辑指令文件,而不是在样板文件中的二十个不同位置。

You could use cog.

class MySocket(Parent):
"""[[[cog
import cog
l = ['in','out']
for item in l:
    cog.outl("def _{0}(self, method, *args, **kwargs):".format(item))

]]]"""
#[[[end]]]

This has the added advantages of easily being updated, not touching your code outside of the end comment, and you can twiddle the generated code if necessary.

I've successfully used cog for generating boilerplate on another project, mixed in with the non generated code. It started out reading an input file of instructions into a dictionary. Then for each section of boilerplate it used that piece of the dictionary to know what to write.

I edit the instruction file in one spot, instead of twenty different places in the boilerplate.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文