我试图做这样的事情:
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"
这是一个以实例作为参数并执行与元类相同的工作的函数:
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"
答案 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)
元类肯定是要走的路。你的元类实现是一个很好的实现,但它在几个边缘情况下都会遇到。这就是有几个可调用的东西,但不会变成可以存在于类中的实例方法。例如,您可能有一个staticmethod
或classmethod
,甚至可以在父类中定义一个类,或者最简单的是一个具有__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)