首页 \ 问答 \ 为什么Python的@staticmethods与装饰类交互如此糟糕?(Why do Python's @staticmethods interact so poorly with decorated classes?)

为什么Python的@staticmethods与装饰类交互如此糟糕?(Why do Python's @staticmethods interact so poorly with decorated classes?)

最近,StackOverflow社区帮助我开发了一个相当简洁的@memoize装饰器,它不仅能够以通用的方式修饰函数,还能修饰方法和类,也就是说,不需要预先知道它将装饰什么类型的东西。

我碰到的一个问题是,如果你用@memoize修饰了一个类,然后试图用@memoize来修饰它的一个方法, @staticmethod这将无法按预期工作,也就是说,你将无法调用ClassName.thestaticmethod() 。 我想到的原始解决方案如下所示:

def memoize(obj):
    """General-purpose cache for classes, methods, and functions."""
    cache = obj.cache = {}

    def memoizer(*args, **kwargs):
        """Do cache lookups and populate the cache in the case of misses."""
        key = args[0] if len(args) is 1 else args
        if key not in cache:
            cache[key] = obj(*args, **kwargs)
        return cache[key]

    # Make the memoizer func masquerade as the object we are memoizing.
    # This makes class attributes and static methods behave as expected.
    for k, v in obj.__dict__.items():
        memoizer.__dict__[k] = v.__func__ if type(v) is staticmethod else v
    return memoizer

但后来我了解了functools.wraps ,它旨在使装饰器函数以更简洁更完整的方式伪装成装饰函数,而且我确实采用了这种方式:

def memoize(obj):
    """General-purpose cache for class instantiations, methods, and functions."""
    cache = obj.cache = {}

    @functools.wraps(obj)
    def memoizer(*args, **kwargs):
        """Do cache lookups and populate the cache in the case of misses."""
        key = args[0] if len(args) is 1 else args
        if key not in cache:
            cache[key] = obj(*args, **kwargs)
        return cache[key]
    return memoizer

虽然这看起来很不错,但functools.wraps绝对不支持staticmethodclassmethod 。 例如,如果你尝试了这样的事情:

@memoize
class Flub:
    def __init__(self, foo):
        """It is an error to have more than one instance per foo."""
        self.foo = foo

    @staticmethod
    def do_for_all():
        """Have some effect on all instances of Flub."""
        for flub in Flub.cache.values():
            print flub.foo
Flub('alpha') is Flub('alpha')  #=> True
Flub('beta') is Flub('beta')    #=> True
Flub.do_for_all()               #=> 'alpha'
                                #   'beta'

这将与我列出的@memoize的第一个实现一起工作,但会引发TypeError: 'staticmethod' object is not callable与第二个一起TypeError: 'staticmethod' object is not callable

我真的很想用functools.wraps解决这个问题,而不必带回__dict__ ugliness,所以我实际上用纯Python重新实现了我自己的staticmethod ,看起来像这样:

class staticmethod(object):
    """Make @staticmethods play nice with @memoize."""

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

    def __call__(self, *args, **kwargs):
        """Provide the expected behavior inside memoized classes."""
        return self.func(*args, **kwargs)

    def __get__(self, obj, objtype=None):
        """Re-implement the standard behavior for non-memoized classes."""
        return self.func

据我所知,这与我在上面列出的第二个@memoize实现完美@memoize

所以,我的问题是:为什么标准内置staticmethod不能正确运行,以及/或者为什么functools.wraps没有预测到这种情况并为我解决?

这是Python中的错误吗? 或者在functools.wraps

重写内置staticmethod哪些注意事项? 就像我说的那样,现在看起来工作得很好,但是恐怕我的实现和内置实现之间可能存在一些隐藏的不兼容性,这可能会在以后爆炸。

谢谢。

编辑澄清:在我的应用程序中,我有一个功能,执行昂贵的查找,并频繁调用,所以我记忆它。 这非常简单。 除此之外,我有许多表示文件的类,并且在文件系统中有多个表示同一文件的实例通常会导致状态不一致,因此每个文件名仅强制一个实例很重要。 将@memoize装饰器调整为此目的并且仍然保留其作为传统记忆器的功能本质上是微不足道的。

@memoize三种不同用途的真实世界示例如下:


Recently, the StackOverflow community helped me develop a fairly concise @memoize decorator that is able to decorate not only functions but also methods and classes in a general way, ie, without having any foreknowledge of what type of thing it will be decorating.

One of the problems that I ran into is that if you decorated a class with @memoize, and then tried to decorate one of it's methods with @staticmethod, this would not work as expected, ie, you would not be able to call ClassName.thestaticmethod() at all. The original solution that I came up with looked like this:

def memoize(obj):
    """General-purpose cache for classes, methods, and functions."""
    cache = obj.cache = {}

    def memoizer(*args, **kwargs):
        """Do cache lookups and populate the cache in the case of misses."""
        key = args[0] if len(args) is 1 else args
        if key not in cache:
            cache[key] = obj(*args, **kwargs)
        return cache[key]

    # Make the memoizer func masquerade as the object we are memoizing.
    # This makes class attributes and static methods behave as expected.
    for k, v in obj.__dict__.items():
        memoizer.__dict__[k] = v.__func__ if type(v) is staticmethod else v
    return memoizer

But then I learned about functools.wraps, which is intended to make the decorator function masquerade as the decorated function in a much cleaner and more complete way, and indeed I adopted it like this:

def memoize(obj):
    """General-purpose cache for class instantiations, methods, and functions."""
    cache = obj.cache = {}

    @functools.wraps(obj)
    def memoizer(*args, **kwargs):
        """Do cache lookups and populate the cache in the case of misses."""
        key = args[0] if len(args) is 1 else args
        if key not in cache:
            cache[key] = obj(*args, **kwargs)
        return cache[key]
    return memoizer

Although this looks very nice, functools.wraps provides absolutely no support for either staticmethods or classmethods. Eg, if you tried something like this:

@memoize
class Flub:
    def __init__(self, foo):
        """It is an error to have more than one instance per foo."""
        self.foo = foo

    @staticmethod
    def do_for_all():
        """Have some effect on all instances of Flub."""
        for flub in Flub.cache.values():
            print flub.foo
Flub('alpha') is Flub('alpha')  #=> True
Flub('beta') is Flub('beta')    #=> True
Flub.do_for_all()               #=> 'alpha'
                                #   'beta'

This would work with the first implementation of @memoize I listed, but would raise TypeError: 'staticmethod' object is not callable with the second.

I really, really wanted to solve this just using functools.wraps without having to bring back that __dict__ ugliness, so I actually reimplemented my own staticmethod in pure Python, which looked like this:

class staticmethod(object):
    """Make @staticmethods play nice with @memoize."""

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

    def __call__(self, *args, **kwargs):
        """Provide the expected behavior inside memoized classes."""
        return self.func(*args, **kwargs)

    def __get__(self, obj, objtype=None):
        """Re-implement the standard behavior for non-memoized classes."""
        return self.func

And this, as far as I can tell, works perfectly alongside the second @memoize implementation that I list above.

So, my question is: Why doesn't the standard builtin staticmethod behave properly on it's own, and/or why doesn't functools.wraps anticipate this situation and solve it for me?

Is this a bug in Python? Or in functools.wraps?

What are the caveats of overriding the builtin staticmethod? Like I say, it seems to be working fine now, but I'm afraid that there might be some hidden incompatibility between my implementation and the builtin implementation, which might blow up later on.

Thanks.

Edit to clarify: In my application, I have a function that does an expensive lookup, and gets called frequently, so I memoized it. That is quite straightforward. In addition to that, I have a number of classes that represent files, and having multiple instances representing the same file in the filesystem will generally result in inconsistent state, so it's important to enforce only one instance per filename. It's essentially trivial to adapt the @memoize decorator to this purpose and still retain it's functionality as a traditional memoizer.

Real world examples of the three different uses of @memoize are here:


原文:https://stackoverflow.com/questions/11174362
更新时间:2022-08-19 10:08

相关文章

更多

最新问答

更多
  • 您如何使用git diff文件,并将其应用于同一存储库的副本的本地分支?(How do you take a git diff file, and apply it to a local branch that is a copy of the same repository?)
  • 将长浮点值剪切为2个小数点并复制到字符数组(Cut Long Float Value to 2 decimal points and copy to Character Array)
  • OctoberCMS侧边栏不呈现(OctoberCMS Sidebar not rendering)
  • 页面加载后对象是否有资格进行垃圾回收?(Are objects eligible for garbage collection after the page loads?)
  • codeigniter中的语言不能按预期工作(language in codeigniter doesn' t work as expected)
  • 在计算机拍照在哪里进入
  • 使用cin.get()从c ++中的输入流中丢弃不需要的字符(Using cin.get() to discard unwanted characters from the input stream in c++)
  • No for循环将在for循环中运行。(No for loop will run inside for loop. Testing for primes)
  • 单页应用程序:页面重新加载(Single Page Application: page reload)
  • 在循环中选择具有相似模式的列名称(Selecting Column Name With Similar Pattern in a Loop)
  • System.StackOverflow错误(System.StackOverflow error)
  • KnockoutJS未在嵌套模板上应用beforeRemove和afterAdd(KnockoutJS not applying beforeRemove and afterAdd on nested templates)
  • 散列包括方法和/或嵌套属性(Hash include methods and/or nested attributes)
  • android - 如何避免使用Samsung RFS文件系统延迟/冻结?(android - how to avoid lag/freezes with Samsung RFS filesystem?)
  • TensorFlow:基于索引列表创建新张量(TensorFlow: Create a new tensor based on list of indices)
  • 企业安全培训的各项内容
  • 错误:RPC失败;(error: RPC failed; curl transfer closed with outstanding read data remaining)
  • C#类名中允许哪些字符?(What characters are allowed in C# class name?)
  • NumPy:将int64值存储在np.array中并使用dtype float64并将其转换回整数是否安全?(NumPy: Is it safe to store an int64 value in an np.array with dtype float64 and later convert it back to integer?)
  • 注销后如何隐藏导航portlet?(How to hide navigation portlet after logout?)
  • 将多个行和可变行移动到列(moving multiple and variable rows to columns)
  • 提交表单时忽略基础href,而不使用Javascript(ignore base href when submitting form, without using Javascript)
  • 对setOnInfoWindowClickListener的意图(Intent on setOnInfoWindowClickListener)
  • Angular $资源不会改变方法(Angular $resource doesn't change method)
  • 在Angular 5中不是一个函数(is not a function in Angular 5)
  • 如何配置Composite C1以将.m和桌面作为同一站点提供服务(How to configure Composite C1 to serve .m and desktop as the same site)
  • 不适用:悬停在悬停时:在元素之前[复制](Don't apply :hover when hovering on :before element [duplicate])
  • 常见的python rpc和cli接口(Common python rpc and cli interface)
  • Mysql DB单个字段匹配多个其他字段(Mysql DB single field matching to multiple other fields)
  • 产品页面上的Magento Up出售对齐问题(Magento Up sell alignment issue on the products page)