Use class method not instance method with the same name

前端 未结 1 2015
我寻月下人不归
我寻月下人不归 2020-12-12 05:03

I have the following snippet:

class Meta(type):
    def __getattr__(self, name):
        pass

class Klass(object):
    __metaclass__ = Meta

    def get(sel         


        
相关标签:
1条回答
  • 2020-12-12 05:31

    You'll have to look up the method on the type and pass in the first (self) argument manually:

    type(Klass).get(Klass, 'arg')
    

    This problem is the very reason that special method names are looked up using this path; custom classes would not be hashable or representable themselves if Python didn't do this.

    You could make use of that fact; rather than use a get() method, use __getitem__, overloading [..] indexing syntax, and have Python do the type(ob).methodname(ob, *args) dance for you:

    class Meta(type):
        def __getitem__(self, arg):
            pass
    
    class Klass(object):
        __metaclass__ = Meta
    
        def __getitem__(self, arg):
            pass
    

    and then Klass()['arg'] and Klass['arg'] work as expected.

    However, if you have to have Klass.get() behave differently (and the lookup for this to be intercepted by Meta.__getattribute__) you have to explicitly handle this in your Klass.get method; it'll be called with one argument less if called on the class, you could make use of that and return a call on the class:

    _sentinel = object()
    
    class Klass(object):
        __metaclass__ = Meta
    
        def get(self, arg=_sentinel):
            if arg=_sentinel:
                if isinstance(self, Klass):
                    raise TypeError("get() missing 1 required positional argument: 'arg'")
                return type(Klass).get(Klass, self)
            # handle the instance case ... 
    

    You could also handle this in a descriptor that mimics method objects:

    class class_and_instance_method(object):
        def __init__(self, func):
            self.func = func
        def __get__(self, instance, cls=None):
            if instance is None:
                # return the metaclass method, bound to the class
                type_ = type(cls)
                return getattr(type_, self.func.__name__).__get__(cls, type_)
            return self.func.__get__(instance, cls)
    

    and use this as a decorator:

    class Klass(object):
        __metaclass__ = Meta
    
        @class_and_instance_method
        def get(self, arg):
            pass
    

    and it'll redirect look-ups to the metaclass if there is no instance to bind to:

    >>> class Meta(type):
    ...     def __getattr__(self, name):
    ...         print 'Meta.{} look-up'.format(name)
    ...         return lambda arg: arg
    ... 
    >>> class Klass(object):
    ...     __metaclass__ = Meta
    ...     @class_and_instance_method
    ...     def get(self, arg):
    ...         print 'Klass().get() called'
    ...         return 'You requested {}'.format(arg)
    ... 
    >>> Klass().get('foo')
    Klass().get() called
    'You requested foo'
    >>> Klass.get('foo')
    Meta.get look-up
    'foo'
    

    Applying the decorator can be done in the metaclass:

    class Meta(type):
        def __new__(mcls, name, bases, body):
            for name, value in body.iteritems():
                if name in proxied_methods and callable(value):
                    body[name] = class_and_instance_method(value)
            return super(Meta, mcls).__new__(mcls, name, bases, body)
    

    and you can then add methods to classes using this metaclass without having to worry about delegation:

    >>> proxied_methods = ('get',)
    >>> class Meta(type):
    ...     def __new__(mcls, name, bases, body):
    ...         for name, value in body.iteritems():
    ...             if name in proxied_methods and callable(value):
    ...                 body[name] = class_and_instance_method(value)
    ...         return super(Meta, mcls).__new__(mcls, name, bases, body)
    ...     def __getattr__(self, name):
    ...         print 'Meta.{} look-up'.format(name)
    ...         return lambda arg: arg
    ... 
    >>> class Klass(object):
    ...     __metaclass__ = Meta
    ...     def get(self, arg):
    ...         print 'Klass().get() called'
    ...         return 'You requested {}'.format(arg)
    ... 
    >>> Klass.get('foo')
    Meta.get look-up
    'foo'
    >>> Klass().get('foo')
    Klass().get() called
    'You requested foo'
    
    0 讨论(0)
提交回复
热议问题