在Python中记住最后一个名为/ get的方法

时间:2014-10-21 15:23:11

标签: python

我试图做这样的事情:

inst = AnyClass()
remember_last_method(inst)

inst.foo()
inst.bar()

print inst.last_method # print bar

inst.foo
print inst.last_method # print foo

inst.remember_last_method = False

inst.bar()
print inst.last_method # print foo
inst.remember.last_method = True

有关编写remember_last_method函数的建议吗?

首先编辑:

似乎投票是否定的......

以下是我开始编写的代码,如果它可以澄清问题:

def remember_last_method_get(cls):
    """
    Subclass cls in a way that remeber last method get by instances

    >>> @remember_last_method_get
    ... class Foo(object):
    ...     def bar(self):
    ...         pass
    ...     def baz(self):
    ...         pass
    >>> foo = Foo()
    >>> foo.bar()
    >>> foo.baz()
    >>> print foo.last_method_get
    baz
    >>> m = foo.bar # get a method without calling it
    >>> print foo.last_method_get
    bar
    """
    class clsRememberLastMethodGet(cls):
        def __getattribute__(self,name):
            attr = cls.__getattribute__(self,name)
            if callable(attr):
                self.last_method_get = name
            return attr
    return clsRememberLastMethodGet
if __name__ == '__main__':
    import doctest
    doctest.testmod()

根据我的需要在实例上而不是在类上工作,并且没有remember_last_method = True / False属性

第二次编辑:

这是一个完成工作的元类(仅用于调用的方法,而不是 得到,哪个更好):

class RememberLastMethod(type):
    def __init__(self, name, bases, d):
        type.__init__(self, name, bases, d)
        for name,attr in d.iteritems():
            if not callable(attr) or name.startswith('_'):
                continue
            def create_new_attr(name,attr):
                def new_attr(self,*args,**kwargs):
                    if self.remember_last_method:
                        self.last_method = name
                    return attr(self,*args,**kwargs)
                return new_attr
            setattr(self,name,create_new_attr(name,attr))
        orig__init__ = self.__init__
        def new__init__(self,*args,**kwargs):
            self.remember_last_method = True
            self.last_method = None
            orig__init__(self)
        self.__init__ = new__init__

class AnyClass(object):
    __metaclass__ = RememberLastMethod
    def foo(self):
        pass
    def bar(self):
        pass

# Call two method, inst.last_method is the last
inst = AnyClass()
inst.foo()
inst.bar()
assert inst.last_method == "bar"

# Call a new method, changes inst.last_method.
inst.foo()
assert inst.last_method == "foo"

# Stop changing inst.last_method.
inst.remember_last_method = False
inst.bar()
assert inst.last_method == "foo"

# Changing last_method again.
inst.remember_last_method = True
inst.bar()
assert inst.last_method == "bar"

# Work with reference to method as well
method = inst.foo
inst.remember_last_method = False
method()
assert inst.last_method == "bar"
inst.remember_last_method = True
method()
assert inst.last_method == "foo"

Thrid编辑:

这是一个以实例作为参数并执行与元类相同的工作的函数:

def remember_last_method(inst):
    inst.remember_last_method = True
    cls = inst.__class__
    for name in dir(inst):
        if name.startswith('_'):
            continue
        attr = getattr(inst,name)
        if not callable(attr):
            continue
        def create_new_attr(name,attr):
            def new_attr(self,*args,**kwargs):
                if self.remember_last_method:
                    self.last_method = name
                return attr(*args,**kwargs)
            return new_attr
        setattr(cls,name,create_new_attr(name,attr))

class AnyClass(object):
    def foo(self):
        pass
    def bar(self):
        pass

inst = AnyClass()
remember_last_method(inst)

# Call two method, inst.last_method is the last
inst.foo()
inst.bar()
assert inst.last_method == "bar"

# Call a new method, changes inst.last_method.
inst.foo()
assert inst.last_method == "foo"

# Stop changing inst.last_method.
inst.remember_last_method = False
inst.bar()
assert inst.last_method == "foo"

