Re: Python-list Digest, Vol 232, Issue 1
Re: Nonetype List In my introductory programming course, I have drawn some attention to this behavior regarding mutating lists. Indeed, Python is very consistent with its behavior: Any function that mutates a list parameter does not return that list as a return value. For one thing, there is no need to return that value, because the caller still owns the list parameter that has been modified. But secondly, (and what I find especially important), is that returning the modified list would lead too many program bugs or misunderstandings. For example, if append did return the list, you might see this: x = [1,2,3] y = x.append(4) z = y.append(5) The principal of 'least surprise' would cause a casual reader to believe that x retains the value of [1,2,3], y would have the value of [1,2,3,4], and z would contain [1,2,3,4,5]. So it would be very surprising indeed to discover that x contains [1,2,3,4,5], especially after that statement that makes no reference to x. Since append modifies the list in place, returning that list would make x, y, and z all aliases of each other, and aliasing is a source of many bugs that are very hard to find. So a recommendation that I make to my class (and which coincides with Python behavior), is to NEVER return a modified list as a return value, but only to return lists that were newly created within the function. So to support this principal of 'least surprise', the append method above would necessarily create new lists for y and z that are not aliases to x. Why Python does not do that is a very obvious cases of run-time efficiency (constant time to append vs. linear to recreate a new list). And as another observation, I have my students review all of the methods defined for the list object, and they are all very consistent. Most of them either define a return value, or modify the list parameter, but almost none do both. The sole exception is the pop() function that modified a list and returns a value, but that returned value still is not the modified list, so the aliasing problem will never arise. So, I am very happy with this Python language decision -- it allows for the most efficient means to modify a list in place and also very much reduce the danger of aliasing bugs. Roger Christman Pennsylvania State University From: Python-list on behalf of python-list-requ...@python.org Sent: Sunday, January 1, 2023 12:00 PM To: python-list@python.org Subject: Python-list Digest, Vol 232, Issue 1 Send Python-list mailing list submissions to python-list@python.org To subscribe or unsubscribe via the World Wide Web, visit https://nam10.safelinks.protection.outlook.com/?url=https%3A%2F%2Fmail.python.org%2Fmailman%2Flistinfo%2Fpython-list=05%7C01%7Cdvl%40psu.edu%7C744c83fc485a4b1c79db08daec19a436%7C7cf48d453ddb4389a9c1c115526eb52e%7C0%7C0%7C638081892123929669%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C3000%7C%7C%7C=etYqO01OszhEpqgjLeKQTMC9b3wT0sc%2FcN8oJo9eEhk%3D=0 or, via email, send a message with subject or body 'help' to python-list-requ...@python.org You can reach the person managing the list at python-list-ow...@python.org When replying, please edit your Subject line so it is more specific than "Re: Contents of Python-list digest..." Today's Topics: 1. Re: NoneType List (Thomas Passin) 2. Re: NoneType List (MRAB) 3. Re: NoneType List (dn) 4. RE: NoneType List (avi.e.gr...@gmail.com) 5. Re: NoneType List (Thomas Passin) 6. Re: NoneType List (Greg Ewing) 7. RE: NoneType List (avi.e.gr...@gmail.com) 8. Re: NoneType List (Chris Angelico) 9. RE: NoneType List (avi.e.gr...@gmail.com) 10. Re: NoneType List (Chris Angelico) 11. Re: NoneType List (Thomas Passin) -- Message: 1 Date: Sat, 31 Dec 2022 12:07:25 -0500 From: Thomas Passin To: python-list@python.org Subject: Re: NoneType List Message-ID: <3eb7480c-88f7-72cf-af66-c0072928b...@tompassin.net> Content-Type: text/plain; charset=UTF-8; format=flowed Oops, my reply got lost somehow. Here it is: Everyone's answer to date has been too complicated. What is going on is that list.append() changes the list in place. It returns nothing. If you want to append an item and then assign the result to a new list, you have to do just that: l1.append(item) # If we want a *copy* of the appended list: l2 = l1[:] # Changes to l2 will not change l1 # If we want another name for the appended list: l2 = l1 # Changes to l2 will change l1 since they are the same object list.sort() also operates in place. There is a function sorted() that returns the sorted list (without changing the original list). The same thing is true of set.add(). The set is changed in place, and nothing is returned. On 12/31/2022 10:50 AM, Thomas Passin wrote: > Happy New Year, everybody! > I'm new in the
Re: memoization (original Subject lost because mailer lost the whole thread)
"Hen Hanna" asked: > so... for a few days i've been revising this Code (in Gauche / Lisp / > Scheme) to make it run faster.. and last night i could improve it enough > to give me the result i wantedin 72 minutes or so (on my slow PC at > home). > ( Maybe... within a few months, i'll write the same program in Python > to see if it runs 10 or 20 times faster. ) > this was the first time i've used Caching (memoization). - instead of > calculating (at run-time)Factorial(x) and Combination(x,y) millions > of times, i made 2 tables in advance...A simple Table-lookup > (Vector-ref in Scheme) seems 100 -- 1000 times faster. > One thought i had was... Maybe Python's Factorial(x) and Combination(x,y) > (in Numpy ?) are already so fast that... i don't have to do the Caching > (memoization) ??? Memoization will generally be very fast -- since it is essentially a table-lookup. If it uses a hash-table (which is common for dictionaries), it would be close to a constant-time access for any entry; otherwise, if it uses some tree structure, it might be logarithmic in the number of entries in the tree. But that fast access comes at a price of storage -- linear with respect to the number of items stored (versus no memory cost incurred when not memoizing). What effect this has when you call these functions "millions of times" depends very much one how many of those calls are on the same values.If all of the calls have different arguments, memoization would not find it in the table yet, and would have to recompute as normal -- and you would end up with no time savings, but a considerable memory allocation for all of those newly-cached values that you never retrieve. The big wins come from asking the same questions repeatedly. Now, as far as Python's functionality, I would not expect it to do any memoization for you. It certainly could not predict what arguments would provide, but it still could still have a reasonable implementation without memoization. Your Factorial(x) and Combination(x,y) would both require a time linear with respect to the value of x, with no memory cost incurred. But if you were to spend most of your application asking the same questions, I would not expect these functions to do any caching or memoization, since I would expect very few applications would have enough calls to these functions to make it worthwhile.So calling these functions, you can expect a linear time for each function call, and if you expect to frequently repeat arguments, then you should add your own memoization for an amortized linear time. And fortunately, Python makes memoization very easy, by using a dictionary as a default value. I've done that often for classroom purposes for cases where it makes a big difference (recursive Fibonacci accelerates from exponential time to linear time). And the process is straightforward enough that you could even define a decorator that could be applied to any function you choose. I don't have an example handy just because I never took the time to write one. Roger Christman Pennsylvania State University -- https://mail.python.org/mailman/listinfo/python-list
Re: New assignmens ...
On 27/10/2021 8:28, Anton Pardon wrote: >>> Suppose I would like to write a loop as follows: >>. >while ((a, b) := next_couple(a, b))[1]: >> >do needed calculations >> >> >>> What I can do is write it as follows: >>> while [tmp := next_couple(a,b), a := tmp[0], b := tmp[1]][-1]: >> >do needed calculations >> >>> I really don't see what is gained by "forcing" me to right the second code >>> over the first. >> No, nobody is forcing you to write it the second way over the first. >> Nobody is forcing you to use the walrus operator at all! >> >> Instead, I would recommend something more like: >> >> while b: >> do needed calculations >> (a,b) = next_couple(a,b) > But AIU the walrus operator was introduced so we no longer needed, to write > such code, > with the calculation of the next candidate at the bottom and the test at the > top. > You just confirmed the walrus operator is not very useful once the next > candidate is > no longer just a name. I must disagree with the first sentence here regarding why the walrus operator was introduced. I read through the PEP, and saw nothing about that in the Rationale. I do see the loop-and-a-half example, but that actually had its next candidate value near the top (just before a break). I'm going to provide two loop-and-a-half segments to illustrate my interpretation of this PEP and the purpose of the walrus operator: Without the walrus: total = 0 value = input() while value >= 0: total += value value = input() print(total) With the walrus: total = 0 while (value := input()) > 0: total += value print(total) In terms of the PEP -- I want to compare each input value to 0 for the loop test, but I also want to preserve that value, to add to the total. There is a subexpression (input()) within a larger expression (compared to 0) that I wish to name for reuse. Now contrast with this example: Without the walrus: replay = True while replay: play_game() replay = input("Play again? ") in ['y','Y','yes','Yes'] (I think it silly to ask about playing again at first). With the walrus: replay = None while replay==None or (replay := input("Play again? ") in ['y','Y','yes','Yes']: play_game() To use the walrus operator here, I have to fabricate a value that would allow me to bypass the input operation, that cannot be otherwise produced. I do not find this second version any clearer or more intuitive than the first (and the PEP did emphasize the value of clarity). Now reading this in terms of the PEP, where I subexpression (the input) in a larger expression (seeing if it is the list), I do not really seem to have a compelling reason to name the value of that subexpression, since I am not using it for any other purpose, so I could keep the input in the while condition without using the walrus operator: first_play = True while first_play or input("Play again? ") in ['y','Y','yes','Yes']: play_game() first_play = False I do not find this particularly clearer or better than my first version with the test at the bottom, again since it requires me to do some extra machinery to avoid the undesired input before the first repetition. But I might also still argue that this is a little bit clearer than the walrus alternative above. My claim from these two examples is that the walrus is helpful to reduce duplicating or recomputing subexpressions, not simply just to facilitate code movement. It is not at all useful for a while loop condition if the subexpression you wish to evaluate depends on whether or not this is the first iteration of the loop. Which then gets me back to your own illustrated use-case for which I had raised a few questions/objections to before: while ((a, b) := next_couple(a, b))[1]: do needed calculations What is the name of the value you wish to test?b What is this actually testing? element [1] of a tuple So already the code is unclear (and was made worse when you said the existing walrus operator forced you to make a list of three elements, two assigned with the walrus, and then using a subscript of [-1] to get what you wanted. My proposed solution explicitly tested b, since that seemed to be what was of interest: while b: do needed calculations (a,b) = next_couple(a,b) To which you had replied: > But AIU the walrus operator was introduced so we no longer needed, to write > such code, > with the calculation of the next candidate at the bottom and the test at the > top. > You just confirmed the walrus operator is not very useful once the next > candidate is > no longer just a name. Okay, I would be happy to confirm my belief that the walrus is not very useful when the thing you wish to test is not the same as the thing you with to assign. But to relate this use case to my earlier loop-and-a-half examples: (a,b) := next_couple(a,b) presumes that there is are values for a and b to begin with for that first call
Re: New assignmens ...
On 27/10/2021 at 12:45 Antoon Pardon wrote: > However with the introduction of the walrus operator there is a > way to simulate a significant number of one and a half loops. > Consider the following: >do > a = expr1 > b = expr2 > while 2 * a > b: > more calculations > We could write that now as >while [ >a := expr1, > b := expr2, > 2 * a > b][-1]: > more calculations Why don't you try this? while 2 * (a := expr1) > (b := expr2): more calculations It seems you are just compelled to create tuple and lists in all of your use-cases, even when they serve no purpose. Roger Christman -- https://mail.python.org/mailman/listinfo/python-list
Re: New assignmens ...
Message: 8 Date: Mon, 25 Oct 2021 11:20:52 +0200 From: Antoon Pardon To: python-list@python.org Subject: Re: New assignmens ... Message-ID: <5761dd65-4e87-8b8c-1400-edb821204...@vub.be> Content-Type: text/plain; charset=utf-8; format=flowed On 25/10/2021 11:20, Anton Pardon wrote: > Suppose I would like to write a loop as follows: >while ((a, b) := next_couple(a, b))[1]: >do needed calculations > What I can do is write it as follows: > while [tmp := next_couple(a,b), a := tmp[0], b := tmp[1]][-1]: >do needed calculations > I really don't see what is gained by "forcing" me to right the second code > over the first. No, nobody is forcing you to right it the second way over the first. Nobody is forcing you to use the walrus operator at all! Instead, I would recommend something more like: while b: do needed calculations (a,b) = next_couple(a,b) This requires even less typing than what you had before! But it also raises a whole lot of problems with this particular example: -- neither a nor b is defined in your sample while loop. It seems you would need to initialize a and b before your while loop (and mine) -- is b truly a boolean value, or are you short-cutting some other value? -- are a and b truly necessary parameters to next_couple, or are they just there to remind the function of its previous return values? If the latter, poerhaps you want a stream or list or something with yield This example (and some of the others I have seen) just highlight how programmers will take advantage of any new tool to help them write worse code than if they did not have the tool. In my mind, the walrus operator was designed to serve a particular niche case like this one: while (x := input()) > 0: where not having the operator required duplicating the input() operation both before the loop and at the end of the loop -- or more complicated cases where some additional operations had to be performed to get that test value for the while condition (such as getting the b out of (a,b)). But the walrus only adds a benefit if it is there to avoid the duplication of the code that is used to obtain that test condition. This next_couple example does not qualify, since apparently (a,b) are initialized by some other means (and not be a call to next_couple with undefined values) Or the other abuse I saw recently about using the walrus operator: while (self.ctr := self.ctr-1) > 0: -- there was no compelling reason for a loop counter to be a class variable (anyone who peeks at this counter when the loop is down would only see a zero) -- this requires self.ctr to be initialized to a value one higher than the first meaningful value (start at 11 if you want to count down from 10) So my recommended alternative, which furthermore also takes less typing: while ctr > 0: ... ctr = ctr-1 TL;DR: The Walrus operator serves the purpose as described in its PEP just as it is, and I see no compelling reason to expand its use. It is there to reduce code size by eliminating a duplication of code, If the code you write using the walrus operator is longer or more complicated than the code would be without it, you are misusing it. Roger Christman Pennsylvania State University -- https://mail.python.org/mailman/listinfo/python-list
Re: new feature in Python
On 30/09/2020, yonatan <53770...@gmail.com> proposed: > instead of > con = "some text here" > con = con.replace("here", "there") > we could do > con = "some text here" > con .= replace("here", "there") That would require a major rewrite of the grammer of the Python language, which would probably be a bad thing. The operands to all of those assignment operators are complete objects in their own rights. This 'replace' is not an independent object, but a member of the string object. Without that "con." in front of it, you would likely get the error about "'replace' not defined" For the grammar to be clean and orthogonal, it would not be at all good to have a context-sensitivity introduced to allow orphaned method names to be permitted, just because there was this ".=" token somewhere earlier in the program statement. Besides, I could foresee a lot of programming errors when people would start to generalize this operation to do things like: list .= append(item) list .= sort() which would most certainly be incorrect. Roger Christman Pennsylvania State University -- https://mail.python.org/mailman/listinfo/python-list
Re: Friday Finking: Limiting parameters
I'll preface this by saying I am a programming instructor who still teaches from the ivory tower, so may not necessarily reflect actual practice in industry. But I have a very simple rule of thumb for limiting parameters: One should be able to summarize what a function does in one or two sentences, e.g. computes the volume of a cone, or print address labels. To fully understand the details of the task would certainly involve listing the parameters, but I would generally expect that a practical function would naturally put a cap on how much data is provided. If you need to include more than 7 parameters to describe what it is you want to do, you are probably doing more than one thing, or doing something unnecessarily complicated. I'll take your example with the address, and assume I want to print a mailing label.I could certainly see you needing to print: first name, middle initial, last name, house number, street name, apartment number, town, state, country, zip code, (and maybe more), and listing all of those individually would produce a very long list. But these values really are not all that independent of each other. A person's name can be viewed as a single value that happens to have multiple parts (collect that into a tuple or class). Similarly, a street address is a single object with multiple parts. These can easily be delivered as complete objects for a short parameter list. That does not necessarily mean that the function needs to know the particular representation or form of that data. Let those be objects with getter methods for the data you wish, and have the function document what methods it will attempt to call. Then any class that provides the expected methods would be suitable. That would also greatly simplify some other features for the problem. For example, "first name" "last name" really does not mean the same thing as "individual name" "family name", when some countries list the family name first. So it might be better to have a name object include a "full name" method for the print-mailing-address function to call instead of giving it access to component parts. TL;DR: I expect each parameter to a function to be independent or orthogonal to the others. Values that are closely related to each other can probably be encapsulated into a collection or object. If you still end up with a large number of independent parameters, I would question the simplicity of your function's intent. Roger Christman Pennsylvania State University -- https://mail.python.org/mailman/listinfo/python-list
Re: Friday Finking: Poly more thick
Emending my own note from moments ago: def any_as_dict(*args, **kwargs): if len(args) == 0: my_dict = kwargs elif type(args[0]) == type(dict()): my_dict = args[0] else: my_dict = dict(args[0]) print(type(my_dict),my_dict) >>> any_as_dict(a=1,b=2) {'a': 1, 'b': 2} >>> any_as_dict({'a':1, 'b':2}) {'a': 1, 'b': 2} >>> any_as_dict([('a',1), ('b',2)]) {'a': 1, 'b': 2} >>> any_as_dict({('a',1), ('b',2)}) {'b': 2, 'a': 1} -- https://mail.python.org/mailman/listinfo/python-list
Re: Friday Finking: Poly more thick
DL Neil asked: > How does one code a function/method signature so that > it will accept either a set of key-value pairs, > or the same data enclosed as a dict, as part of > a general-case and polymorphic solution? Will this do for you? def any_as_dict(*args, **kwargs): if len(args) == 0: my_dict = kwargs elif type(args[0]) == type(dict()): my_dict = args[0] else: my_dict = dict(args) print(type(my_dict),my_dict) >>> any_as_dict(a=1,b=2) {'a': 1, 'b': 2} >>> any_as_dict({'a':1, 'b':2}) {'a': 1, 'b': 2} >>> any_as_dict([('a',1), ('b',2)]) {('a', 1): ('b', 2)} >>> any_as_dict({('a',1), ('b',2)}) {('b', 2): ('a', 1)} This seems to address your apparent definition of "key-value pairs" in the form of a=1, b=2 and my interpretation as key-value tuples, either in a list, or set, or presumably any iterable collection, in addition to taking an actual dictionary. Roger Christman Pennsylvania State University -- https://mail.python.org/mailman/listinfo/python-list
Re: encapsulating a global variable (BlindAnagram)
> On Tue, 25 Feb 2020 3:06 PM BlindAnagram wrote: > My interest in this stems from wanting to keep the dictionary only > available to the function that uses it and also a worry about being > called from threaded code. It seems like the simplest solution for this is to make a completely new file (module) containing nothing but the dictionary and this one function that uses it. Then you can import the function from the module wherever it is called without importing the dictionary. The thing I find curious is that apparently no other function is allowed to create or populate the dictionary in the first place. Is it practical to have the entire dictionary statically defined? Roger Christman Pennsylvania State University -- https://mail.python.org/mailman/listinfo/python-list
Re: Loop with Else Clause
-Original Message- From: Python-list On Behalf Of DL Neil Sent: Monday, February 4, 2019 11:29 PM To: 'Python' Subject: Loop with else clause What is the pythonic way to handle the situation where if a condition exists the loop should be executed, but if it does not something else should be done? -- Just reading this by itself without seeing the illustrative problem, my first gut reaction was "while loop!" but then it became more obvious that this was a one-off condition to decide to get started, as opposed to being the loop condition itself. That being said, I would agree that you really cannot do better than the if-else already presented, since it is clearly a binary choice: execute the complete loop, or don't if you can't. I personally try to avoid the else part on both while loops and for loops because of this sort of confusion. All other things being equal, having an else section is fundamentally the same thing as simply omitting the 'else:' and out-denting the consequence, and makes it very clear just what is going on. The difference, of course, appears when you have a 'break' within that loop that exits prematurely. And that's where the structured-programming purist in me starts to gibber in fear and loathing. My purist self says that if 'break' is applicable, than exception handling is just as applicable with a much cleaner model, since you can control where to go next, whether to continue repeating, etc. If I don't expect to get all the way through a for loop, I simply don't write a for loop -- I'll substitute with a while, moving the logical negation of the break condition into the while loop condition, making it very clear that I don't plan to make it to the end. Unfortunately, the for loop is such a common Pythonic thing to do that I really can't say 'for/break' is unPythonic -- I can only substitute Lovecraftian adjectives (in my opinion, of course) to describe that construct. But this whole little tangent of mine doesn't seem to apply to your situation at all, and I don't there is a 'single' language element in any programming language I can think of that does what you ask -- so stick with the cleaner: if (__): # loop; else: #don't loop Roger Christman Pennsylvania State University -- https://mail.python.org/mailman/listinfo/python-list
Re: IDLE Default Working Directory
On 13 Nov 2018, at 09:51, Bev in TX wrote: > On Nov 12, 2018, at 5:50 PM, Terry Reedy wrote: > > For me, open (command-O) opens 'Documents'. I presume it should be easy > enough to move into a 'py' subfolder. The whole point is for Idle -> File -> Open (or command-O) to automatically open to a specific folder. Bev in TX Thank you, Bev in TX for clarifying my question. That is indeed what I seek. My course has proved cumbersome every time a student created a new program in class, since they would have to change folders.And when I got to working with data files, I ended up with a little punt to avoid having to specify a complete path name to get to a workable directory. My students are not programmers. I have English majors, Education majors, and students in their first year at a University. I won't say they are completely computer-illiterate, they can use a browser well enough. But I expect none of them to have ever seen the command line, so I really don't want to go that route. As far as activating IDLE on this Windows 10 system, I go down to the search bar at the bottom left, type 'IDLE', and then up comes a list of various installations we have lying around, including 2.7, 3.4, and 3.6 versions. I have them just select the 3.6 version from the list to launch IDLE.I don't consider that quite the same as using a 'shortcut', since we are not clicking on any icon on the desktop. I am not on the campus-wide labs right now, so I cannot really say much further -- and I haven't tried right-clicking on those items to see if configuration options show up, to talk to the %AppData% path, etc. The Penn State computer labs are networked -- each computer has a C: drive, which I presume might be local to each machine -- but in any case, I think it is read-only to the students. And since they could easily sit down at a different machine on any day, I wouldn't want to rely on any configuration file or anything on the C drive anyway. There is a networked U: drive (for users) that is campus wide, which is useful. There is also a virtual V: drive, which simply maps to each individual's folder set aside for them on the U: drive. That place would be ideal for my purposes as a place to save code and data files. But the default directory is somewhere else. I would have to get back on campus before I can quote exactly, but wherever it is a readonly file space that's not the Desktop or My Documents, or who knows what. I'd preferably like to reset the default to the V: drive (or even create a Python folder on that virtual V: drive). And the real challenge is to come up with the simplest solution that I can explain or show to first-time programming students in under a minute, which therefore does not involve the command-line interface. I don't want to scare half the students away in the very first class, just trying to configure their development environment. If that's impossible, then I guess I'll have to fire a note off to the university tech support requesting them to play with that "Start In" option through %AppData%, or whatever it was. Roger Christman Pennsylvania State University -- https://mail.python.org/mailman/listinfo/python-list
Re: IDLE Default Working Directory
eryk sun responded: On 11/12/18, Christman, Roger Graydon wrote: > > I looked in IDLE's own configuration menu, and didn't see anything there -- > and I fear that I might have to fight some Windows settings somewhere else > instead. I think this is Windows 10. Modify the "Start in" field of the IDLE shortcut. You can use environment variables, e.g. "%UserProfile%\Documents". Unfortunately, since I do not have any administrative privileges, I do not think I have the ability to modify any of these shortcuts. Nor do I know off hand how to set environment variables in a manner that would work consistently throughout the term, so that they persist for all logins. Since this is Windows-y system, I can't just tell my students to edit a .cshrc. Any other ideas? Roger Christman Pennsylvania State University -- https://mail.python.org/mailman/listinfo/python-list
IDLE Default Working Directory
Could anyone tell me how to set up IDLE's default working directory, so that it would be in the same place for each startup? (Like C:\Users\myname\Python) I teach a course that mounts a lot of file space across the network and the default directory for all my students is a readonly directory, which means they have to adjust anytime they want to save a new program or use a complete path name to write to a new output file. I would prefer that they wouldn't continually have to fight the system to save their code. I looked in IDLE's own configuration menu, and didn't see anything there -- and I fear that I might have to fight some Windows settings somewhere else instead. I think this is Windows 10. Roger Christman Pennsylvania State University -- https://mail.python.org/mailman/listinfo/python-list