【问题标题】:Why doesn't __get__ method of metaclass get called?为什么不调用元类的 __get__ 方法?
【发布时间】:2018-05-07 21:44:47
【问题描述】:

我上课了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__。为什么?

【问题讨论】:

  • 描述符必须放在类上,而不是实例上。

标签: python-3.x metaclass


【解决方案1】:

要开始工作,描述符必须是类属性,而不是实例属性。 这段代码做了我们想要的。

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()

【讨论】:

    【解决方案2】:

    很难说你真正想要什么。但是,在每个新类中添加一个属性的元类可能更适合你想要的任何东西。

    据我所知,在您创建新实例时,旧类不会填充对新类的引用(反过来,获取其他类的引用)。

    尽管如此,动态地创建 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()
    

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

    但是,我们也可以通过直接在创建的类上创建__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()))
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-03-05
      • 2019-05-08
      • 1970-01-01
      • 2014-07-17
      • 1970-01-01
      • 1970-01-01
      • 2018-07-11
      • 2019-11-15
      相关资源
      最近更新 更多