classmethod 和 instancemethod 的名称相同 [英] Same name for classmethod and instancemethod

查看:36
本文介绍了classmethod 和 instancemethod 的名称相同的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想做这样的事情:

X 类:@类方法定义 ID(cls):返回 cls.__name__定义 ID(自己):返回 self.__class__.__name__

现在为类或其实例调用id():

<预><代码>>>>X.id()'X'>>>X().id()'X'

显然,这个确切的代码不起作用,但有没有类似的方法让它起作用?

或者任何其他解决方法来获得这种行为而没有太多的hacky"东西?

解决方案

类和实例方法存在于同一个命名空间中,您不能重复使用这样的名称;在这种情况下,id 的最后一个定义将获胜.

类方法将继续在实例上工作,但是不需要创建单独的实例方法;只需使用:

X 类:@类方法定义 ID(cls):返回 cls.__name__

因为方法继续绑定到类:

<预><代码>>>>X级:... @classmethod...定义 ID(cls):...返回 cls.__name__...>>>X.id()'X'>>>X().id()'X'

这是明确记录的:

<块引用>

它可以在类(如C.f())或实例(如C().f())上调用.除了它的类之外,该实例被忽略.

如果确实需要区分绑定到类和实例

如果您需要一种方法根据使用的位置而有所不同;在类上访问时绑定到类,在实例上访问时绑定到实例,您需要创建一个自定义描述符对象.

描述符 API 是 Python 使函数成为绑定为方法,并将 classmethod 对象绑定到类;请参阅描述符操作指南.

您可以通过创建具有 __get__ 方法的对象来为方法提供自己的描述符.这是一个简单的方法,它根据上下文切换方法绑定到的内容,如果 __get__ 的第一个参数是 None,则描述符正在绑定到一个类,否则它被绑定到一个实例:

class class_or_instancemethod(classmethod):def __get__(self, instance, type_):descr_get = super().__get__ if instance is None else self.__func__.__get__返回 descr_get(instance, type_)

这重新使用了classmethod,并且只重新定义了它如何处理绑定,将instance is None 的原始实现委托给标准函数__get__ 否则实现.

请注意,在方法本身中,您可能必须测试它绑定到的内容.isinstance(firstargument, type) 是一个很好的测试:

