在 Python 中准确地得到你想要的东西是很棘手的——那是因为,当你做“instance.x.method”时——Python 首先从“instance”中检索属性“x”,然后它们会尝试找到“method” " 作为“x”对象本身的一个属性(没有对“实例”的任何引用,该“实例”最初具有对“x”的引用,可以从“方法”内部检索到 - 但用于框架内省)。
我说它“可以完成” - 并且适用于大多数类型的 x,但最终可能会失败或产生附带影响,具体取决于属性“x”的类型:如果你 __setattr__
为你的类编写一个方法对于实例上设置的每个属性,它实际上会创建该属性的动态子类——这将在新对象上启用所需的方法。缺点是并非所有类型的对象都可以被子分类,并且并非所有子分类对象的行为都与它们的父对象完全相同。(例如,如果“x”是一个函数)。但它适用于大多数情况:
class Base(object):
def __setattr__(self, name, attr):
type_ = type(attr)
new_dict = {}
for meth_name in dir(self.__class__):
function = getattr(self.__class__, meth_name)
# Assume any methods on the class have the desired behavior and would
# accept the attribute as it's second parameter (the first being self).
# This could be made more robust by making a simple method-decorator
# which would mark the methods that one wishes to be appliable
# to attributes, instead of picking all non "_" starting methods like here:
if not callable(function) or meth_name in new_dict or meth_name.startswith("_"):
continue
def pinner(f):
def auto_meth(se, *args, **kw):
return f(se._container, se, *args, **kw)
return auto_meth
new_dict[meth_name] = pinner(function)
# This could be improved in order to have a class-based cache of derived types
# so that each attribute setting would only create a new_type for
# each different type that is being set
new_type = type(type_.__name__, (type_,), new_dict)
try:
attr.__class__ = new_type
except TypeError:
# here is the main problem withthis approach:
# if the type being stored can't have it's `__class__`dynamically
# changed, we have to build a new instance of it.
# And if the constructor can't take just the base type
# as its building parameter, it won't work. Worse if having another instance
# does have side-effects in the code, we are subject to those.
attr = new_type(attr)
attr._container = self
super(Base, self).__setattr__(name, attr)
class oObject(Base):
def __init__(self, x = 0, y = 0, z = 0):
self.x = x
self.y = y
self.z = z
def asString(self, attr):
return str(attr)
在交互式部分中加载这些内容后:
>>> v = oObject(1,2,3)
>>> v.x.asString()
'1'
>>> v.w = [1,2,3]
>>> v.w.append(3)
>>> v.w.asString()
'[1, 2, 3, 4]'
>>>
正如你所看到的,这可以通过普通的类继承来完成,不需要元类。
对于任何 Parameter 类型,另一种更可靠的方法是对属性名称和方法使用另一个分隔符 - 您可以__getattribute__
在基类上编写一个更简单的方法,该方法将动态检查请求方法并调用它属性。这种方法不需要动态子分类,并且简单了大约 2 个数量级。代价是你会写一些类似的东西vector.x__asString
而不是点分隔符。这实际上是在用于 Python 的久经考验的 SQLALchemy ORM 中采用的方法。
# Second approach:
class Base(object):
separator = "__"
def __getattr__(self, attr_name):
if self.__class__.separator in attr_name:
attr_name, method_name = attr_name.split(self.__class__.separator, 1)
method = getattr(self, method_name)
return method(getattr(self, attr_name))
raise AttributeError
现在:
>>> class oObject(Base):
... def __init__(self, x = 0, y = 0, z = 0):
... self.x = x
... self.y = y
... self.z = z
...
... def asString(self, attr):
... return str(attr)
...
>>>
>>>
>>> v = oObject(1,2,3)
>>> v.x__asString
'1'
(如果您希望将更多参数传递给被调用的方法,则需要更多代码,但我认为这足以理解这个想法)。