从Python3中的比较中获取对象的类型和属性的名称

时间:2018-01-17 21:52:31

标签: python python-3.x

这是类的定义:

class Child:
    pass
class Parent:
    child = Child()

其中Child可以有不同的argskwargs,因此请不要考虑这种表示法:child = Child(Parent, 'child')

完成作业之后:

result = Parent.child > 1

其中Parent是一种类型(不是实例)

结果应该是:

{
    'type': Parent,
    'property': 'child',
    'action': '>',
    'value': 1,
}

我不确定它是否可能,但有没有人知道一招?

2 个答案:

答案 0 :(得分:4)

是的,这是可能的,但您需要明确地将父引用存储在子对象中。 >运算符可以与__gt__ method挂钩。

由于您正在生成包含child属性的动态信息的输出,因此您可以使用__getattr__ method生成动态子对象,只要访问了其他不存在的属性,就会调用它:

class Child:
    def __init__(self, parent_type, attr_name):
        self._parent_type = parent_type
        self._parent_attr_name = attr_name

    def __gt__(self, other):
        return {
            'type': self._parent_type,
            'property': self._parent_attr_name,
            'action': '>',
            'value': other,
        }

class Parent:
    def __getattr__(self, name):
        return Child(type(self), name)

演示:

>>> Parent().child > 1
{'type': <class '__main__.Parent'>, 'property': 'child', 'action': '>', 'value': 1}

如果Parent 必须是一个类(为什么?),那么使用固定属性(child = Child(Parent, 'child')),或使用元类:

class ParentMeta(type):
    def __getattr__(cls, name):
        return Child(cls, name)

class Parent(metaclass=ParentMeta):
    pass

此时您可以获得相同的效果,但无需创建实例:

>>> Parent.child > 1
{'type': <class '__main__.Parent'>, 'property': 'child', 'action': '>', 'value': 1}

从Python 3.6开始,您还可以使用__set_name__ hook来捕获定义属性的类的类型:

class Child:
    def __set_name__(self, owner, attr_name):
        self._parent_type = owner
        self._parent_attr_name = attr_name

    def __gt__(self, other):
        return {
            'type': self._parent_type,
            'property': self._parent_attr_name,
            'action': '>',
            'value': other,
        }

请注意,这些属性未在__init__中设置!您可以将子对象创建为类的属性,此时会自动调用__set_name__方法。输出再次相同:

>>> class Parent:
...     child = Child()
...
>>> Parent.child > 1
{'type': <class '__main__.Parent'>, 'property': 'child', 'action': '>', 'value': 1}

请注意,子类不会反映在父类型中;你必须每次使用__get__ method捕获当前类型并返回一个包装器对象:

class ChildWrapper:
    def __init__(self, child, parent_type):
        self._child = child
        self._parent_type = parent_type

    def __getattr__(self, name):
        return getattr(self._child, name)

    def __gt__(self, other):
        return {
            'type': self._parent_type,
            'property': self._child._parent_attr_name,
            'action': '>',
            'value': other,
        }

class Child:
    def __set_name__(self, owner, attr_name):
        self._parent_attr_name = attr_name

    def __get__(self, instance, owner):
        return ChildWrapper(self, owner)

这会在每个属性访问(在类或实例上)上创建一个ChildWrapper()实例,并且所有进一步的属性访问都会被委托回原始Child()实例(但请注意特殊方法{{ 3}}):

>>> class Parent:
...     child = Child()
...
>>> Parent.child
<__main__.ChildWrapper object at 0x10fc564e0>
>>> Parent.child > 1
{'type': <class '__main__.Parent'>, 'property': 'child', 'action': '>', 'value': 1}

因为每次根据访问对象创建ChildWrapper,所以这也适用于子类化,跟踪当前类型:

>>> class Subclass(Parent):
...     pass
...
>>> Subclass.child > 1
{'type': <class '__main__.Subclass'>, 'property': 'child', 'action': '>', 'value': 1}

答案 1 :(得分:0)

供将来参考:

class BaseChild:
    type = None
    property = None

    def __gt__(self, other):
        return {
            'type': self.type,
            'property': self.property,
            'action': '>',
            'value': other,
        }


class MetaParent(type):
    def __new__(mcs, name, bases, attrs):
        cls = super().__new__(mcs, name, bases, attrs)

        # This is the needed trick
        for name, attr in attrs.items():
            if isinstance(attr, BaseChild):
                attr.type = cls
                attr.property = name

        return cls


class SomeChild(BaseChild):
    pass


class Parent(metaclass=MetaParent):
    first_child = SomeChild()


print(Parent.first_child > 1)

结果:

{'type': <class '__main__.Parent'>, 'value': 1, 'property': 'first_child', 'action': '>'}