yurisich
yurisich

Reputation: 7119

Any method to denote object assignment?

I've been studying magic methods in Python, and have been wondering if there's a way to outline the specific action of:

a = MyClass(*params).method()

versus:

MyClass(*params).method()

In the sense that, perhaps, I may want to return a list that has been split on the '\n' character, versus dumping the raw list into the variable a that keeps the '\n' intact.

Is there a way to ask Python if its next action is about to return a value to a variable, and change action, if that's the case? I was thinking:

class MyClass(object):
    def __init__(params):
        self.end = self.method(*params)

    def __asgn__(self):
        return self.method(*params).split('\n')

    def __str__(self):
        """this is the fallback if __asgn__ is not called"""
        return self.method(*params)

Upvotes: 0

Views: 856

Answers (3)

anjsimmo
anjsimmo

Reputation: 714

Yes.* Python allows inspecting its own stack, which can be used to peek ahead at the next instruction.

#!/usr/bin/env python3
import dis
import inspect
from itertools import dropwhile

class MyClass(object):
    def method(self):
        # inspect the stack to get calling line of code
        frame = inspect.stack()[1].frame
        # disassemble stack frame
        ins = dis.get_instructions(frame.f_code)
        # move to last instruction
        ins = dropwhile(lambda x: x.offset < frame.f_lasti, ins)
        # the last call would have been to this method/function
        current_instruction = ins.__next__()
        assert current_instruction.opname.startswith('CALL_') 
        # peek ahead at the next instruction
        next_instruction = ins.__next__()
        # vary behaviour depending on the next instruction
        if next_instruction.opname.startswith('STORE_'):
            return "returning to assignment"
        elif next_instruction.opname.startswith('CALL_'):
            return "returning to function/method call"
        elif next_instruction.opname == 'POP_TOP':
            print("return value thrown away")
            return "return ignored"
        elif next_instruction.opname == 'PRINT_EXPR':
            return "return to interactive console"
        else:
            return "return to {}".format(next_instruction.opname)

This will result in the following behaviour:

a = MyClass().method()
print(a)
# returning to assignment

def someFunc(x):
    return x.split()

b = someFunc(MyClass().method())
print(b)
# ['returning', 'to', 'function/method', 'call']

MyClass().method()
# return value thrown away       (if called as program)
# return to interactive console  (if run interactively)

* Though as the accepted answer points out, doing so is "very bad". It's also fragile, as it can be affected by bytecode optimisation. See also: Nested dictionary that acts as defaultdict when setting items but not when getting items

Upvotes: 1

Matthew Trevor
Matthew Trevor

Reputation: 14962

There should be no difference between calling MyClass(*params).method() directly and assigning it to a variable. What you may be seeing here is your interpreter automatically printing return results, which is why it appears to be split while the variable value contains EOL markers.

There is no way to override default assignment to a variable. However, by using an object, you can easily provide your own hooks:

class Assigner(object):
    def __init__(self, assignment_callback):
        self.assignment = assignment_callback

    def __setattr__(self, key, value):
        if hasattr(self, 'assignment'):
            value = self.assignment(value)
        super(Assigner, self).__setattr__( key, value )       

def uppercase(value):
    # example function to perform on each attribute assignment
    return value.upper()

Then in your code, rather than assigning to a variable directly you assign to attributes on your object:

>>> my = Assigner(uppercase)
>>> my.a = 'foo'
>>> print my.a
FOO

Upvotes: 1

BrenBarn
BrenBarn

Reputation: 251378

No. You cannot change what happens when you assign to a bare name.

You can change what happens if the assignment target on the left hand side is an attribute or item of an object. You can override a[blah] = ... with __setitem__ and a.blah = ... with __setattr__ (although you can only hook into these on a, not on the object being assigned). But you can't override or in any way influence a = ....

Note that having the right-hand side change based on what is "going to happen" would be even stranger, and very bad. That would mean that

someFunc(MyClass().method())

could be different than

a = MyClass().method()
someFunc(a)

In Python names are just labels attached to objects. Objects don't get to know what labels are attached to them, and that's a good thing. You might assign the result a computation to an intermediate variable just to make subsequent lines more readable, and you don't want that assignment to change the result of that computation.

Upvotes: 4

Related Questions