【问题标题】:A class method which behaves differently when called as an instance method?作为实例方法调用时行为不同的类方法?
【发布时间】:2010-10-26 01:16:28
【问题描述】:

我想知道是否有可能创建一个在作为类方法调用时与作为实例方法调用时行为不同的方法。

例如,作为一个技能改进项目,我正在编写一个Matrix 类(是的,我知道已经有非常好的矩阵类)。我为它创建了一个名为identity 的类方法,它返回一个指定大小的单位矩阵。

现在,当在Matrix实例 上调用时,不需要指定大小似乎是合乎逻辑的;它应该返回一个与调用它的Matrix 大小相同的单位矩阵。

换句话说,我想定义一个方法,它可以确定它是否是通过实例调用的,如果是,则访问该实例的属性。不幸的是,即使在浏览了文档和一些谷歌搜索之后,我也没有发现任何表明这是可能的。有人知道不同吗?

编辑:

哇!显然,我还不太习惯一流的功能。这就是我最终得到的结果——感谢 Unknown 提供了密钥!

class Foo(object):
    def __init__(self, bar):
        self.baz = bar
        self.bar = MethodType(lambda self: self.__class__.bar(self.baz), self, self.__class__)

    @classmethod
    def bar(cls, baz):
        return 5 * baz

Foo.bar(3) # returns 15

foo = Foo(7)
foo.bar() # returns 35

编辑 2:

简单说明一下 - 这种技术(以及下面介绍的大多数技术)不适用于定义 __slots__ 的类,因为您无法重新分配该方法。

【问题讨论】:

  • 该问题要求提供一个可以检测其调用方式的可调用对象。 (这应该可以使用检查模块来获取调用框架。)提出的“解决方案”是创建第二个可调用对象,一个绑定方法,该方法绑定到与第一个可调用对象同名的每个实例。这可以解决如何让类和“实例方法”同名的问题。但这与拥有一个可以充当两者之一的单个可调用对象不同。
  • @TerryJanReedy 是否可以在运行时检查调用帧?
  • 是的,这就是我所说的“使用检查模块获取调用帧”的意思。为此有一个或多个函数。

标签: python class methods


【解决方案1】:

我认为更大的问题是您在类 'Foo' 上重载了名称 'bar',这是 python 不允许的。 “bar”的第二个定义破坏了“bar”的第一个定义。

尝试为您的类方法和实例方法考虑唯一名称。即

@classmethod
def create(cls, baz):
   ...

def rubber_stamp(self):
   ...

【讨论】:

  • +1:而且——当然——变体行为的固有混淆因素,即使“密切相关”。
  • 您的建议是修复 OP 已经确定为他需要帮助的大问题的“无效概念代码”?
  • IMO,不工作的概念代码是个坏主意——解决方法是重新考虑 API。没有“修复”。
【解决方案2】:

[已编辑:使用属性作为更直接的答案;请参阅 John Fouhy 的有用评论]

你可以使用描述符来做你想做的事:

class cls_or_inst_method(object):
    def __init__(self, class_method, instance_method):
        self.class_method = class_method
        self.instance_method = instance_method

    def __get__(self, obj, objtype):
        if obj is None:
            return self.class_method
        else:
            return lambda: self.instance_method(obj)

def my_class_method(baz):
    return baz + 1

def my_instance_method(self):
    return self.baz * 2

class Foo(object):
    baz = 10
    bar = cls_or_inst_method(my_class_method, my_instance_method)

使用上面的:

>>> print Foo.bar(5)
6
>>> my_foo = Foo()
>>> print my_foo.bar()
20

【讨论】:

  • 作为一个解决方案,我认为实例方法应该引用 self.baz 并且不带任何参数(self除外)..
【解决方案3】:

您可以在 init 中使用短 lambda 函数重新分配您的身份方法:

class Matrix(object):
    def __init__(self):
        self.identity = lambda s=self:s.__class__.identity(s)

        #...whatever initialization code you have...
        self.size = 10        

    @classmethod
    def identity(self, other):
        #...always do you matrix calculations on 'other', not 'self'...
        return other.size


m = Matrix()
print m.identity()
print Matrix.identity(m)

如果您不熟悉 lambda,它会创建一个匿名函数。它很少需要,但它可以使您的代码更简洁。上面的 lambda 行可以重写:

    def identity(self):
        self.__class__.indentity(self)
    self.identity = identity

【讨论】:

    【解决方案4】:

    @Unknown 你的和这个有什么区别:

    class Foo(object):
    
        def _bar(self, baz):
            print "_bar, baz:", baz
    
        def __init__(self, bar):
            self.bar = self._bar
            self.baz = bar
    
        @classmethod
        def bar(cls, baz):
            print "bar, baz:", baz
    
    In [1]: import foo
    
    In [2]: f = foo.Foo(42)
    
    In [3]: f.bar(1)
    _bar, baz: 1
    
    In [4]: foo.Foo.bar(1)
    bar, baz: 1
    
    In [5]: f.__class__.bar(1)
    bar, baz: 1
    

    【讨论】:

    • 不同的是,你可以绑定任何你想要的函数作为方法。我只是利用这个机会炫耀这个把戏。而且没有悬空的 f._bar()
    • @Unknown: 如果“悬空”f._bar() 是一个问题,那么只需使用 lambda 或 __init__() 内的嵌套函数定义它。这里的代码很直观地知道它是如何工作的,而其他解决方案涉及晦涩的 Python voodoo,更不用说模块可见的顶级函数 methodize()bar()
    【解决方案5】:

    有用的 Python hack 是我的强项。

    from types import *
    
    class Foo(object):
        def __init__(self):
            self.bar = methodize(bar, self)
            self.baz = 999
    
        @classmethod
        def bar(cls, baz):
            return 2 * baz
    
    
    def methodize(func, instance):
        return MethodType(func, instance, instance.__class__)
    
    def bar(self):
        return 4*self.baz
    
    
    >>> Foo.bar(5)
    10
    >>> a=Foo()
    >>> a.bar()
    3996
    

    【讨论】:

    • 对代码示例所做的努力,尤其是对于“可疑有用的 Python hacks 是我的强项”这一短语表示敬意和点赞:-)
    • @Jarret,它也不仅仅是表演的引述。查看我的其他内容,例如:stackoverflow.com/questions/813882/…
    • 好的,很好。现在你怎么腌制这个?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-10-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多