为什么没有调用元类的__get__方法?

时间:2017-11-23 22:02:27

标签: python-3.x metaclass

我上了课Op

class Pipeable(type):
    def __get__(self, instance, owner):
        def pipe_within(*args, **kwargs):
            return self(*args, op=instance, **kwargs)
        print('piping...')
        return pipe_within

class Op(metaclass=Pipeable):
    def __init__(self, op=None):
        if op is not None:
            print('piped!')
        self.op = op
        self.__dict__[type(self).__name__] = type(self)

我希望Op类本身可以用作描述符,因为它的元类有__get__方法,但是代码

op = Op().Op()

不会调用Op.__get__。为什么呢?

2 个答案:

答案 0 :(得分:1)

很难说出你真正想要的东西。但是,在每个新类中为自己添加属性的元类可能更适合您想要的任何内容。

就我能理解你的代码而言,旧的类不会被新的实例引用填充,因为你创建新实例(反过来,获取其他人的参考)。

但是,在第二个版本中,创建属性inisde __new__似乎很苛刻 - 但您可以实现元类__getattr____dir__方法,以获得更少复杂的代码:

简单版本适用于类,但不适用于它们的实例 - 因为实例不会触发元类上的__getattr__

class Pipeable(type):
    _classes = {}

    def __new__(metacls, name, bases, namespace, **kwds):
        cls = type.__new__(metacls, name, bases, namespace)
        metacls._classes[name] = cls 
        return cls


    def __getattr__(cls, attr):
        classes = cls.__class__._classes
        if attr not in classes:
            raise AttributeError
        def pipe_within(*args, **kwargs):
            return cls(*args, op=classes[attr], **kwargs)
        print('piping...')
        return pipe_within

    def __dir__(cls):
        regular = super().__dir__()
        return sorted(regular + list(cls.__class__._classes.keys()))


class Op(metaclass=Pipeable):
    def __init__(self, op=None):
        if op is not None:
            print('piped!')
        self.op = op

Op.Op()

(另请注意,随着时间的推移,我选择了这个参数命名约定用于元类 - 因为大多数方法都采用用它们创建的类来代替普通类中的“self”,我发现这个命名更容易遵循。虽然不是强制性的,但不一定是“正确的”

但是,我们可以通过直接在创建的类上创建__dir____getattr__来使其适用于实例。与此相关的是,您正在创建的类已经有__getattr__或自定义__dir__,即使在他们的超类中,也必须包含它们。然后,我们不想重新包装我们自己的__dir____getattr__,所以需要一些额外的关注:

class Pipeable(type):
    _classes = {}

    def __new__(metacls, name, bases, namespace, **kwds):
        cls = type.__new__(metacls, name, bases, namespace)
        metacls._classes[name] = cls 
        original__getattr__ =  getattr(cls, "__getattr__", None)
        if hasattr(original__getattr__, "_metapipping"):
            # Do not wrap our own (metaclass) implementation of __getattr__
            original__getattr__ = None
        original__dir__ =  getattr(cls, "__dir__")  # Exists in "object", so it is always found.

        # these two functions have to be nested so they can get the 
        # values for the originals "__getattr__" and "__dir__" from
        # the closure. These values could be set on the class created, alternatively. 
        def __getattr__(self, attr):
            if original__getattr__:
                # If it is desired that normal attribute lookup have
                # less precedence than these injected operators
                # move this "if" block down. 
                try:
                    value = original__getattr__(self, attr)
                except AttributeError:
                    pass
                else:
                    return value
            classes = self.__class__.__class__._classes
            if attr not in classes:
                raise AttributeError
            def pipe_within(*args, **kwargs):
                return cls(*args, op=classes[attr], **kwargs)
            print('piping...')
            return pipe_within
        __getattr__._pipping = True

        def __dir__(self):
            regular = original__dir__(self)
            return sorted(regular + list(self.__class__.__class__._classes.keys()))
        __dir__.pipping = True

        if not original__getattr__ or not hasattr(original__getattr__, "_pipping"):
            cls.__getattr__ = __getattr__
        if not hasattr(original__dir__, "_pipping"):
            cls.__dir__ = __dir__
        return cls


    def __getattr__(cls, attr):
        classes = cls.__class__._classes
        if attr not in classes:
            raise AttributeError
        def pipe_within(*args, **kwargs):
            return cls(*args, op=classes[attr], **kwargs)
        print('piping...')
        return pipe_within
    __getattr__._metapipping = True

    def __dir__(cls):
        regular = super().__dir__()
        return sorted(regular + list(cls.__class__._classes.keys()))


class Op(metaclass=Pipeable):
    def __init__(self, op=None):
        if op is not None:
            print('piped!')

Op().Op()

因此,这最终会变得冗长 - 但是通过确保层次结构中的所有类和实例可以看到彼此,无论创建顺序如何,它都“做正确的事”。

此外,弥补复杂性的原因是在类层次结构中正确包装__getattr____dir__的其他可能的自定义项 - 如果您没有对其进行任何自定义,这可以是一个订单更简单:

class Pipeable(type):
    _classes = {}

    def __new__(metacls, name, bases, namespace, **kwds):
        cls = type.__new__(metacls, name, bases, namespace)
        metacls._classes[name] = cls

        def __getattr__(self, attr):
            classes = self.__class__.__class__._classes
            if attr not in classes:
                raise AttributeError
            def pipe_within(*args, **kwargs):
                return cls(*args, op=classes[attr], **kwargs)
            print('piping...')
            return pipe_within

        def __dir__(self):
            regular = original__dir__(self)
            return sorted(regular + list(self.__class__.__class__._classes.keys()))

        cls.__getattr__ = __getattr__
        cls.__dir__ = __dir__

        return cls

    def __getattr__(cls, attr):
        classes = cls.__class__._classes
        if attr not in classes:
            raise AttributeError
        def pipe_within(*args, **kwargs):
            return cls(*args, op=classes[attr], **kwargs)
        print('piping...')
        return pipe_within

    def __dir__(cls):
        regular = super().__dir__()
        return sorted(regular + list(cls.__class__._classes.keys()))

答案 1 :(得分:0)

要开始工作,描述符必须是class属性,而不是实例。 这段代码完成了所需的工作。

class Pipeable(type):
    _instances = {}

    def __new__(cls, name, bases, namespace, **kwds):
        namespace.update(cls._instances)
        instance = type.__new__(cls, name, bases, namespace)

        cls._instances[name] = instance
        for inst in cls._instances:
            setattr(inst, name, instance)
        return instance

    def __get__(self, instance, owner):
        def pipe_within(*args, **kwargs):
            return self(*args, op=instance, **kwargs)
        print('piping...')
        return pipe_within


class Op(metaclass=Pipeable):
    def __init__(self, op=None):
        if op is not None:
            print('piped!')
        self.op = op

Op().Op()