2011-11-29 38 views
11

Tôi có khoảng 20 phương pháp để chuyển hướng đến một phương pháp wrapper mà có phương pháp ban đầu, và phần còn lại của các đối số:lập trình tạo ra các phương pháp cho một lớp

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... 

Làm thế nào tôi có thể thêm nhiều các phương pháp lập trình? Đây là về như xa như tôi có được trước khi mọi thứ bắt đầu trông sai:

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

Tôi có thể làm điều này bằng cách gán thuộc định nghĩa lớp, hay cái gì khác mà cảm thấy tự nhiên hơn?

class my_socket(parent): 

    def makes_recv_methods(name): 
     # wraps call to name 

    def recv_meh = makes_recv_methods('recv_meh') 

Tôi muốn sử dụng __get__ và bạn bè khi có thể trên các chức năng ma thuật từ types.

+0

bạn không thể cấu trúc lại mã của bạn để làm 'stuff' funky trong một '@ decorator' - hoặc là có một cái gì đó tôi không' Tôi đến đây? – Kimvais

+0

@Kimvais: Vâng nhưng sau đó làm thế nào tôi sẽ ràng buộc chức năng trang trí với một tên trở lại lớp học? Tôi sẽ kết thúc bằng '@decorator ('recv_into'): def recv_into (self): pass', phải không? –

+0

Điểm của 'super' khi không có siêu lớp là gì? –

Trả lời

8

Tôi sẽ làm điều đó bằng cách chạy một số mã để tạo các phương thức từ danh sách sau khi lớp được xác định - bạn có thể đặt điều này vào trình trang trí.

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 
+0

Tôi nghĩ rằng Matt muốn gọi phương thức trong '_in', có thể sau khi sửa đổi các đối số. Thay vào đó, bạn có thể truyền phương thức bị ràng buộc làm đối số đầu tiên: 'self._in (được bọc .__ get __ (self, cls), * args, ** kwargs)'. – eryksun

+0

Ah, vâng - thay đổi để kết hợp điều đó. – babbageclunk

+0

Sau khi đọc điều vô nghĩa đó, tôi đã quyết định chống lại nó. Có quá nhiều sự vô cảm khiến tôi buồn nôn. Tôi hy vọng cuối cùng chúng tôi có được các chức năng ẩn danh. –

0

đề nghị wilberforce hoạt động, nhưng có một cách đơn giản hơn chỉ sử dụng 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 
+0

Rất tiếc, tôi không thể thực hiện thay đổi đối với lớp cha. –

+0

Yêu cầu là _add phương pháp lập trình_ ... – gecco

+0

tốt, không có hại gì tôi cung cấp thay thế cho lập trình meta. Tôi đã bỏ phiếu cho câu trả lời của wilberforce, nhưng tôi có xu hướng đồng ý với @Matt. Các op đang cố gắng để đạt được một cái gì đó nguy hiểm. Anh ta nên cố gắng thiết kế mã của mình để anh ta có thể thoát ra mà không có những mánh khóe như thế này. – Simon

-1

Bạn có thể sử dụng 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]]] 

Điều này có thêm các ưu điểm dễ dàng cập nhật, không chạm vào mã của bạn bên ngoài nhận xét cuối và bạn có thể viết mã được tạo nếu cần.

Tôi đã sử dụng thành công cog để tạo bản mẫu trên một dự án khác, được trộn lẫn với mã không được tạo. Nó bắt đầu đọc một tập tin đầu vào của các hướng dẫn vào một từ điển. Sau đó, cho mỗi phần của boilerplate nó được sử dụng mà phần của từ điển để biết những gì để viết.

Tôi chỉnh sửa tệp hướng dẫn tại một vị trí, thay vì hai mươi vị trí khác nhau trong bản mẫu.

+1

IMHO, tạo mã là không bao giờ cần thiết trong python, vì bạn luôn có thể tìm thấy một cách để refactor boilerplate – Simon

+0

@Simon Ngôn ngữ chính tôi sử dụng cog không phải là python. :) Và tùy thuộc vào những gì anh ấy muốn làm, việc tạo mã tạo nên ý nghĩa hoàn hảo. Đó là những gì một trình biên dịch hoặc thông dịch viên thực hiện, lấy mã lệnh và đầu ra của bạn. –

0

Tôi muốn mở rộng câu trả lời được chấp nhận. Tôi muốn có khả năng có một danh sách rất dài các phương pháp trang trí được áp dụng cho một danh sách rất dài các phương pháp.

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 

Và đây là lớp học mà kết thúc tốt đẹp ban đầu

@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 
Các vấn đề liên quan