如何根据属性访问类型(使用 class 或实例)在函数之间创建 "switch"?

How to make a "switch" between functions, depending on attribute access type (using class or instance)?

免责声明:

这篇文章与其说是一个问题,不如说是一个秘诀,但我发现这个主题很有趣,在网络上几乎没有参考资料。

如果 Whosebug 上有更好的地方可以发布这类文章,请告诉我。

主题:

如何根据属性访问类型(使用 class 或实例)强制 Python 调用不同的函数 - 例如强制 Python 为 MyClass.my_method()MyClass().my_method()?

调用不同的方法

用例:

比方说,我们有自定义枚举实现(基于 Python36 枚举,但有一些自定义)。作为这个 Enum 的用户,我们想创建一个 CustomEnum,不仅继承 Enum,还继承 str: class MyEnum(str, Enum)。我们还想添加编码和解码功能。我们的想法是使用 MyEnum.encode 来编码任何对象,包括我们的枚举成员,但保留原始 str.encode 对我们的枚举 class 实例的权力。 简而言之:MyEnum.encode 调用我们的自定义编码函数,并且从这个角度来看具有完美的意义。 MyEnum() 是一个字符串,所以 MyEnum().encode 应该调用继承自 str class.

的编码函数

解法:

写一个描述符,它将作为一个开关。 完整答案在我的第一个 post.

解法:

据我所知,描述符是唯一可以区分的对象,如果它们是为 class 或实例调用,因为 __get__ 函数签名:__get__(self, instance, instance_type) . 属性 允许我们在其上构建一个开关。

class boundmethod(object):    
    def __init__(self, cls_method=None, instance_method=None, doc=None):
        self._cls_method = cls_method
        self._instance_method = instance_method
        if cls_method:
            self._method_name = cls_method.__name__
        elif instance_method:
            self._method_name = instance_method.__name__

        if doc is None and cls_method is not None:
            doc = cls_method.__doc__
        self.__doc__ = doc

        self._method = None
        self._object = None

    def _find_method(self, instance, instance_type, method_name):
        for base in instance_type.mro()[1:]:
            method = getattr(base, method_name, None)
            if _is_descriptor(method):
                method = method.__get__(instance, base)
            if method and method is not self:
                try:
                    return method.__func__
                except AttributeError:
                    return method

    def __get__(self, instance, instance_type):
        if instance is None:
            self._method = self._cls_method or self._find_method(instance, instance_type, self._method_name)
            self._object = instance_type
        else:
            self._method = self._instance_method or self._find_method(instance, instance_type, self._method_name)
            self._object = instance
        return self

    @staticmethod
    def cls_method(obj=None):
        def constructor(cls_method):
            if obj is None:
                return boundmethod(cls_method, None, cls_method.__doc__)
            else:
                return type(obj)(cls_method, obj._instance_method, obj.__doc__)

        if isinstance(obj, FunctionType):
            return boundmethod(obj, None, obj.__doc__)
        else:
            return constructor

    @staticmethod
    def instance_method(obj=None):
        def constructor(instance_method):
            if obj is None:
                return boundmethod(None, instance_method, instance_method.__doc__)
            else:
                return type(obj)(obj._cls_method, instance_method, obj.__doc__)

        if isinstance(obj, FunctionType):
            return boundmethod(None, obj, obj.__doc__)
        else:
            return constructor

    def __call__(self, *args, **kwargs):
        if self._method:
            try:
                return self._method(self._object, *args, **kwargs)
            except TypeError:
                return self._method(*args, **kwargs)
        return None

示例:

>>> class Walkmen(object):
...     @boundmethod.cls_method
...     def start(self):
...         return 'Walkmen start class bound method'
...     @boundmethod.instance_method(start)
...     def start(self):
...         return 'Walkmen start instance bound method'
>>> print Walkmen.start()
Walkmen start class bound method
>>> print Walkmen().start()
Walkmen start instance bound method

希望对大家有所帮助。

最佳。

实际上我只是问了这个问题( I hadn't seen this question). 使用描述符和元类进行继承。

来自 :

class dynamicmethod:
    '''
        Descriptor to allow dynamic dispatch on calls to class.Method vs obj.Method

        fragile when used with inheritence, to inherit and then overwrite or extend
        a dynamicmethod class must have dynamicmethod_meta as its metaclass
    '''
    def __init__(self, f=None, m=None):
        self.f = f
        self.m = m

    def __get__(self, obj, objtype=None):
        if obj is not None and self.f is not None:
            return types.MethodType(self.f, obj)
        elif objtype is not None and self.m is not None:
            return types.MethodType(self.m, objtype)
        else:
            raise AttributeError('No associated method')

    def method(self, f):
        return type(self)(f, self.m)

    def classmethod(self, m):
        return type(self)(self.f, m)

def make_dynamicmethod_meta(meta):
    class _dynamicmethod_meta(meta):
        def __prepare__(name, bases, **kwargs):
            d = meta.__prepare__(name, bases, **kwargs)
            for base in bases:
                for k,v in base.__dict__.items():
                    if isinstance(v, dynamicmethod):
                        if k in d:
                            raise ValueError('Multiple base classes define the same dynamicmethod')
                        d[k] = v
            return d

    return _dynamicmethod_meta

dynamicmethod_meta=make_dynamicmethod_meta(type)

class A(metaclass=dynamicmethod_meta):
    @dynamicmethod
    def a(self):
       print('Called from obj {} defined in A'.format(self))

    @a.classmethod
    def a(cls)
       print('Called from class {} defined in A'.format(cls))

class B(A):
   @a.method
   def a(self):
       print('Called from obj {} defined in B'.format(self))

A.a()
A().a()
B.a()
B().a()

结果:

Called from class <class 'A'> defined in A
Called from obj <A object at ...> defined in A
Called from class <class 'B'> defined in A
Called from obj <B object at ...> defined in B