# Changing last_method again.
inst.remember_last_method = True
inst.bar()
assert inst.last_method == "bar"

# Work with reference to method as well
method = inst.foo
inst.remember_last_method = False
method()
assert inst.last_method == "bar"
inst.remember_last_method = True
method()
assert inst.last_method == "foo"

2 个答案:

答案 0 :(得分:0)

您可以自己为每种方法实现此目的:

class X(object):
    def __init__(self):
        self.last_method = None
        self.should_store_last_method = True

    def set_last_method(self, meth):
        if self.should_store_last_method:
            self.last_method = meth

    def store_last_method(self, should_store):
        self.should_store_last_method = should_store

    def one(self):
        self.set_last_method(self.one)
        print("ONE")

    def two(self):
        self.set_last_method(self.two)
        print("TWO")

使用它:

x = X()

x.one()
# ONE
print x.last_method
# <bound method X.one of <__main__.X object at 0x1035f8210>>
x.last_method()
# ONE
x.two()
# TWO
print x.last_method
# <bound method X.two of <__main__.X object at 0x1035f8210>>
x.last_method()
# TWO
x.store_last_method(False)
x.one()
# ONE
print x.last_method
# <bound method X.one of <__main__.X object at 0x1035f8210>>

给出:

ONE
<bound method X.one of <__main__.X object at 0x1035f8210>>
ONE
TWO
<bound method X.two of <__main__.X object at 0x1035f8210>>
TWO
ONE
<bound method X.one of <__main__.X object at 0x1035f8210>>

答案 1 :(得分:0)

元类肯定是要走的路。你的元类实现是一个很好的实现,但它在几个边缘情况下都会遇到。这就是有几个可调用的东西,但不会变成可以存在于类中的实例方法。例如,您可能有一个staticmethodclassmethod,甚至可以在父类中定义一个类,或者最简单的是一个具有__call__方法的类的对象。

您的函数/属性实现避免了这些问题,但是记录这些函数调用的缺点。这些函数不能访问可以找到它们的对象,所以你真的想录制它们吗?

我在下面提供了一个实现。此元类仅适用于Python 3.要转换为Python 2,您必须从name上的__init____new__方法中删除MethodLoggerMetaclass arg。您还必须使用__metaclass__名称,而不是在类声明行中将其作为参数提供。

from types import FunctionType, MethodType
from collections import Callable
import builtins

class MethodLoggerMetaclass(type):
    """Records the last method that was called on a class as the base function
    (called an unbound method in python 2.x)

    By default _last_method is used to record which method was last called.
    Use `class MyClass(metaclass=MethodLoggerMetaclass, name="new_name")' to
    change this behaviour.

    Set record_superclass to True to also record the methods on superclasses.

    Set record_hidden to True to also record attributes beginning with s
    single underscore.

    Set record_callable to True to also record callable objects that are *not*
    instance methods. eg. static methods and class methods."""

    def __init__(self, classname, parents, attributes, name="_last_method",
                 record_superclass=False, record_hidden=False, 
                 record_callable=False):
        type.__init__(self, classname, parents, attributes)
        method_logger_names[self] = name
        if record_superclass:
            for attr, value, needs_self in get_superclass_functions(self, 
                    record_hidden, record_callable):
                type.__setattr__(self, attr, wrap(value, name, needs_self))

    def __new__(metaclass, classname, parents, attributes, 
                name="_last_method", record_superclass=False, 
                record_hidden=False, record_callable=False):
        types = FunctionType if not record_callable else Callable
        for attr, value in attributes.items():
            if record(attr, value, record_hidden, types):
                attributes[attr] = wrap(value, name, isinstance(value, 
                    FunctionType))
        attributes[name] = MethodLoggerProperty()
        return type.__new__(metaclass, classname, parents, attributes)

    def __setattr__(self, attr, value):
        """Used to wrap functions that are added to the class after class
        creation."""
        if isinstance(value, FunctionType):
            type.__setattr__(self, attr, wrap(value, 
                             method_logger_names[self], True))    
        else:
            type.__setattr__(self, attr, value)


