I created a discussion topic located here https://discuss.python.org/t/a-proposal-and-implementation-to-add-assignment-and-load-overloading/1905

On Tue, Jun 25, 2019 at 6:41 PM nate lust <natelust@linux.com> wrote:
I am happy to move this discussion to where ever is appropriate. I won't get to it in the next few hours (bed time for my kid) so if you would like feel free to move discussion there, and I guess I can watch this email thread for if you do. Otherwise I will do it when I am free.
Nate

On Tue, Jun 25, 2019, 6:36 PM Juancarlo Añez <apalala@gmail.com> wrote:
Nate,

I find this mightily interesting! I think it's worth discussing at length.

Is there any chance you'll want to move the discussion to the richer context here? https://discuss.python.org/c/ideas

Regards,

On Tue, Jun 25, 2019 at 5:00 PM nate lust <natelust@linux.com> wrote:
This message is related to two previous threads, but was a sufficiently evolved to warrant a new topic.

I am proposing that two new magic methods be added to python that will control assignment and loading of class
instances. This means that if an instance is bound to a variable name, any attempts to rebind that name will
result in a call to the __setself__ (name negotiable) of the instance already bound to that name.  Likewise
when a class instance bound to a name is loaded by the interpreter, if present, the __getself__ method of that
instance will be called and its result will be returned instead. I have been internally calling these cloaking
variables as they "cloak" the underlying instance, parallelling the idea of shadowing. Feel free to suggest
better names.                                                                                                          

On first read, that may be surprising, but it extends a behavior pattern that already exists for things like
properties (and generically descriptors) to object instances themselves. Similar caveats and behaviors will
apply here as well.

A working implementation built against python 3.7 can be found here:
https://github.com/natelust/cpython/tree/cloakingVars. This is not pull ready quality code, but the diffs may
be interesting to read.

An example for what is possible for this new behavior are instance level properties as seen in the demo at the
end of this message.

These changes have minimal impact on the runtime of existing code, and require no modifications to existing
syntax other than the use of the names __setself__ and __getself__.

A more detailed write-up with more examples can be found at
https://github.com/natelust/CloakingVarWriteup/blob/master/writeup.md, with the example executable demo here: https://github.com/natelust/CloakingVarWriteup/blob/master/examples.py

The demos include:
* Variables which keep track of their assignment history, with ability to rollback (possibly useful with try
  except blocks)
* Variables which write out their value to disk when assigned to
* An implementation of context variables using only this new framework (does not implement tokens, but could
  be added)
* const variables that can be used to protect module level 'constants'
* Instance properties (reproduced below) that allow dynamically adding properties
* An implementation of templated expression, to defer the addition of many arrays to a single for loop,
  saving possibly expensive python iterations.

I am sure the community can come up with many more interesting ideas.

class InstanceProperty:                                                                                                
    def __init__(self, wrapped, getter, setter=None):
        self.wrapped = wrapped
        self.getter = getter
        self.setter = setter
   
    def __getself__(self):
        return self.getter(self.wrapped)
   
    def __setself__(self, value):
        if self.setter:
            return self.setter(self.wrapped, value)


class MachineState:
    def __init__(self):
        self._fields = {}
   
    def add_input(self, name, start):
        def getter(slf):
            return slf._fields[name]
   
        def setter(slf, value):
            '''
            the state of a machine part can only be above zero or below
            100
            '''
            if value < 0:
                value = 0
            if value > 100:
                value = 100
            slf._fields[name] = value
        setter(self, start)
        inst_prop = InstanceProperty(self, getter, setter)  # noqa: F841
        # Need to directly assign the instance property, or decloak it.
        setattr(self, name, getcloaked('inst_prop'))


machine = MachineState()

for letter, start in zip(['a', 'b', 'c'], [-1, 0, 1]):
    machine.add_input(letter, start)

print(f"machine.a is {machine.a}")
print(f"machine.b is {machine.b}")
print(f"machine.c is {machine.c}")

# Assign a value that is too high
machine.c = 200

print(f"machine.c is {machine.c}")
# Omited from this proposal but present in the linked documentation are
# tools for getting the underlying variables, and or rebinding them.

On Fri, Jun 21, 2019 at 9:34 PM nate lust <natelust@linux.com> wrote:
It probably doesn't, this was just something I typed up on the fly, so is unlikely the end result would be what you see above if it was actually implemented.

The only way around that that I can think of now would be if there was two functions, an impl_dictget that actually did the lookup that type could use (and possibly getattr and the like) which would be called in the normal dict get which would just return if the type did not define __getself__ and would call it and return the result if it did.

This is not at all dissimilar to how dict setting works now

On Fri, Jun 21, 2019, 9:27 PM Chris Angelico <rosuav@gmail.com> wrote:
On Sat, Jun 22, 2019 at 11:19 AM nate lust <natelust@linux.com> wrote:
> Typing this out though does make me think of an interesting idea. If there was something like __getself__ in addition to __setself__, you could implement things like MyInt. __getself__ would look something like:
>
> class MyInt:
>     def __init__(self, value):
>         self.value = value
>     def __getself__(self):
>         return self.value
>     def __setself__(self, value):
>         raise ValueError("Cant set MyInt")
> x = MyInt(2)
> print(x) -> 2
> type(x) -> MyInt
>
> Now I have not really thought through how this would work, if it could work...

How does print know to call getself, but type know not to?

ChrisA
_______________________________________________
Python-ideas mailing list -- python-ideas@python.org
To unsubscribe send an email to python-ideas-leave@python.org
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/3734V62OHMJN3736PKWQ7IZ533TPM23J/
Code of Conduct: http://python.org/psf/codeofconduct/


--
Nate Lust, PhD.
Astrophysics Dept.
Princeton University
_______________________________________________
Python-ideas mailing list -- python-ideas@python.org
To unsubscribe send an email to python-ideas-leave@python.org
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/TMU3MJCDVNAHMJQAJUBIHRJXXLYMWSRH/
Code of Conduct: http://python.org/psf/codeofconduct/


--
Juancarlo Añez


--
Nate Lust, PhD.
Astrophysics Dept.
Princeton University