Python中的元类和语法

3
我尝试制作类似于这样的东西:
class oObject(object):
    def __init__(self, x = 0, y = 0, z = 0):
        self.x = x
        self.y = y
        self.z = z

def asString (self, value):
    return str(value)

vector = oObject(5,5,5)

# So i can do
asString(vector.x)

# But I want this kind of syntax
vector.x.asString()

这只是一个例子,我并不真正想将整数转换为字符串。更多的是关于类转换成类。


你是在谈论如何向内置的 int 添加自定义方法吗?如果这只是一个例子,而你真正指的是对象,那么 x 可能是一个带有 asString 方法的自定义对象。 - jdi
是的,我想添加动态自定义方法。不仅针对x,还包括y和z。但我不知道如何做到这一点。 - MObject
你可以像这样做:vector.asString('x') - Joel Cornett
3个回答

3
你可以为你的oObject类编写一个自定义方法来返回给定key的字符串,或者你可以编写一个自定义的Variant类并包装你的值:
class oObject(object):
    def __init__(self, x = 0, y = 0, z = 0):
        self.x = Variant(x)
        self.y = Variant(y)
        self.z = Variant(z)

class Variant(object):
    def __init__(self, obj):
        self._obj = obj

    def __repr__(self):
        return '<%s: %s>' % (self.__class__.__name__, self.asString())

    def __str__(self):
        return self.asString()

    def asString(self):
        return str(self._obj)

    def value(self):
        return self._obj

查看此参考文献,了解PyQt4如何使用Qt中的QVariant类来处理多类型数据。虽然在Python中通常不需要这种类型,但在C++中需要它来表示多个类型。


2

你在 Python 中 不能 不应该做这种事情。

然而,你可以在类中实现标准的__str__方法,当使用str(instance)将实例转换为字符串时,该代码将被使用。

在技术上,你可以在 Python 中玩很多技巧,试图弯曲语法到你习惯的样子,但这是一个坏主意,因为许多工作已经被投入到使 Python 更可读的方向,而你基本上正在破坏这项工作。

在 Python 中,转换为字符串是通过str(x)完成的,而不是调用名为asString的方法。使用__str__,你已经可以自定义str将返回什么,为什么要添加一个方法呢?如果你需要一种自定义字符串转换的方法,那么只需定义一个分派到对象类型的函数,而不是尝试在现有类上注入新方法:

converters = dict()

def add_converter(klass, f):
    converters[klass] = f

def default_converter(x):
    return "<%s: %s>" % (x.__class__.__name__, str(x))

def mystr(x):
    return converters.get(x.__class__, default_converter)(x)

这种方法没有“魔法”(即令人惊讶的)行为,也不会包装东西(另一种可能会令读代码的人感到惊讶的方法)。
在上面的例子中,我没有处理转换器继承,但是如果需要并且确实需要它(不确定将一个转换为字符串函数继承是否有意义,因为它会默默地丢失信息),可以通过使用更复杂的查找来实现。
此外,如果您不了解元类的用途,请不要涉及该概念,因为大多数情况下您实际上并不需要它。元类是一个强大但有些复杂的工具,通常并不经常使用...
我认为这篇文章是关于元类是什么以及您可以用它们做什么的很好的一般性说明。请注意,某些细节被省略了,您应该使用官方文档来深入了解。

是的,也许你是对的,我不理解这个概念。我以为需要一个元类... - MObject
1
@jsbueno:我认为在Python中没有合理的方法可以添加一个新的数字方法而不会导致这种结构泄漏(即不按预期行为)。关键点是,在Python中做这种事情真的是毫无意义的。实际上,我认为在一般情况下使用这种方法也很少有意义......在任何我能想到的情况下,基于参数类型的顶层函数分派更加清晰且更有效。 - 6502
这并不是关于向数字本身添加新方法 - 在Python中确实是不可能的 - 而是关于向“数字”或其他作为另一个对象属性的对象添加新行为。如果在属性检索时适当地包装对象(如下面的示例),则可以完成此操作。问题是关于后者 - 我没有从您最初的措辞中理解到您正在谈论向数字本身添加属性,因为在Ruby中是可能的。 - jsbueno

0
在Python中确切地获得你所要求的内容是棘手的 - 这是因为,当你执行“instance.x.method”时,Python首先从“instance”检索属性“x”,然后尝试在“x”对象本身中查找“method”作为属性(与最初引用“x”的“instance”无关,可能可以从“方法”内部检索到“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]'
>>> 

如你所见,这可以通过普通的类继承来实现,不需要元类。
对于任何参数类型,另一种更可靠的方法是使用另一个分隔符来表示属性名和方法 - 然后你可以在基类上编写一个更简单的`__getattribute__`方法,动态地检查请求的方法并调用相应的属性。这种方法不需要动态子类化,而且要简单两个数量级。代价是你需要像`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'

如果您想要传递更多参数给被调用的方法,那么需要编写更多的代码,但我认为这已经足够让您了解思路了。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接