Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
4.3k views
in Technique[技术] by (71.8m points)

python - Can I patch an arbitrary object instance to throw when used?

Motivation

I have singleton objects I am replacing with new instances. I want to be sure that nobody uses stale references to invalidated objects. I have tried a few solutions (listed below) that haven't worked and now I'm wondering if this is a fools errand.

My desired user experience is something like

singleton = OriginalClass()
user_variable = singleton

...

make_radioactive(singleton) 
singleton = NewSingletonClass() 
# User code should also update here but what if they dont?

...

# If they didn't update correctly I want this to break
user_variable.do_something()
Exception: Object has been invalidated

Requirements:

  1. No added overhead to object before it is destroyed/deprecated
  2. Any attribute access or method calls on the old instance throws an exception of my choosing
  3. The new instance of singleton may be of the same or different type

Approaches considered and rejected

1: Override getattribute at runtime

def make_radioactive(some_object):
    def throw(self, name):
        raise Exception("Using an invalidated instance")
    some_object.__getattribute__ = throw

I was hopeful about this but it does not work because of special method lookup rules. __getattribute__ is accessed through the class not the instance and monkeypatching it on the class would invalidate all instances not just one of them.

2: Erase __dict__

def make_radioactive(some_object):
    some_object.__dict__ = {}

This is close to what I want but the exception users would see is

AttributeError: 'ClassType' object has no attribute 'attribute_name'

instead of

MyCustomExceptionType: You used an invalidated instance

And it only works on variables not methods

3: Require common base class and override __getattribute__

class Destroyable(abc.ABC):
    def __init__(self):
        self.__radioactive = False
    def make_radioactive(self):
        self.__radioactive = True
    def __getattribute__(self, name):
        if object.__getattribute__(self, '_radioactive'):
            raise MyCustomException("Accessed an inavlidated object")
        return object.__getattribute__(self, name)

This gives me a custom exception message but requires inheriting from a base class and adds overhead to normal object operations before make_radioactive is called


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

It's possible to change the __class__ attribute of the object you want to invalidate. Change it to a custom class that raises an error in the __getattribute__ method. In my tests similar to the one you describe, it throws the error as you expected when trying to access attributes or methods.

If the objects type has __slots__, then changing class to a trivial class will throw TypeError: __class__ assignment: 'X' object layout differs from 'Y'. To avoid that we can dynamically create an invalid subclass of the passed objects type.

invalid_classes = {}
def make_radioactive(some_object):
    global invalid_classes
    c = type(some_object)

    if c not in invalid_classes:
        # To avoid "TypeError: __class__ assignment: 'X' object layout differs from 'Y'"
        # We create a subclass of the objects class
        class A(c):
            # override __getattribute__ to always throw an error
            def __getattribute__(self, name):
                raise MyCustomException("Accessed an inavlidated object")
        # Remember it so we don't need to recreate on every call with same type of object
        invalid_classes[c] = A

    # Assign new class to the object
    some_object.__class__ = invalid_classes[c]

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...