如何禁止在实例上调用类方法?

问题描述 投票:0回答:2

我一直在寻找

peewee
的源代码,特别是
Model
update
函数:https://github.com/coleifer/peewee/blob/a33e8ccbd5b1e49f0a781d38d40eb5e8f344eee5/peewee.py#L4718

我不喜欢这样的事实:如果该语句未与

where
子句正确耦合,则任何更新操作都会影响模型中的每一行,可以从行实例调用此方法。因此,我想找到一些方法来禁止从模型实例调用这个类方法。

一些谷歌搜索让我相信这可能相当困难。来自

delattr
__init__
似乎不起作用。从更新函数运行
isclass(self)
总是返回 True,因为看起来当我们在类方法内部时,我们实际上类而不是实例。

有什么建议吗?

python class-method peewee cpython
2个回答
5
投票

使用元类

您可以像 Schwobaseggl 的答案一样自定义类

__getattribute__
- 但您也可以使用自定义元类。

当我们在Python中提到“元类”时,人们通常会想到重写它的

__new__
方法并在类创建时(与实例创建时相反)做复杂的事情。然而,如果你把所有特殊的 dunder (
__these__ __methods__
) 放在一边,元克拉只是一个类的类 - 并且它的所有方法将从类本身可见,但从类的实例中不可见。这意味着,当一个“dir”是实例时,它们不会显示,但当一个“dir”是类时,它们会显示 - 并且不能通过实例直接检索。 (当然,尽管人们总是可以做到
self.__class__.method

此外,尽管元类因复杂性而名声不佳,但重写

__getattribute__
本身可能会存在一些 陷阱

在这种特定情况下,您想要保护的类已经使用了元类 - 但这种特殊用途与“普通”元类用途不同,可以像普通类层次结构一样自由组合:

class ClsMethods(BaseModel):  
     # inherit from `type` if there is no metaclass already
     
     # now, just leave __new__, __init__, __prepare__ , alone
     # and write your class methods as ordinary methods:
     def update(cls, *args, **kw):
          ...
     
     def fetch_rows_from(self, ...):
          ...

class Model(with_metaclass(ClsMethods)):
      # This really socks. Do you really still need Py2 support? :-) 

      ...

(这应该是显而易见的,但你认为你不需要声明 元类中的方法作为类方法:所有这些方法都是 元类实例的类方法,即类)

控制台上的快速演示:

In [37]: class M(type):
    ...:     def secret(cls): print("At class only")
    ...:     

In [38]: class A(metaclass=M):
    ...:     pass
    ...: 

In [39]: A.secret()
At class only

In [40]: A().secret()
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-40-06355f714f97> in <module>()
----> 1 A().secret()

AttributeError: 'A' object has no attribute 'secret'

创建专门的装饰器

Python 的

classmethod
装饰器,甚至普通的实例方法,实际上都使用了描述符协议:这些方法本身就是对象,有一个专门的
__get__
方法,在从实例或类中检索它们并修改时使用相应地可调用。

因此,我们所要做的就是创建一个

classmethod
的等价物,它将禁止从实例调用:


from functools import partial

class strict_classmethod:
    def __init__(self, func):
         self.func = func
    def __get__(self, instance, owner):
         if instance is not None:
              raise TypeError("This method cannot be called from instances")
         return partial(self.func, owner)

class A:
   @strict_classmethod
   def secret(cls, ...):
       ...

这是一个简单的实现,可以工作,但是修饰的方法仍然会出现在类的自省中,并且

dir
- 但是,它足以避免错误调用。


0
投票

您可以覆盖

__getattribute__
,它被称为 every 属性访问和 only 实例,并检查为类方法返回的内容。或者,您可以拒绝某个
item
:

import inspect

class A(object):  # aka Model
    @classmethod
    def f(cls, *args, **kwargs):
        print(args, kwargs)

class B(A):  # your Model subclass
    def __getattribute__(self, item):
        # if item == 'update':
        #     raise TypeError
        obj = super(B, self).__getattribute__(item)
        # classmethod check
        if inspect.ismethod(obj) and obj.__self__ is B:
            raise TypeError
        return obj

> a = A()
> b = B()

> A.f(5, p=7)
(5,) {'p': 7}

> B.f(5, p=7)
(5,) {'p': 7}

> a.f(5, p=7)
(5,) {'p': 7}

> b.f(5, p=7)
# TypeError

类方法检查取自Martijn Pietersanswer

© www.soinside.com 2019 - 2024. All rights reserved.