class MethodLogger:
    """Used to record the last method called on a instance. Method stored as
    base function. Has convenience properties for getting just the name or as a
    method, or the unwrapped function.

    Calling the provided function or method will also record the call if record
    is set to True."""

    _function = None
    record = True

    def __init__(self, instance):
        self.instance = instance

    @property
    def function(self):
        return wrap(self._function, method_logger_names[type(self.instance)])

    @function.setter
    def function(self, function):
        if self.record:
            self._function = function

    @property
    def unwrapped_function(self):
        return self._function

    @property
    def method(self):
        if self._function:
            return MethodType(self.function, self.instance)

    @property
    def name(self):
        if self._function:
            return self._function.__name__

    def __str__(self):
        return "MethodLogger(instance={}, function={}, record={})".format(
            self.instance, self._function, self.record)
    __repr__ = __str__

class MethodLoggerProperty:
    """Provides initial MethodLogger for new instances of a class"""
    def __get__(self, instance, cls=None):
        method_logger = MethodLogger(instance)
        setattr(instance, method_logger_names[cls], method_logger)
        return method_logger

def wrap(function, method_logger_name, needs_self):
    """Wraps a function and in a logging function, and makes the wrapper
    appear as the original function."""
    if needs_self:
        def wrapper(self, *args, **kwargs):
            if getattr(self, method_logger_name).record:
                getattr(self, method_logger_name).function = function
            return function(self, *args, **kwargs)
    else: 
        def wrapper(self, *args, **kwargs):
            if getattr(self, method_logger_name).record:
                getattr(self, method_logger_name).function = function
            return function(*args, **kwargs) 
    wrapper.__name__ = getattr(function, "__name__", str(function))
    wrapper.__doc__ = function.__doc__
    return wrapper


# used to store name where the method logger is stored for each class
method_logger_names = {}


def record(attr, value, record_hidden, types=FunctionType):
    """Returns whether an attribute is a method and should be logged.
    Never returns true for "dunder" attributes (names beginning with __)"""
    return isinstance(value, types) and not attr.startswith("__") and \
        (record_hidden or not attr.startswith("_"))


def get_superclass_functions(cls, include_hidden, include_callable):
    """Finds all functions derived from the superclasses of a class. Gives
    the name under which the function was found and the function itself.

    Returns tuples of (attribute name, function, if the function needs an
    object instance). If `include_callable' is False then the the function 
    always needs an object instance."""
    base_types = FunctionType if not include_callable else Callable
    attrs = set(vars(cls).keys())
    for superclass in cls.mro()[1:-1]: # exclude actual class and object
        types = (base_types if not hasattr(builtins, superclass.__name__) else 
            Callable)
        for attr, value in vars(superclass).items():
            if attr not in attrs and record(attr, value, include_hidden, types):
                attrs.add(attr)
                yield attr, value, (isinstance(value, FunctionType) or 
                    hasattr(builtins, superclass.__name__))

使用示例:

class MethodLoggerList(list, metaclass=MethodLoggerMetaclass, 
                       name="_method_logger", record_superclass=True):
    def f(self, kwarg="keyword argument"):
        print(self, kwarg)
    def g(self):
        pass

# example use
t = MethodLoggerList()
print(t._method_logger)
t.f()
t.f("another value")
print(t._method_logger)

# note that methods on superclass are not recorded by default
t.append(0)
print(t._method_logger)
# won't record dunder/magic methods at all
t += [1]
print(t._method_logger)

# stop recording
t._method_logger.record = False
t.g()
print(t._method_logger)

# add methods to class after class creation, and still record them
def h(self):
    pass
MethodLoggerList.h = h
t._method_logger.record = True
t.h()
print(t._method_logger)

# also records lambdas
MethodLoggerList.i = lambda self: None
t.i()
print(t._method_logger)

# does not record monkey-patched methods
def j():
    pass
t.j = j
t.j()
print(t._method_logger)

# does record method or function access from _last_method
method = t._method_logger.method
t.g()
method()
print(t._method_logger)