As far as I know, the chain assignment pattern is started by jquery in frontend domain. It used to be popular before jquery is replaced by declarative modern frameworks (React, vue, etc). As is seen, github frontend and popular frontend framework bootstrap both abandoned the dependency of jquery, as well as such pattern of programming.
In the python world, as far as I know, chain assignment is often used in the domain of data processing, such as ORM query or dataframe processing. Take examples:
After reading so many helpful advices, I think it is better for me to develop a tiny polyfill to illustrate this idea more concretely, which will translate python codes with this new syntax to plain python. Just like Karl mentioned,
I am working on it. If there is any headway Iâll share the updates in this thread. Wish me luck!
Iâve found a way of doing this but with the current syntax of python it feels, sub-optimal. So perhaps some first class support would be appreciated.
def closure(f):
def activator(*args, **kwargs):
def wrapper(closure):
return f(closure, *args, **kwargs)
return wrapper
return activator
names = ['James', 'Josh', 'Micheal']
@closure
def filter(closure, array):
yield from [i for i in array if closure(i)]
@filter(names)
def filtered_names(name: str):
return name.startswith('J')
print(list(filtered_names))
This code is using decorator which converts the desired function we want to have a trailing block into a decorator factory.
This factory is then called with the functions arguments, and will return a decorator which should be applied to the function acting as the closure.
The original function is called with the closure function passed in. The decorator then returns the trailing block functionâs result which though how python works is set to a variable with same name as the closure function i.e âfiltered_namesâ
Although it give us same functionality - I donât love it as it doesnât feel very explicit or pythonic. so ultimately some first class support to allow for us to do this without abusing decorators would be ultimate goal (unless someone can improve on this code)
So I think having a @closure type built in decorator similar to @property would be the first step of adding this in. Having it pass the closure in as the first arg would provide good symmetry with functions like filter which currently accept a lambda as their first argument and prevent need of any new syntax or dunner methods (meaning in theory wrapping them as @closure would mean code would be upgraded without breaking existing code and gain support for trailing closures)
The only syntax adjustment we would need would be regarding the calling of the method - itâs strange that the closure function being decorated with the function which uses it as a trailing closure ends up being swapped out for the return of the decorator. People reading that code would find it odd, âwhere did the function goâ ?
I think in these cases we can introduce a way of using the with keyword as a way of indicating we want to provide a code block as a closure to the function just as it means we are wanting to run a code blcok within a context manger
For example, this is how the ideal syntax would look like:
names = ['James', 'Josh', 'Micheal']
@closure
def filter(closure, array):
yield from [i for i in array if closure(i)]
filtered_names = filter(names) with name: str -> bool:
return name.startswith('J')
print(list(filtered_names))
In my opinion this could unlock declarative DSLs as desired by the original author but also cases where having a small block of logic without needing to have a very long line (like with lambdas) or needing to define another inner-function which then needs the developer to make sure is always co-located.
Finally here is an example with a DSL
# Node is a DOM like class
@closure
def create_node(closure):
n = Node()
closure(n)
return n
document = node()
document.create_child with child: Node:
node.create_child(text="Hey There") with child: Node:
child.create_child(text="Here are some things")
But at the very least if this syntax change is too large then at least this way of doing trailing blocks in the python of today could be added to functools until people demand the new syntax.
I like this style. It can currently be implemented if people donât mind accessing the callerâs frame for the current widget in the context:
from sys import _getframe
from textwrap import indent
class Widget:
def __init__(self, name=None):
self.name = name
self.children = []
if widgets := _getframe(1).f_locals.get('_widgets'):
widgets[-1].children.append(self)
def __enter__(self):
self.widgets = _getframe(1).f_locals.setdefault('_widgets', [])
self.widgets.append(self)
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.widgets.pop()
def __repr__(self):
return (f'{self.__class__.__name__}({self.name or ''})\n' +
indent(''.join(map(repr, self.children)), ' '))
Usage:
class Window(Widget): ...
class Tabs(Widget): ...
class Tab(Widget): ...
class TextField(Widget): ...
class Button(Widget): ...
with Window('Login') as login_window:
with Tabs():
with Tab('Host'):
TextField('IP')
TextField('Alias')
with Tab('User'):
TextField('Username')
TextField('Password')
Button('Submit')
print(login_window)
James: many thanks for getting back to me! Over the year Iâve been learning the knowledge of ast & EBNF to implement a tiny precompile tool for my new syntax, it is really a lot of work.
The idea of closure & first-class function paradigm also came to me when I thought out the syntax proposal. In my opinion, it seems the way that ruby or swift does actually to implement complex structure declaration, not-likely the pythonic way. Say, python place the functional programming content to the functools module, instead of the global scope; and the lambda syntax or annoymous function is not recommended to be frequently used too. Thus I think to introduce proposal of functional programming syntax may not be consist with the language development.
My opinion is that we can introduce a definitely pythonic way to achieve same goals like the closure & funcitonal programming do. It needs a lot of work and I am working on it
Thank you Ben. Chris knows the core concept of the new trailing method syntax, whose proposal may be not the same like mine but share the same spirit.
Iâve been learning how to precompile python with polyfill syntax, just like the 2to3 module did. I think once a precompiler tool is developed, many similar ideas will be implemented.
I want to make sure I understand the proposal you make.
Normally, when a function or any old object is mentioned in python without being assigned, then if you are in the REPL, it gets echoed using whichever dunder method it can find to display it.
Are you suggesting that in your code, it do something a tad like this, without explicitly saying so?
with list() as mylist:
for elem in some_iterable:
v = some_function(elem)
# returns x_is
mylist.append(some_other_function(v, flag=True))
...
# return y_is
mylist.append(yet_another_function(elem))
An ineresting concept, but for a proper design, what would you suggest for these case:
mylist does not exist before the with. Does it get created? In what scope is it, especially after the loop and the with end?
What if mylist already exists and is not empty?
What if the body of the with contains lots more code including multiple such loops or even a nested with statement or even perhaps a different kind of such statement. Where do the contents go?
What if what you get back from a function or variable is a multiple return as might be captured into a tuple normally. Do you add a tuple or multiple singletons to the growing list?
If a function returns something like an iterator, is it left alone or iterated?
Does anything go to the screen or get evaluated by the REPL?
What happens if I call a function that does not intend on returning anything special and is sort of a pure procedure for doing side effects, or perhaps an object returning some kind of Null as in some methods that change an object in place. An example follows:
The middle line returns pretty much nothing, by design, as compared to sorted(temp) which returns a sorted list.
I am sure others would have their own questions and I suspect people reading the code may not easily understand what is being saved or even why.
Which leads to wondering if such syntactic sugar is useful enough. And would you want it extended so you could start with multiple lists and append selectively as can be done now without any problems.?
dear Roderick, I agree with the opinion the structure constructed by append method is much less readable than other ways.
The common idea is: for an object with complicated structure, it should be declared with structure inheritance remained, instead of being built by calling adding children function again and again.
Dear Gross, I also think of the side effect of operating unassigned variables. In my proposal, I come up with a solution which is: only certain type of unassigned variables should be caught (the one with trailing dunder method).
This approach has been already used in some popular tools. For example, in jupyter REPL environment, normally, an unassigned variable will be echoed with repr result; but, if it is an instance of pandas dataframe, it would be operated with HTML table rendering process; and if an unassigned variable is of the type of matplotlib plot, it will be processed with figure output.
Just FYI, my surname is not how I tend to be addressed but an easy mistake to make. LOL!
I tried to understand what you were updating and to see which of my points it was addressing. I donât think I was able to.
My understanding of your main point was that in that special situation of a âwith as variableâ, you wanted people to not use any unassigned statements unless they wanted it added to the list called variable. I had no special objection, especially since I was not likely going to be the one implementing it if approved. I was asking if you had thought through it so any proposal you made would answer as many questions or objections as needed. Or, perhaps, you would need to modify or withdraw a suggestion if âŚ
So what do you mean by this:
" only certain type of unassigned variables should be caught (the one with **trailing** dunder method)."
Your original proposal sounds like anything not assigned such as sin(x) should be evaluated and the result added to the list. Use of an underscore before, inside and after other characters in a variable name is pretty much allowed but other than within a name to make it easier to read, and use of a single underscore to often mean you want to ignore something, I rarely see it used with one exception.
Inside the definition of a class, it is common to use a single underscore in front of a variable to suggest it is not meant for external access/use. And, there is a growing slew of dunder methods like repr with double underscores.
So, when you talk about certain variable to be âcaughtâ if they have a trailing underscore, what do you mean? My example of sin(x) cannot easily qualify unless I write one line assigning the result to a variable like sin_ and on the next line invoke that.
Maybe I am thinking differently than you mean and you can correct me.
As to your second part, I believe you are trying to provide a justification based on yet another system, Jupyter, which really is mostly just about a REPL response. It has nothing to do with silently adding results to a list instead of, or perhaps in addition to, showing them on the output.
I did a little thinking and wonder if you are talking about a Jupyter feature for remembering recent output. You can use specific variable like _ or _1 or _2 to receive recent values seen by the REPL. Obviously these change as program statements are executed.
But I hazard to say these are not features of Python as a language and are more of a localized added feature only within Jupiter. Standard Python does save the very last thing seen by the REPL in _ BUT not really as shown below where I hijacked the undersore variable to mean something else.
>>> max(5,6)
6
>>> _
6
>>> _,b = 1,2
>>> _
1
Your other point that a pandas displays differently is in subtle ways not quite right. The REPL knows little to nothing about objects but does know to use one of several dunder methods normally found in objects to ask it to display itself. Read up on str and repr if interested. Generally, these methods will not produce HTML, albeit they could be programmed too. Quite likely the representation shown by Python outside Jupyter might be different.
In summary, Jupyter is not Python or vice versa. There is plenty of overlap. I do not need you to answer my questions but want you to understand an actual proposal meant to be acted on will need more than you supplied.
I am sorry to address you with unfamiliar name âŚ
Excuse me for I may not express my idea clearly. I mean actually , in my syntax proposal, whether a local scope variable should be caught or be ignored, is determined by its type instead of its name. If the type of the local variable is conform to Trailer protocol (the class which has a trailing dunder method), it should be caught for later operations like appending; otherwise it should be ignored just as normal unassigned variable.
The name trailing in this thread may be so confusing . It seems that I want to use variable name suffix to distinguish these two kinds of local variables; while it is not what I meant.
As for the jupyter example, I take it just as âunassigned variables can be operated differently by their typeâ. Because jupyter does operate unassigned variable that conform to dataframe class with different operations (call to_html_table rather than just get repr result). In this scenario, if you replace the to_html_table rendering with appending, it is just the case that we talking about.
I do aware of that the proposal is far from practical ⌠Currently I am been coding for a translator or compiler for this proposal. I will reveal it as soon as I can. cheer me on!!
No problem Du. I was just amused as I encounter similar issues in other languages I speak where names are indeed done the other way and in my weaker languages, I make my own amusing mistakes.
Letâs get back to python where you are now explaining what you want better.
If I now understand your proposal, it may get a bit trickier to implement. To be clear, the first proposal sounded like you wanted a region of the code where the interpreter flagged any unassigned values and added them to a growing designated list. I have not seen the code, but it sounds doable if someone decides.
Your updated proposal sounds a bit different and I hesitate to say I understand it. I am not aware of a trailer protocol in python. I also am not clear on when people use a single underscore after a variable, at least outside a class definition.
It does sound like your ideas are largely about the Jupyter environment, which is a sort of superset of python. However, your examples may not work as you think.
First, I have pointed out that any difference in how things are displayed may be either internal to the object itself as in specifying the display format, or may be done by Jupyter by internal logic that does what might be more readable in special cases.
In the latter case, yes, there may be a hook you can use for additional functionality but how exactly? I mean, in order for the current evaluated object to be added to a named list somewhere in the environment, you would need whatever hook to know about all of this. Your âwithâ statement would have to set up some environment that other code could use to locate and implement such a policy, perhaps selectively. Then, when it ends, it would need to clean up while leaving the named list available further in the code, and different than any other such uses.
It seems much simpler to have the interpreter save a copy of everything as it will know what it needs to know. And, you do not actually need this feature if it applies to few selected things you want to save as you can use the code earlier discussed to do that directly.
Itâs true that itâs so far not been recommended but I would actually argue using trailing closures / is a more pythonic way of pulling of DSLs (As shown in my original comment) and Roderick de Nijs proposal (It could just be a closure that yields)
Whilst it does help with functional programming, itâs not itâs only purpose regardless of pythonâs opinion on it.
Firstly, itâs just a bit of syntactical sugar with no additional keywords being added so it doesnât introduce any new behaviour, meaning the implementation could be kept small and doesnât present any additional learning for the user (For all intents and purposes it functions exactly the same as the decorated function I showed above).
This would make this feature an incremental change for Python and something that could land in 3.15 for example. It could be a first class decorator like @property currently is.
Secondly, itâs more explicit than the trailing proposal, Since this closure would rely on the user calling methods on the objects passed in or using the same concepts of yielding or returning as functions use. When reading it I know that for example I am adding a child to the parent node because in my closure I have node.create_child. This is very pythonic.
Thirdly, I think the many reason a lot of the python ecosystem doesnât recommend lambdas is readability and to avoid callback hell.
I would argue that this is a function of the syntax that you currently cannot write a lambda that can span multiple lines. And even if you could it would appear in the middle of your functionâs arguments making it hard to read.
My proposal of trailing closures was introduced into Ruby + Swift to solve this issue, it appears after the functions arguments meaning you keep the context and remove the callback hell
You donât have really long hard to read functions like with pythonâs lambdas. And itâs much more readable than having to pass in the name of a function that handles the construction a part of your DLSâs tree (Which is how tools like Gradio do this) or have massive if statements deciding which nodes to construct depending on some runtime value.
In my opinion trailing closures just like with the context managers will improve readability whilst keeping in line with pythonâs expressivness. Without needing to introduce such a drastic change just to support DLS.
Iâm looking forward to the preprocessor as then we can consider building trailing closures in, otherwsie if this isnât the dicussion for it Iâll raise another proposal for trailing closures.
I just want to add a note on what may be a side topic.
The concept I have seen expressed several times is of using an existing keyword, or perhaps I should say reusing, in some new context.
But at what point does that become even more confusing and disruptive than introducing a new keyword?
As an example, some smart editor may need to learn that the prescence of a keyword may not mean the same thing or should not be drawn in the same color, as a use elsewhere. And, of course, there may be edge cases where the meaning of a keyword is even more confusing, and perhaps worse.
If you compare python to other languages, this problem is far from unique. Any growing or evolving language may want to add functionality and face these questions.
But some seem freer to evolve. As an example, R allows you to create all kinds of new operators in multiple contexts that look a bit like new keywords so that you can write something like:
result <- var1 %newname% var2
You can make more complex names that can include spaces and brackets by putting the name in grave accents.
So, when writing code in R, I often have no need to ask for new keywords for quite a few constructs and can selective import packages into the namespace or just define them and use them inline.
Another example would be SCALA where all kinds of little languages can easily be created as code like this:
result = var1 newop var2
Becomes transformed through syntactic sugar to be a method call where newop is a method defined on the object called var1 and it is passed the variable var2 as in:
result = var1.newop(var2)
The ânewopâ can be all kinds of symbol combination such as + or +: with the latter ending in a colon making it associate in the other direction and so on.
Again, quite a bit of functionality can be done so that operators can be overloaded and the definition of something like + can mean something different like adding entries from var2 to what was in var1 but only if they are missing in var1.
There are features in python that people use for some flexibility, of course. But I question how wise it is to have the same keyword, such as else used in places where half of the programmers get the impression it is wrong!
I am wondering if one new feature may be worth considering. Could there be a way, very briefly, where you can declare a ânameâ to be reserved in the code to the point where you say you want to unreserve it.
You could then have a variety of pre-canned keywords you could in some sense import. Inside that zone, it would be a keyword that does whatever it is supposed to do. The programmer would be responsible for that zone to be clear of conflicting variable names, and, of course, the keyword would be very local and not visible in code called in that zone, just in the top level.
I cannot say that would be very useful, but see possibilities. And, please note, I am not proposing any such feature, just wondering if there is a compromise that allows some careful features using new and unique key words without being as confusing as re-use.