<预><代码>>>>X级:... @class_or_instancemethod... def foo(self_or_cls):...如果是实例(self_or_cls,类型):...返回 f绑定到类,{self_or_cls}"... 别的:...返回 f"绑定到实例,{self_or_cls"...>>>X.foo()绑定到类,">>>X().foo()'绑定到实例,<__main__.X 对象在 0x10ac7d580>'

另一种实现可以使用两个函数,一个用于绑定到类,另一个用于绑定到实例:

类混合方法:def __init__(self, fclass, finstance=None, doc=None):self.fclass = fclassself.finstance = finstanceself.__doc__ = doc 或 fclass.__doc__# 支持在抽象基类上使用self.__isabstractmethod__ = bool(getattr(fclass, '__isabstractmethod__', False))def classmethod(self, fclass):返回类型(自我)(fclass,self.finstance,无)def instancemethod(self, finstance):返回类型(self)(self.fclass,finstance,self.__doc__)def __get__(self, instance, cls):如果 instance 是 None 或 self.finstance 是 None:# 要么绑定到类,要么没有可用的实例方法返回 self.fclass.__get__(cls, None)返回 self.finstance.__get__(instance, cls)

这是一个带有可选实例方法的类方法.像使用 property 对象一样使用它;用 @.instancemethod 装饰实例方法:

<预><代码>>>>X级:... @hybridmethod...定义栏(CLS):...返回 f"bound to class, {cls}"... @bar.instancemethod...定义栏(自我):...返回 f绑定到实例,{self}"...>>>X.bar()绑定到类,">>>X().bar()'绑定到实例,<__main__.X 对象在 0x10a010f70>'

就我个人而言,我的建议是谨慎使用它;根据上下文改变行为的完全相同的方法可能会令人困惑.但是,这有一些用例,例如 SQLAlchemy 区分 SQL 对象和 SQL 值,其中模型中的列对象会像这样切换行为;查看他们的混合属性 文档.这个实现遵循与我上面的 hybridmethod 类完全相同的模式.

I'd like to do something like this:

class X:

    @classmethod
    def id(cls):
        return cls.__name__

    def id(self):
        return self.__class__.__name__

And now call id() for either the class or an instance of it:

>>> X.id()
'X'
>>> X().id()
'X'

Obviously, this exact code doesn't work, but is there a similar way to make it work?

Or any other workarounds to get such behavior without too much "hacky" stuff?

解决方案

Class and instance methods live in the same namespace and you cannot reuse names like that; the last definition of id will win in that case.

The class method will continue to work on instances however, there is no need to create a separate instance method; just use:

class X:
    @classmethod
    def id(cls):
        return cls.__name__

because the method continues to be bound to the class:

>>> class X:
...     @classmethod
...     def id(cls):
...         return cls.__name__
... 
>>> X.id()
'X'
>>> X().id()
'X'

This is explicitly documented:

It can be called either on the class (such as C.f()) or on an instance (such as C().f()). The instance is ignored except for its class.

If you do need distinguish between binding to the class and an instance

If you need a method to work differently based on where it is being used on; bound to a class when accessed on the class, bound to the instance when accessed on the instance, you'll need to create a custom descriptor object.

The descriptor API is how Python causes functions to be bound as methods, and bind classmethod objects to the class; see the descriptor howto.

You can provide your own descriptor for methods by creating an object that has a __get__ method. Here is a simple one that switches what the method is bound to based on context, if the first argument to __get__ is None, then the descriptor is being bound to a class, otherwise it is being bound to an instance:

class class_or_instancemethod(classmethod):
    def __get__(self, instance, type_):
        descr_get = super().__get__ if instance is None else self.__func__.__get__
        return descr_get(instance, type_)

This re-uses classmethod and only re-defines how it handles binding, delegating the original implementation for instance is None, and to the standard function __get__ implementation otherwise.

Note that in the method itself, you may then have to test, what it is bound to. isinstance(firstargument, type) is a good test for this:

>>> class X:
...     @class_or_instancemethod
...     def foo(self_or_cls):
...         if isinstance(self_or_cls, type):
...             return f"bound to the class, {self_or_cls}"
...         else:
...             return f"bound to the instance, {self_or_cls"
...
>>> X.foo()
"bound to the class, <class '__main__.X'>"
>>> X().foo()
'bound to the instance, <__main__.X object at 0x10ac7d580>'

An alternative implementation could use two functions, one for when bound to a class, the other when bound to an instance:

class hybridmethod:
    def __init__(self, fclass, finstance=None, doc=None):
        self.fclass = fclass
        self.finstance = finstance
        self.__doc__ = doc or fclass.__doc__
        # support use on abstract base classes
        self.__isabstractmethod__ = bool(
            getattr(fclass, '__isabstractmethod__', False)
        )

    def classmethod(self, fclass):
        return type(self)(fclass, self.finstance, None)

    def instancemethod(self, finstance):
        return type(self)(self.fclass, finstance, self.__doc__)

    def __get__(self, instance, cls):
        if instance is None or self.finstance is None:
              # either bound to the class, or no instance method available
            return self.fclass.__get__(cls, None)
        return self.finstance.__get__(instance, cls)

This then is a classmethod with an optional instance method. Use it like you'd use a property object; decorate the instance method with @<name>.instancemethod:

>>> class X:
...     @hybridmethod
...     def bar(cls):
...         return f"bound to the class, {cls}"
...     @bar.instancemethod
...     def bar(self):
...         return f"bound to the instance, {self}"
... 
>>> X.bar()
"bound to the class, <class '__main__.X'>"
>>> X().bar()
'bound to the instance, <__main__.X object at 0x10a010f70>'

Personally, my advice is to be cautious about using this; the exact same method altering behaviour based on the context can be confusing to use. However, there are use-cases for this, such as SQLAlchemy's differentiation between SQL objects and SQL values, where column objects in a model switch behaviour like this; see their Hybrid Attributes documentation. The implementation for this follows the exact same pattern as my hybridmethod class above.

这篇关于classmethod 和 instancemethod 的名称相同的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