Python:具有相同名称的常规方法和静态方法

时间:2013-12-12 01:27:32

标签: python class static decorator

简介

我有一个Python类,其中包含许多方法。我希望其中一个方法具有静态对应物 - 即具有相同名称的静态方法 - 可以处理更多参数。经过一些搜索,我发现我可以使用@staticmethod装饰器来创建一个静态方法。

问题

为方便起见,我创建了一个简化的测试用例来重现问题:

class myclass:

    @staticmethod
    def foo():
        return 'static method'

    def foo(self):
        return 'public method'

obj = myclass()
print(obj.foo())
print(myclass.foo())

我希望上面的代码会打印以下内容:

public method
static method

但是,代码会打印以下内容:

public method
Traceback (most recent call last):
  File "sandbox.py", line 14, in <module>
    print(myclass.foo())
TypeError: foo() missing 1 required positional argument: 'self'

由此,我只能假设调用myclass.foo()尝试调用其非静态对应物而没有参数(这不起作用,因为非静态方法总是接受参数self)。这种行为让我感到困惑,因为我希望对静态方法的任何调用都能实际调用静态方法。

我在Python 2.7 3.3中测试了这个问题,只是为了收到同样的错误。

问题

为什么会发生这种情况,以及如何修复我的代码以便打印出来:

public method
static method

正如我所料?

5 个答案:

答案 0 :(得分:11)

类似的问题在这里:override methods with same name in python programming

函数按名称查找,因此您只需使用实例方法重新定义foo。在Python中没有重载函数这样的东西。您可以使用单独的名称编写新函数,也可以以可以处理两者逻辑的方式提供参数。

换句话说,您不能拥有相同名称的静态版本和实例版本。如果您查看其vars,则会看到一个foo

In [1]: class Test:
   ...:     @staticmethod
   ...:     def foo():
   ...:         print 'static'
   ...:     def foo(self):
   ...:         print 'instance'
   ...:         

In [2]: t = Test()

In [3]: t.foo()
instance

In [6]: vars(Test)
Out[6]: {'__doc__': None, '__module__': '__main__', 'foo': <function __main__.foo>}

答案 1 :(得分:8)

因为Python中的属性查找是程序员控制的内容,所以这种技术在技术上是可行的。如果你把任何值放在以“pythonic”方式编写代码中(使用python社区的首选约定和习惯用法),很可能是构建问题/设计的错误方法。但是,如果你知道描述符如何允许你控制属性查找,以及函数如何成为绑定函数(提示:函数描述符),你可以完成大致你想要的代码。

对于给定的名称,只有一个对象可以在类上查找,无论您是在类的实例上查找名称,还是在类本身上查找名称。因此,你正在查找的东西必须处理这两种情况,并适当地发送。

(注意:这不是完全 true;如果实例在其属性命名空间中的名称与其类的命名空间中的名称冲突,则实例上的值将在某些实例中获胜但即使在这种情况下,它也不会像你希望的那样成为一种“约束方法”。)

我不建议使用此类技术设计您的程序,但以下内容大致会按您的要求进行。了解其工作原理需要对python作为一种语言有一个相对深刻的理解。

class StaticOrInstanceDescriptor(object):

    def __get__(self, cls, inst):
        if cls is None:
            return self.instance.__get__(self)
        else:
            return self.static

    def __init__(self, static):
        self.static = static

    def instance(self, instance):
        self.instance = instance
        return self


class MyClass(object):

    @StaticOrInstanceDescriptor
    def foo():
        return 'static method'

    @foo.instance
    def foo(self):
        return 'public method'

obj = MyClass()
print(obj.foo())
print(MyClass.foo())

打印出来:

% python /tmp/sandbox.py
static method
public method

答案 2 :(得分:5)

虽然没有严格的可能做,正如所指出的那样,你可以通过在实例化时重新定义方法来“伪造”它,如下所示:

class YourClass(object):

    def __init__(self):
        self.foo = self._instance_foo

    @staticmethod
    def foo()
        print "Static!"

    def _instance_foo(self):
        print "Instance!"

会产生预期的结果:

