Link: Bidirectional Aliasing in Python
Hi, Python community I would like to discuss with you about the idea of "bidirectional Aliasing". I am not good at English wording. I will try my best to communicate the idea Link is a language feature that allows multiple variable names to always refer to the same underlying object define in a namespace. For now, if the variable a link with b. I will denote as a >< b or link('a', 'b') a = 2 a >< alpha # link 'a' with 'alpha' b = 3 print(b**alpha) # print 9 alpha = 3 print(b**a) # print 27 This also able to apply to function name, class property and method. More in detail on this link https://dev.to/circleoncircles/python-ideas-link-bidirectional-aliasing-in-p... Look forward to hearing from you Nutchanon
On Mon, 23 Sep 2019 at 10:14, Nutchanon Ninyawee <me@nutchanon.org> wrote:
Hi, Python community I would like to discuss with you about the idea of "bidirectional Aliasing". I am not good at English wording. I will try my best to communicate the idea
I think the idea is clear enough, but what isn't clear is the motivation. There's also a lot of other things to consider - the ergonomics of working with it, corner cases, compiler changes etc, but lets start with the core: Why would we want to do this; who does it benefit and why does it benefit them ? Thank you, Rob
On Mon, Sep 23, 2019 at 12:13:13PM +1200, Robert Collins wrote:
On Mon, 23 Sep 2019 at 10:14, Nutchanon Ninyawee <me@nutchanon.org> wrote:
Hi, Python community I would like to discuss with you about the idea of "bidirectional Aliasing". I am not good at English wording. I will try my best to communicate the idea
I think the idea is clear enough, but what isn't clear is the motivation. There's also a lot of other things to consider - the ergonomics of working with it, corner cases, compiler changes etc, but lets start with the core: Why would we want to do this; who does it benefit and why does it benefit them ?
Aside from implementing the idea, all of those questions are discussed at the link Nutchanon gave: https://dev.to/circleoncircles/python-ideas-link-bidirectional-aliasing-in-p... -- Steven
On Sun, Sep 22, 2019 at 09:08:18AM -0000, Nutchanon Ninyawee wrote:
Link is a language feature that allows multiple variable names to always refer to the same underlying object define in a namespace. For now, if the variable a link with b. I will denote as a >< b or link('a', 'b')
I don't know of any standard term for this, but I would call it aliasing. It is not quite the same as the version described here: https://en.wikipedia.org/wiki/Aliasing_%28computing%29 In Python terms, we would say that an alias makes two names refer to each other, rather than two names referring to a single object. In current Python: spam = [42, None] eggs = spam # Assignment (name binding) binds both names to the same object. assert spam is eggs # Mutations applied through one name affects the other name, # since they are the same object. spam.append("hello") assert eggs[-1] == "hello" # But re-binding of one name doesn't touch the other. spam = "something else" assert eggs == [42, None, "hello"] With a "name alias", the first two assertions would also apply, but the third would be different: spam = [42, None] eggs >< spam assert spam is eggs spam.append("hello") assert eggs[-1] == "hello" # But rebinding affects both names. spam = "something else" assert eggs == "something else" eggs = "it works in both directions" assert spam == "it works in both directions" Likewise unbinding: ``del spam`` would delete eggs as well, and vice versa. Is that what you had in mind?
More in detail on this link https://dev.to/circleoncircles/python-ideas-link-bidirectional-aliasing-in-p...
That's a nice explanation, although I disagree with some of your claimed benefits. BTW, your home page https://nutchanon.org/ is currently reporting a 404. -- Steven
"rebinding" is the accurate term. Yes, `del spam` would delete eggs as well, and vice versa. Thanks for reporting my home page 404.
On Sep 22, 2019, at 18:46, Nutchanon Ninyawee <me@nutchanon.org> wrote:
Yes, `del spam` would delete eggs as well, and vice versa.
Would they remain linked, so if I bound something else to eggs it would also be bound to spam? More importantly, what about the answers to the other dozen or so questions? Just answering one of them and ignoring the rest doesn’t really help much to clarify the proposal.
Andrew Barnert wrote:
Yes, del spam would delete eggs as well, and vice versa. Would they remain linked, so if I bound something else to eggs it would also be bound to spam? More importantly, what about the answers to the other dozen or so questions? Just answering one of them and ignoring the rest doesn’t really help much to clarify the
On Sep 22, 2019, at 18:46, Nutchanon Ninyawee me@nutchanon.org wrote: proposal.
Already, I answered them below. Q: Would they remain linked, so if I bound something else to eggs it would also be bound to spam? A: Yes, until it is `unlink('egg', 'spam')`
I had an idea for an abstract representation of the feature Nutchanon described. Let's call it aliasing and let's define it intuitively: a = 1 b alias a print(b) -> 1 b = 2 print(a) -> 2 The abstract representation would define a new construct _aliased_identifier_ (aid in short), which will behave exactly as the "normal" identifier behaves now. Now the Python identifier is basically a restricted string literal. The new aliased identifier will be a set of current identifiers. I am not saying it needs to be implemented as a Python set (it could be namespace, tuple, list, etc.), but feature-wise it will be a set - without ordering and with only unique members. Using Python "repr() style", one can write for example an aid with only one identifier called 'a' as aid('a'). Whenever the code uses an identifier, it will be using the aid which contains the corresponding identifier and technically, writing: a = 1, will mean under the hood aid('a') = 1. The important property of the aid is that it behaves the same as an identifier now, so it can bind to only one object. The other important property is that the regular identifier can be present in only one aid, because the identifier has to identify this aid in a unique way. Therefore it is not possible to have aid('a', 'b') and aid('b', 'c'), because 'b' does not identify a unique aid. Now executing: b alias a, will translate to: 'b' alias aid('a') => aid('a').add('b') = aid('a', 'b'). while preserving the binding aid('a') had before (if it had any). after that, whenever the code uses 'a' identifier, or 'b' identifier it will mean the same aid('a', 'b'). aids should behave as the identifiers today, except for the aliasing and unaliasing commands (unaliasing done by 'del' in the example below). For example, what happens if I do: a = 1 => aid('a') = 1 b alias a => aid('a', 'b') refers to the original object 1 c = 2 => aid('c') = 2 d alias c => aid('c', 'd') refers to the original object 2 Now what to do when: b alias c => aid('c', 'd'),add('b') = aid('b', 'c', 'd') and refer to the object aid('c', 'd') referred before, i.e. 2 but at the same time 'b' has to be removed from aid('a', 'b'), so we need to define: del b => aid('a', 'b').remove('b') => aid('a'), 'b' does not identify anything anymore, aid('a') still holds the reference. or if 'b' represents only simple aid('b') then del b => aid('b').remove('b') => 'b' does not exist anymore Can I alias an identifier to the item of the list, dict, etc.? No, because a[10] or d['key'] are not identifiers. I can make an alias to 'a' or 'd' though. The same principle can be applied to types, functions, etc. Running 'dir' should return aids though, not plain identifiers, but since each aid is uniquely identifiable by anyone of its member identifiers, dir could return only identifiers with an assumption that each identifier represents an aid to which it belongs. I expect that this abstraction can be used to build the behavior of the aids as an analogy to how identifiers behave today. It can also point to the problems, for example do we need to make the aids hashable, or how to make the identifiers in them searchable, etc. The implementation can be an additional attribute on an identifier, which will list the additional aliases (if any was defined). The interpreter will have to check this attribute and eventually search it for matching identifier (or perform some other search to find the matching aid for a given id). Richard
Richard Musil writes:
The implementation can be an additional attribute on an identifier
That's not an implementation yet. From the point of view of the Python program, an identifier is an entry in a namespace. To describe an implementation of something that acts on identifiers, you need to say what namespaces are subject to this action, and what happens when the action might "cross" namespaces. Steve
On Tue, Sep 24, 2019 at 5:55 AM Stephen J. Turnbull < turnbull.stephen.fw@u.tsukuba.ac.jp> wrote:
Richard Musil writes:
The implementation can be an additional attribute on an identifier
That's not an implementation yet. From the point of view of the Python program, an identifier is an entry in a namespace. To describe an implementation of something that acts on identifiers, you need to say what namespaces are subject to this action, and what happens when the action might "cross" namespaces.
I am afraid this is where my abstraction ends, and my ignorance of Python internal implementation begins. I cannot answer those questions. Richard
On 9/24/19 4:54 AM, Richard Musil wrote:
On Tue, Sep 24, 2019 at 5:55 AM Stephen J. Turnbull <turnbull.stephen.fw@u.tsukuba.ac.jp <mailto:turnbull.stephen.fw@u.tsukuba.ac.jp>> wrote:
Richard Musil writes:
> The implementation can be an additional attribute on an identifier
That's not an implementation yet. From the point of view of the Python program, an identifier is an entry in a namespace. To describe an implementation of something that acts on identifiers, you need to say what namespaces are subject to this action, and what happens when the action might "cross" namespaces.
I am afraid this is where my abstraction ends, and my ignorance of Python internal implementation begins. I cannot answer those questions.
Richard
I think part of the issue is the idea that identifiers have attributes. In Python, identifiers are basically just labels in a namespace that bind to objects (and multiple labels can bind to the same object). Basically, in most cases the identifier is a key to a dictionary, the entry associated with it being a binding to an object. To add an 'attribute' to that identifier requires major changes in how that works and also will interfere with a namespace being able to override how that binding works. -- Richard Damon
Richard Musil wrote:
I had an idea for an abstract representation of the feature Nutchanon described. Let's call it aliasing and let's define it intuitively: a = 1 b alias a print(b) -> 1 b = 2 print(a) -> 2 The abstract representation would define a new construct _aliased_identifier_ (aid in short), which will behave exactly as the "normal" identifier behaves now. Now the Python identifier is basically a restricted string literal. The new aliased identifier will be a set of current identifiers. I am not saying it needs to be implemented as a Python set (it could be namespace, tuple, list, etc.), but feature-wise it will be a set - without ordering and with only unique members. Using Python "repr() style", one can write for example an aid with only one identifier called 'a' as aid('a'). Whenever the code uses an identifier, it will be using the aid which contains the corresponding identifier and technically, writing: a = 1, will mean under the hood aid('a') = 1. The important property of the aid is that it behaves the same as an identifier now, so it can bind to only one object. The other important property is that the regular identifier can be present in only one aid, because the identifier has to identify this aid in a unique way. Therefore it is not possible to have aid('a', 'b') and aid('b', 'c'), because 'b' does not identify a unique aid. Now executing: b alias a, will translate to: 'b' alias aid('a') => aid('a').add('b') = aid('a', 'b'). while preserving the binding aid('a') had before (if it had any). after that, whenever the code uses 'a' identifier, or 'b' identifier it will mean the same aid('a', 'b'). aids should behave as the identifiers today, except for the aliasing and unaliasing commands (unaliasing done by 'del' in the example below). For example, what happens if I do: a = 1 => aid('a') = 1 b alias a => aid('a', 'b') refers to the original object 1 c = 2 => aid('c') = 2 d alias c => aid('c', 'd') refers to the original object 2 Now what to do when: b alias c => aid('c', 'd'),add('b') = aid('b', 'c', 'd') and refer to the object aid('c', 'd') referred before, i.e. 2 but at the same time 'b' has to be removed from aid('a', 'b'), so we need to define: del b => aid('a', 'b').remove('b') => aid('a'), 'b' does not identify anything anymore, aid('a') still holds the reference. or if 'b' represents only simple aid('b') then del b => aid('b').remove('b') => 'b' does not exist anymore Can I alias an identifier to the item of the list, dict, etc.? No, because a[10] or d['key'] are not identifiers. I can make an alias to 'a' or 'd' though. The same principle can be applied to types, functions, etc. Running 'dir' should return aids though, not plain identifiers, but since each aid is uniquely identifiable by anyone of its member identifiers, dir could return only identifiers with an assumption that each identifier represents an aid to which it belongs. I expect that this abstraction can be used to build the behavior of the aids as an analogy to how identifiers behave today. It can also point to the problems, for example do we need to make the aids hashable, or how to make the identifiers in them searchable, etc. The implementation can be an additional attribute on an identifier, which will list the additional aliases (if any was defined). The interpreter will have to check this attribute and eventually search it for matching identifier (or perform some other search to find the matching aid for a given id). Richard
Talking about `del` and `unaid()` ("un-alias" or `unlink()` in my previous mention), I think `del` should keep its ability to let garbage collection free the memory. if we use the keyword del to "unbind" and "un-alias" that could be a mistake. ```python a = large_object() b alias a # now aid('a,'b') c alias a # now aid('a', 'b', 'c') # To free mem. as Richard propose del a # remain aid('b', 'c') cannot free mem del b # remain aid('c') cannot free mem del c # can free mem ``` Also, note that this is the same as Python chain assignment. ```python # a,b,c are chain-assigned. a = b = c = large_object() # To free mem del a # remain b,c cannot free mem del b # remain c cannot free mem del c # can free mem ``` My propose is `del` should un-bind all aliases at once. And still keep aliasing status until `unaid() or unaid_all()` is explicitly called. ```python obj = a = large_object() b alias a # now aid('a,'b') c alias a # now aid('a', 'b', 'c') # To free mem. del a # aid('b', 'c', 'a') remains, but aid('b', 'c', 'a') not point to the large_object() anymore del b # raise NameError del obj # free mem by delete last reference. # To "un-alias" unaid a # a is un-aliased. b, c are still aliases. a alias b # a, b, c are aliases again. a = heavy_object() # aid('a', 'b', 'c') = heavy_object() unaid_all a # all are unaliased. but, each is not unbind to the object. assert a is b and b is c # will be true. ``` Nutchanon
On Tue, Sep 24, 2019 at 10:26 AM Nutchanon Ninyawee <me@nutchanon.org> wrote:
Talking about `del` and `unaid()` ("un-alias" or `unlink()` in my previous mention), I think `del` should keep its ability to let garbage collection free the memory. if we use the keyword del to "unbind" and "un-alias" that could be a mistake.
There are two points: 1) As you wrote later, `del` will only allow garbage collection if the deleted reference was the last one. So it is not an operation on the object itself but on the binding between the identifier and the object. And since there is no notion in Python about the identifier without a binding, the identifier is deleted as well.
Also, note that this is the same as Python chain assignment. ```python # a,b,c are chain-assigned. a = b = c = large_object()
# To free mem del a # remain b,c cannot free mem del b # remain c cannot free mem del c # can free mem ```
2) When I wrote the first version of my idea, I actually defined `del` differently: ``` a = 1 b alias a del b ``` would translate to: ``` aid('a') = 1 aid('a').add('b') del aid('a', 'b') ``` which would work exactly as it works now in Python, `del` will remove the binding of aid('a', 'b') to '1', and consequently also delete aid('a', 'b') (because it will be no longer bound to any object). The consequence was that also the aliasing ceases to exist. While I considered it more true to the `del` behavior as we have now, I was bothered by a confusing aspect that it will basically delete all aliased names as well, and it may not be obvious which or how many they are. This was the reason why I eventually changed the behavior and let `del` work on aliases and only if the identifier was the last one in an aid remove the binding as well (i.e. as a `del` as we have now). This also kind of matches the today's behavior of the object with multiple references (as in your example). The consequence however is that you have to do `del` on every name in an aid to really release the object. So I guess both approches have some pros and cons. My propose is `del` should un-bind all aliases at once. And still keep
aliasing status until `unaid() or unaid_all()` is explicitly called. ```python obj = a = large_object() b alias a # now aid('a,'b') c alias a # now aid('a', 'b', 'c')
# To free mem. del a # aid('b', 'c', 'a') remains, but aid('b', 'c', 'a') not point to the large_object() anymore del b # raise NameError del obj # free mem by delete last reference.
Now, what you propose, does not really have a analogy in current Python. Preserving the aid even when it is not longer bound to any object would mean that things like this will stop to work: ``` In [1]: a = 1 In [2]: a Out[2]: 1 In [3]: del a In [4]: a --------------------------------------------------------------------------- NameError Traceback (most recent call last) <ipython-input-4-3f786850e387> in <module> ----> 1 a NameError: name 'a' is not defined In [5]: ``` Because Python will have to keep all aids once they were created somewhere (it could not distinguish between only one-id aid, and many-id aid, because these could change easily during the lifetime by aliasing or unaliasing). Richard
On Sep 22, 2019, at 02:08, Nutchanon Ninyawee <me@nutchanon.org> wrote:
Link is a language feature that allows multiple variable names to always refer to the same underlying object define in a namespace. For now, if the variable a link with b. I will denote as a >< b or link('a', 'b')
a = 2 a >< alpha # link 'a' with 'alpha' b = 3 print(b**alpha) # print 9 alpha = 3 print(b**a) # print 27
How would this work? I can think of a few options, but they all either badly slow down every variable use, don’t work with locals, or require >< to be handled at compile time. Do you have at least a sketch of how this works? Also, do you have a real-life use case? Usually when you want the equivalent of “variable references” in Python, you just stick the value in a list or an explicit namespace; why isn’t that sufficient here? Also, usually you’re doing it to share a reference with a function, not just using another name in the same namespace; why do you need actual references here but not in the more general and more often-desired case? And finally, a whole bunch of questions on behavior: What happens if you `del a`? Is there a syntax to unlink variables? It seems like you’d want that at least at the interactive REPL. Is there any way to discover whether two names are linked? If you link `a >< b` and then link `b >< c`, does that make a three-way link, or unlink a from b? If you link two locals and capture them both in a closure, are the closures a single cell, or two cells that reference captured names that happen to be linked? What if you link two names in a class body? What if it’s inside code passed to eval or exec with normal locals and globals? Or with a custom dict subclass or Mapping class for one or the other? Can you link arbitrary targets (e.g., self.a >< self.b, or even a >< b[10]), or just bare names? If you call globals(), do you get back some kind of dict-like object that knows about the links (and can be inspected), or something that looks like a plain-old dict but if you assign to its `a` you also change `b`, or something that looks like a plain-old dict but if you assign to its `a` you unlink it?
On Mon, Sep 23, 2019 at 10:42 AM Andrew Barnert via Python-ideas <python-ideas@python.org> wrote:
On Sep 22, 2019, at 02:08, Nutchanon Ninyawee <me@nutchanon.org> wrote:
Link is a language feature that allows multiple variable names to always refer to the same underlying object define in a namespace. For now, if the variable a link with b. I will denote as a >< b or link('a', 'b')
a = 2 a >< alpha # link 'a' with 'alpha' b = 3 print(b**alpha) # print 9 alpha = 3 print(b**a) # print 27
How would this work? I can think of a few options, but they all either badly slow down every variable use, don’t work with locals, or require >< to be handled at compile time. Do you have at least a sketch of how this works?
Since there's nothing in the OP's article about implementation, I'm going to assume that the OP hasn't actually planned any, and is just looking at syntax and semantics. So there's plenty of flexibility to discuss potential implementations. Currently, Python has a default set of rules for how to interpret the meaning of a bare name, but you can alter that with "global" and "nonlocal" declarations. Aliasing could be handled exactly the same way: at compile time, interpret this name as meaning the same as that name. It would be more Pythonic to use a keyword rather than the "><" glyph (even while typing it for that example, my fingers autocorrected to "<>", which would be syntactically valid but usually meaningless - a dangerous trap), so let's suppose that we can use the word "alias" for this. The trouble is that the "alias-ness" of it needs to be able to be stored in a namespace somehow. The class example implies that "program" is an alias for "code" in some way that means that can be stored in the class's dict, and doesn't interfere with descriptor protocol for the function (which, I think, implies that it cannot itself be a descriptor). The same applies to monkeypatching Selenium (in the given example, I honestly think the original name is better, but that's just a matter of the example); the alias has to be able to be stored in the instance's dict, and then looked up. In some cases, it may be possible to translate "x.y >< z" into something akin to "type(x).y = property(lambda: z, lambda v: z=v)" (yes, I know, a lambda function can't assign like that, but this is compiler magic so it can do what it likes). Unfortunately that would apply to ALL instances of that type - probably not a problem in the OP's Selenium example, but a fairly major problem when working with a generic class (ie where type(x) is type). Not sure where this leads us, though, as instance properties have been proposed and rejected before. ChrisA
On Sep 22, 2019, at 18:15, Chris Angelico <rosuav@gmail.com> wrote:
On Mon, Sep 23, 2019 at 10:42 AM Andrew Barnert via Python-ideas <python-ideas@python.org> wrote:
On Sep 22, 2019, at 02:08, Nutchanon Ninyawee <me@nutchanon.org> wrote:
Link is a language feature that allows multiple variable names to always refer to the same underlying object define in a namespace. For now, if the variable a link with b. I will denote as a >< b or link('a', 'b')
a = 2 a >< alpha # link 'a' with 'alpha' b = 3 print(b**alpha) # print 9 alpha = 3 print(b**a) # print 27
How would this work? I can think of a few options, but they all either badly slow down every variable use, don’t work with locals, or require >< to be handled at compile time. Do you have at least a sketch of how this works?
Since there's nothing in the OP's article about implementation, I'm going to assume that the OP hasn't actually planned any, and is just looking at syntax and semantics.
Sure. Part of the reason I asked all those specific questions about the semantics is that if the OP (or anyone else who groks the use case) has a strong feeling that the answer must be X or must be Y, that would constrain the possible ways to implement it. To take one example: if aliases shouldn’t affect globals(), that means we probably need to modify something about the way STORE_NAME and friends work with normal namespaces rather than to create a new AliasDict and make that the default type for module namespaces and leave STORE_NAME alone. Also, if there are “obvious right answers” to many of those questions, but they imply contradictory things, it’s worth knowing that before getting into the details of the implementation. And finally: I suspect that most of this proposal is an attempt to work around a complexity that Python and other namespace-based languages don’t have in the first place, only lvalue-based languages like C++ (see the “ensure no copy” thing). But there seems to be a core idea that is _not_ be irrelevant to Python. And diving into the semantics should hopefully separate those out.
Currently, Python has a default set of rules for how to interpret the meaning of a bare name, but you can alter that with "global" and "nonlocal" declarations. Aliasing could be handled exactly the same way: at compile time, interpret this name as meaning the same as that name.
Sure, but the OP clearly wants this to work in globals within a Jupyter notebook, not just locals. I’m not sure you’d even want that to be compile time, but if you did, it couldn’t work the same way as locals (where the entire scope is compiled at once, so the compiler can distinguish different kinds of names and hang them on the code object). Also, some of the examples add a link to a class after it’s already been created, or even to an instance. Even if you could figure out how to make compile-time links in a class statement, I can’t see how you could make dynamically adding links to a class object compile-time.
It would be more Pythonic to use a keyword rather than the "><" glyph (even while typing it for that example, my fingers autocorrected to "<>", which would be syntactically valid but usually meaningless -
Wait, isn’t <> a syntax error (unless you import Barry)? Not that I think >< is beautiful—and the OP gave that as a placeholder until someone thinks of something better—but if <> is an error, and linters and IDEs can suggest that maybe you meant ><, I’m not sure this part is a problem.
The trouble is that the "alias-ness" of it needs to be able to be stored in a namespace somehow.
Yes, and it needs to be storable in both dict-based and fast-local namespaces and work the same way (or differently but in a well-defined and -motivated different way), not to mention _slots_ and modules with custom namespace types and so on, and it needs to do so without making every runtime access and assignment significantly slower.
Chris Angelico wrote:
On Mon, Sep 23, 2019 at 10:42 AM Andrew Barnert via Python-ideas python-ideas@python.org wrote:
On Sep 22, 2019, at 02:08, Nutchanon Ninyawee me@nutchanon.org wrote:
Link is a language feature that allows multiple variable names to always refer to the same underlying object define in a namespace. For now, if the variable a link with b. I will denote as a >< b or link('a', 'b') a = 2 a >< alpha # link 'a' with 'alpha' b = 3 print(balpha) # print 9 alpha = 3 print(ba) # print 27 How would this work? I can think of a few options, but they all either badly slow down every variable use, don’t work with locals, or require >< to be handled at compile time. Do you have at least a sketch of how this works? Since there's nothing in the OP's article about implementation, I'm
going to assume that the OP hasn't actually planned any, and is just looking at syntax and semantics. So there's plenty of flexibility to discuss potential implementations. Currently, Python has a default set of rules for how to interpret the meaning of a bare name, but you can alter that with "global" and "nonlocal" declarations. Aliasing could be handled exactly the same way: at compile time, interpret this name as meaning the same as that name. It would be more Pythonic to use a keyword rather than the "><" glyph (even while typing it for that example, my fingers autocorrected to "<>", which would be syntactically valid but usually meaningless - a dangerous trap), so let's suppose that we can use the word "alias" for this. The trouble is that the "alias-ness" of it needs to be able to be stored in a namespace somehow. The class example implies that "program" is an alias for "code" in some way that means that can be stored in the class's dict, and doesn't interfere with descriptor protocol for the function (which, I think, implies that it cannot itself be a descriptor). The same applies to monkeypatching Selenium (in the given example, I honestly think the original name is better, but that's just a matter of the example); the alias has to be able to be stored in the instance's dict, and then looked up. In some cases, it may be possible to translate "x.y >< z" into something akin to "type(x).y = property(lambda: z, lambda v: z=v)" (yes, I know, a lambda function can't assign like that, but this is compiler magic so it can do what it likes). Unfortunately that would apply to ALL instances of that type - probably not a problem in the OP's Selenium example, but a fairly major problem when working with a generic class (ie where type(x) is type). Not sure where this leads us, though, as instance properties have been proposed and rejected before. ChrisA
Both the symbol "><" and keyword "link" are open for changes. In my idea, the link idea is closer to '=', more than any built-in functions. I see, this symbol '~' also a possible alternative. 'alias' is a good keyword. but, the first thing that comes to my mind is 'bash alias' which is not a very accurate behavior for. ```bash $ a=1 $ alias b='a' $ b=2 $ echo $a # print 1 ```
Andrew Barnert wrote:
Link is a language feature that allows multiple variable names to always refer to the same underlying object define in a namespace. For now, if the variable a link with b. I will denote as a >< b or link('a', 'b') a = 2 a >< alpha # link 'a' with 'alpha' b = 3 print(balpha) # print 9 alpha = 3 print(ba) # print 27 How would this work? I can think of a few options, but they all either badly slow down every variable use, don’t work with locals, or require >< to be handled at compile time. Do you have at least a sketch of how this works? Also, do you have a real-life use case? Usually when you want the equivalent of “variable references” in Python, you just stick the value in a list or an explicit namespace; why isn’t that sufficient here? Also, usually you’re doing it to share a reference with a function, not just using another name in the same namespace; why do you need actual references here but not in the more general and more often-desired case? And finally, a whole bunch of questions on behavior: What happens if you del a? Is there a syntax to unlink variables? It seems like you’d want that at least at the interactive REPL. Is there any way to discover whether two names are linked? If you link a >< b and then link b >< c, does
On Sep 22, 2019, at 02:08, Nutchanon Ninyawee me@nutchanon.org wrote: that make a three-way link, or unlink a from b? If you link two locals and capture them both in a closure, are the closures a single cell, or two cells that reference captured names that happen to be linked? What if you link two names in a class body? What if it’s inside code passed to eval or exec with normal locals and globals? Or with a custom dict subclass or Mapping class for one or the other? Can you link arbitrary targets (e.g., self.a >< self.b, or even a >< b[10]), or just bare names? If you call globals(), do you get back some kind of dict-like object that knows about the links (and can be inspected), or something that looks like a plain-old dict but if you assign to its a you also change b, or something that looks like a plain-old dict but if you assign to its a you unlink it?
Q: Do you have at least a sketch of how this works? A: I have no idea how to make this work in c/kernel level. I have no experience in implementing the core language before. Q: What happens if you `del a`? A: `del a` would unbind all linked names. Q: Is there a syntax to unlink variables? It seems like you’d want that at least at the interactive REPL. Q: Is there any way to discover whether two names are linked? A: Yes, there is. It is currently open for design and discussion. Q: If you link `a >< b` and then link `b >< c`, does that make a three-way link, or unlink a from b? A: That makes a three-way link. Q: If you link two locals and capture them both in a closure, are the closures a single cell, or two cells that reference captured names that happen to be linked? Q: What if you link two names in a class body? Q: What if it’s inside code passed to eval or exec with normal locals and globals? Or with a custom dict subclass or Mapping class for one or the other? A: I don't understand the "single cell, or two cells" concept. I bet it relates to inner representation like [this](http://code.activestate.com/recipes/439096-get-the-value-of-a-cell-from-a-cl...). I don’t know much about the implementation. Q: Can you link arbitrary targets (e.g., self.a >< self.b, or even a ><b[10]), or just bare names? A: I would like to limit this to bare names at first for the sake of simplification. Q: If you call `globals()`, do you get back some kind of dict-like object that knows about the links (and can be inspected), or something that looks like a plain-old dict but if you assign to its `a` you also change `b`, or something that looks like a plain-old dict but if you assign to its `a` you unlink it? A: I thinks there should be a `links()` function to inspect linked variables. globals() should behave like a plain-old dict but if you assign to its `a` you also change `b`.
On Sep 22, 2019, at 02:08, Nutchanon Ninyawee <me@nutchanon.org> wrote:
More in detail on this link https://dev.to/circleoncircles/python-ideas-link-bidirectional-aliasing-in-p...
After reading the whole thing… In “explicitly not-copy assignment”: assignment in Python never copies. Doing `df = data_train` already ensures that it’s the same DataFrame, as you can see if you check them with `is`. In fact, that’s true with most of your examples. Just using `=` already does what you want, because the only difference is what happens if you assign to one of the names again with `=`. None of your examples do so, and in most cases it seems very unlikely that you’d ever want to do so. In fact, the one example where I could imagine wanting to re-assign is this `df` example, where (especially in the REPL or a Jupyter notebook) I might well want to reassign `df` to a _different_ DataFrame, and I certainly would want that to change the binding of `df_train`. So, the one case where `=` and `><` would have different effects, I think `=` is the one you’d always actually want. In “simplify aliasing”, `uninstall = remove` already has the same effect as your `uninstall >< remove`, and in fact it’s a common idiom used all over the standard library. Sure, it means that if you shadow the class’s `uninstall` attribute with an instance attribute it’s no longer the same thing as `remove`, but that’s (presumably) still true in your link case, so neither one is really like `self.uninstall = self.remove`. I don’t think anyone does the `self.uninstall = self.remove` version often anyway, but if that is what you wanted to make easier, you haven’t achieved that. In the same section: how often do people use @property to create a getter and setter that just provides another name for something they’re already exposing publicly? Sure, you _can_ do that, but I don’t think I’ve ever seen anyone do it, or even ask how to do it, so why do we need to make it easier? In “change unintuitive naming”, again, you can already do the same thing with assignment. Whether you want to monkey patch the class or the instance, = will have the same effect as ><, unless some other code tries to monkey patch the same name later—which (a) seems very unlikely, and (b) I’m not sure you’d want it to patch both names anyway. In “allow unnested naming”: it seems like that makes it even harder to implement your feature in a reasonable way. Finally, in the “bad” section: there is a feature a lot like this in most languages where variables are lvalues instead of names. For example, in C++, you can write `auto & a = b`, and then any assignment to either variable changes the other. But that goes along with on a whole lot of things about the language being different. In C++, you can have references as function parameters; `=` is a normal operator that you can overload; storage and identity and type belong to variables rather than values; you can cast values between types through pointers; etc.
Andrew Barnert wrote:
More in detail on this link https://dev.to/circleoncircles/python-ideas-link-bidirectional-aliasing-in-p... After reading the whole thing… In “explicitly not-copy assignment”: assignment in Python never copies. Doing df = data_train already ensures that it’s the same DataFrame, as you can see if you check them with is. In fact, that’s true with most of your examples. Just using = already does what you want, because the only difference is what happens if you assign to one of the names again with =. None of your examples do so, and in most cases it seems very unlikely that you’d ever want to do so. In fact, the one example where I could imagine wanting to re-assign is this df example, where (especially in the REPL or a Jupyter notebook) I might well want to reassign df to a _different_ DataFrame, and I certainly would want
On Sep 22, 2019, at 02:08, Nutchanon Ninyawee me@nutchanon.org wrote: that to change the binding of df_train. So, the one case where = and >< would have different effects, I think = is the one you’d always actually want. In “simplify aliasing”, uninstall = remove already has the same effect as your uninstall >< remove, and in fact it’s a common idiom used all over the standard library. Sure, it means that if you shadow the class’s uninstall attribute with an instance attribute it’s no longer the same thing as remove, but that’s (presumably) still true in your link case, so neither one is really like self.uninstall = self.remove. I don’t think anyone does the self.uninstall = self.remove version often anyway, but if that is what you wanted to make easier, you haven’t achieved that. In the same section: how often do people use @property to create a getter and setter that just provides another name for something they’re already exposing publicly? Sure, you _can_ do that, but I don’t think I’ve ever seen anyone do it, or even ask how to do it, so why do we need to make it easier? In “change unintuitive naming”, again, you can already do the same thing with assignment. Whether you want to monkey patch the class or the instance, = will have the same effect as ><, unless some other code tries to monkey patch the same name later—which (a) seems very unlikely, and (b) I’m not sure you’d want it to patch both names anyway. In “allow unnested naming”: it seems like that makes it even harder to implement your feature in a reasonable way. Finally, in the “bad” section: there is a feature a lot like this in most languages where variables are lvalues instead of names. For example, in C++, you can write auto & a = b, and then any assignment to either variable changes the other. But that goes along with on a whole lot of things about the language being different. In C++, you can have references as function parameters; = is a normal operator that you can overload; storage and identity and type belong to variables rather than values; you can cast values between types through pointers; etc.
Since Python is an interpreted, dynamic language. In general, the type of variables tends to change throughout its lifetime especially in Jupyter notebook workflow. There will always be uncertainty about types, instance ids underlying each variable. Using `><`, will make a set of names refer to an object/value regardless of its immutable or mutable. Fair enough for the point that `df >< df_train` is the same as `df = df_train`. and the following preferable of `=` over `><` in most of the time. I agreed that `><` and `=` have the same effects most of the time. But the true value of `><` is laying in reassigning or re-binding which is not the case for `=`. Monkey patch may only benefit from a use case if you have like 3 or more name to patch and that not happen often. Personally, my code has a considerable amount of links. I would be much easier to maintain if there is such a thing like 'link'. This might be a paved way to multilingual coding. It will be a solid feature at least for me.
Nutchanon Ninyawee wrote:
Hi, Python community I would like to discuss with you about the idea of "bidirectional Aliasing". I am not good at English wording. I will try my best to communicate the idea Link is a language feature that allows multiple variable names to always refer to the same underlying object define in a namespace. For now, if the variable a link with b. I will denote as a >< b or link('a', 'b') a = 2 a >< alpha # link 'a' with 'alpha' b = 3 print(balpha) # print 9 alpha = 3 print(ba) # print 27 This also able to apply to function name, class property and method. More in detail on this link https://dev.to/circleoncircles/python-ideas-link-bidirectional-aliasing-in-p... Look forward to hearing from you Nutchanon Thank you for all constructive and considerate responses. I refined my proposal. [This is the updated one.](https://dev.to/circleoncircles/rewrite-link-bidirectional-aliasing-in-python...) The update aims to clarify more about the behaviors and rationale.
I guessed that this idea will stop here since the implementation ways are still unclear and the thread became inactive.
participants (8)
-
Andrew Barnert
-
Chris Angelico
-
Nutchanon Ninyawee
-
Richard Damon
-
Richard Musil
-
Robert Collins
-
Stephen J. Turnbull
-
Steven D'Aprano