在实例化时知道上下文/范围

时间:2017-12-14 21:12:58

标签: python-3.x scope

有没有办法知道对象实例化的上下文?到目前为止,我一直在搜索并尝试inspect模块(currentcontext),效果不佳。

例如

class Item:
    pass

class BagOfItems:
    def __init__(self):
        item_1 = Item()
        item_2 = Item()
item_3 = Item()

我想在item_3的实例化中引发异常(因为它在BagOfItems之外),而在item_1item_2中没有这样做。我不知道元类是否可以解决这个问题,因为问题出现在实例化而非声明时。

持有者类(BagOfItems)无法实现检查,因为当Item实例化在其外部发生时,将不会进行检查。

1 个答案:

答案 0 :(得分:1)

当您使用Item()之类的内容实例化对象时,您基本上正在执行type(Item).__call__(),它会在调用序列中的某个位置调用Item.__new__()Item.__init__()。这意味着,如果您浏览导致Item.__init__()的调用序列,您最终会找到不在Itemtype(Item)中的代码。你的要求是堆栈中第一个这样的“上下文”以某种方式属于BagOfItem

在一般情况下,您无法确定包含负责堆栈框架 1 的方法的类。但是,如果您要求只能在类方法中实例化,则不再使用“一般情况”。方法的第一个参数始终是类的实例。因此,我们可以向上移动堆栈跟踪,直到找到一个方法调用,其第一个参数既不是Item的实例,也不是type(Item)的子类。如果框架有参数(即,它不是模块或类体),并且第一个参数是BagOfItems的实例,请继续。否则,引发错误。

请记住,像type(Item).__call__()这样的非显而易见的调用可能根本不会出现在堆栈跟踪中。我只是想为他们做好准备。

支票可以这样写:

import inspect

def check_context(base, restriction):
    it = iter(inspect.stack())
    next(it)  # Skip this function, jump to caller
    for f in it:
        args = inspect.getargvalues(f.frame)
        self = args.locals[args.args[0]] if args.args else None
        # Skip the instantiating calling stack
        if self is not None and isinstance(self, (base, type(base))):
            continue
        if self is None or not isinstance(self, restriction):
            raise ValueError('Attempting to instantiate {} outside of {}'.format(base.__name__, restriction.__name__))
        break

然后,您可以将其嵌入Item.__init__

class Item:
    def __init__(self):
        check_context(Item, BagOfItems)
        print('Made an item')

class BagOfItems:
    def __init__(self):
        self.items = [Item(), Item()]

boi = BagOfItems()
i = Item()

结果将是:

Made an item
Made an item
Traceback (most recent call last):
    ...
ValueError: Attempting to instantiate Item outside of BagOfItems

<强>注意事项

所有这些都阻止您在另一个类的方法之外调用一个类的方法。它无法在staticmethod或classmethod或模块范围内正常工作。如果你有动力,你可以解决这个问题。我已经学到了更多关于内省和堆栈跟踪的知识,所以我会把它称为一天。这应该足以让你开始,或者更好,向你展示为什么你不应该继续这条道路。

此处使用的功能可能是特定于CPython的。我真的不太了解检查能够肯定地告诉我。我确实试图尽可能地远离CPython特有的功能。

<强>参考

<子> 1. Python: How to retrieve class information from a 'frame' object?
2. How to get value of arguments passed to functions on the stack?
3. Check if a function is a method of some object
4. Get class that defined method
5. Python文档:inspect.getargvalues
6. Python文档:inspect.stack