>>> YourClass.foo()
Static!
>>> your_instance = YourClass()
>>> your_instance.foo()
Instance!

答案 3 :(得分:1)

从谷歌结束这里,以为我会发布我的解决方案到这个“问题”......

class Test():
    def test_method(self=None):
        if self is None:
            print("static bit")
        else:
            print("instance bit")

通过这种方式,您可以使用静态方法或类似实例方法的方法。

答案 4 :(得分:0)

当您尝试调用 MyClass.foo() 时,Python 会抱怨,因为您没有传递所需的 self 参数。 @coderpatros 的回答有正确的想法,我们为 self 提供默认值,因此不再需要它。但是,如果除了 self 之外还有其他参数,这将不起作用。这是一个可以处理几乎所有类型的方法签名的函数:

import inspect
from functools import wraps

def class_overload(cls, methods):
    """ Add classmethod overloads to one or more instance methods """
    for name in methods:
        func = getattr(cls, name)
        # required positional arguments
        pos_args = 1 # start at one, as we assume "self" is positional_only
        kwd_args = [] # [name:str, ...]
        sig = iter(inspect.signature(func).parameters.values())
        next(sig)
        for s in sig:
            if s.default is s.empty:
                if s.kind == s.POSITIONAL_ONLY:
                    pos_args += 1
                    continue
                elif s.kind == s.POSITIONAL_OR_KEYWORD:
                    kwd_args.append(s.name)
                    continue
            break
        @wraps(func)
        def overloaded(*args, **kwargs):
            # most common case: missing positional arg or 1st arg is not a cls instance
            isclass = len(args) < pos_args or not isinstance(args[0], cls)
            # handle ambiguous signatures, func(self, arg:cls, *args, **kwargs);
            # check if missing required positional_or_keyword arg
            if not isclass:
                for i in range(len(args)-pos_args,len(kwd_args)):
                    if kwd_args[i] not in kwargs:
                        isclass = True
                        break
            # class method
            if isclass:
                return func(cls, *args, **kwargs)
            # instance method
            return func(*args, **kwargs)
        setattr(cls, name, overloaded)

class Foo:
    def foo(self, *args, **kwargs):
        isclass = self is Foo
        print("foo {} method called".format(["instance","class"][isclass]))

class_overload(Foo, ["foo"])

Foo.foo() # "foo class method called"
Foo().foo() # "foo instance method called"

您可以使用 isclass bool 来实现类与实例方法的不同逻辑。

class_overload 函数有点笨拙,需要在声明类时检查签名。但是运行时装饰器 (overloaded) 中的实际逻辑应该非常快。

此解决方案不适用于一个签名:具有 Foo 类型的可选的第一个位置参数的方法。在这种情况下,无法仅通过签名来判断我们是在调用静态方法还是实例方法。例如:

def bad_foo(self, other:Foo=None):
    ...
bad_foo(f) # f.bad_foo(None) or Foo.bad_foo(f) ???

请注意,如果您将错误的参数传递给该方法,此解决方案也可能会报告错误的 isclass 值(程序员错误,因此可能对您不重要)。 >

通过执行相反的,我们可以获得一个可能更健壮的解决方案:首先从一个类方法开始,然后创建它的实例方法重载。这与@Dologan 的答案基本相同,但我认为如果您需要在多种方法上执行此操作,我的样板会少一些:

from types import MethodType

def instance_overload(self, methods):
    """ Adds instance overloads for one or more classmethods"""
    for name in methods:
        setattr(self, name, MethodType(getattr(self, name).__func__, self))

class Foo:
    def __init__(self):
        instance_overload(self, ["foo"])

    @classmethod
    def foo(self, *args, **kwargs):
        isclass = self is Foo
        print("foo {} method called:".format(["instance","class"][isclass]))

Foo.foo() # "foo class method called"
Foo().foo() # "foo instance method called"

不计算 class_overloadinstance_overload 的代码,代码同样简洁。通常签名自省被吹捧为做这些事情的“pythonic”方式。但我认为我建议改用 instance_method 解决方案; isclass 对于任何方法签名都是正确的,包括您使用不正确的参数(程序员错误)调用的情况。