Re: A technique from a chatbot

2024-04-05 Thread Mark Bourne via Python-list

Stefan Ram wrote:

Mark Bourne  wrote or quoted:

I don't think there's a tuple being created.  If you mean:
 ( word for word in list_ if word[ 0 ]== 'e' )
...that's not creating a tuple.  It's a generator expression, which
generates the next value each time it's called for.  If you only ever
ask for the first item, it only generates that one.


   Yes, that's also how I understand it!

   In the meantime, I wrote code for a microbenchmark, shown below.

   This code, when executed on my computer, shows that the
   next+generator approach is a bit faster when compared with
   the procedural break approach. But when the order of the two
   approaches is being swapped in the loop, then it is shown to
   be a bit slower. So let's say, it takes about the same time.


There could be some caching going on, meaning whichever is done second 
comes out a bit faster.



   However, I also tested code with an early return (not shown below),
   and this was shown to be faster than both code using break and
   code using next+generator by a factor of about 1.6, even though
   the code with return has the "function call overhead"!


To be honest, that's how I'd probably write it - not because of any 
thought that it might be faster, but just that's it's clearer.  And if 
there's a `do_something_else()` that needs to be called regardless of 
the whether a word was found, split it into two functions:

```
def first_word_beginning_with_e(target, wordlist):
for w in wordlist:
if w.startswith(target):
return w
return ''

def find_word_and_do_something_else(target, wordlist):
result = first_word_beginning_with_e(target, wordlist)
do_something_else()
return result
```


   But please be aware that such results depend on the implementation
   and version of the Python implementation being used for the benchmark
   and also of the details of how exactly the benchmark is written.

import random
import string
import timeit

print( 'The following loop may need a few seconds or minutes, '
'so please bear with me.' )

time_using_break = 0
time_using_next = 0

for repetition in range( 100 ):
 for i in range( 100 ): # Yes, this nesting is redundant!

 list_ = \
 [ ''.join \
   ( random.choices \
 ( string.ascii_lowercase, k=random.randint( 1, 30 )))
   for i in range( random.randint( 0, 50 ))]

 start_time = timeit.default_timer()
 for word in list_:
 if word[ 0 ]== 'e':
 word_using_break = word
 break
 else:
 word_using_break = ''
 time_using_break += timeit.default_timer() - start_time

 start_time = timeit.default_timer()
 word_using_next = \
 next( ( word for word in list_ if word[ 0 ]== 'e' ), '' )
 time_using_next += timeit.default_timer() - start_time

 if word_using_next != word_using_break:
 raise Exception( 'word_using_next != word_using_break' )

print( f'{time_using_break = }' )
print( f'{time_using_next = }' )
print( f'{time_using_next / time_using_break = }' )


--
https://mail.python.org/mailman/listinfo/python-list


Re: A technique from a chatbot

2024-04-05 Thread Mark Bourne via Python-list
next` (nor anything else) on it, so 
no need for it to be kept hanging around in memory.



Perhaps worse, imagine doing the search in parallel and as sone as it is found 
anywhere, ...



-Original Message-
From: Python-list  On 
Behalf Of Mark Bourne via Python-list
Sent: Thursday, April 4, 2024 3:04 PM
To: python-list@python.org
Subject: Re: A technique from a chatbot

Thomas Passin wrote:

On 4/2/2024 1:47 PM, Piergiorgio Sartor via Python-list wrote:

On 02/04/2024 19.18, Stefan Ram wrote:

Some people can't believe it when I say that chatbots improve
my programming productivity. So, here's a technique I learned
from a chatbot!
It is a structured "break". "Break" still is a kind of jump,
you know?
So, what's a function to return the first word beginning with
an "e" in a given list, like for example
[ 'delta', 'epsilon', 'zeta', 'eta', 'theta' ]

? Well it's
def first_word_beginning_with_e( list_ ):
  for word in list_:
  if word[ 0 ]== 'e': return word

. "return" still can be considered a kind of "goto" statement.
It can lead to errors:

def first_word_beginning_with_e( list_ ):
  for word in list_:
  if word[ 0 ]== 'e': return word
  something_to_be_done_at_the_end_of_this_function()
The call sometimes will not be executed here!
So, "return" is similar to "break" in that regard.
But in Python we can write:
def first_word_beginning_with_e( list_ ):
  return next( ( word for word in list_ if word[ 0 ]== 'e' ), None )


Doesn't look a smart advice.


. No jumps anymore, yet the loop is aborted on the first hit


It's worse than "not a smart advice". This code constructs an
unnecessary tuple, then picks out its first element and returns that.


I don't think there's a tuple being created.  If you mean:
  ( word for word in list_ if word[ 0 ]== 'e' )

...that's not creating a tuple.  It's a generator expression, which
generates the next value each time it's called for.  If you only ever
ask for the first item, it only generates that one.

When I first came across them, I did find it a bit odd that generator
expressions look like the tuple equivalent of list/dictionary
comprehensions.

FWIW, if you actually wanted a tuple from that expression, you'd need to
pass the generator to tuple's constructor:
  tuple(word for word in list_ if word[0] == 'e')
(You don't need to include an extra set of brackets when passing a
generator a the only argument to a function).


--
https://mail.python.org/mailman/listinfo/python-list


Re: A technique from a chatbot

2024-04-04 Thread Mark Bourne via Python-list

Thomas Passin wrote:

On 4/2/2024 1:47 PM, Piergiorgio Sartor via Python-list wrote:

On 02/04/2024 19.18, Stefan Ram wrote:

   Some people can't believe it when I say that chatbots improve
   my programming productivity. So, here's a technique I learned
   from a chatbot!
   It is a structured "break". "Break" still is a kind of jump,
   you know?
   So, what's a function to return the first word beginning with
   an "e" in a given list, like for example
[ 'delta', 'epsilon', 'zeta', 'eta', 'theta' ]

   ? Well it's
def first_word_beginning_with_e( list_ ):
 for word in list_:
 if word[ 0 ]== 'e': return word

   . "return" still can be considered a kind of "goto" statement.
   It can lead to errors:

def first_word_beginning_with_e( list_ ):
 for word in list_:
 if word[ 0 ]== 'e': return word
 something_to_be_done_at_the_end_of_this_function()
   The call sometimes will not be executed here!
   So, "return" is similar to "break" in that regard.
   But in Python we can write:
def first_word_beginning_with_e( list_ ):
 return next( ( word for word in list_ if word[ 0 ]== 'e' ), None )


Doesn't look a smart advice.


   . No jumps anymore, yet the loop is aborted on the first hit


It's worse than "not a smart advice". This code constructs an 
unnecessary tuple, then picks out its first element and returns that.


I don't think there's a tuple being created.  If you mean:
( word for word in list_ if word[ 0 ]== 'e' )

...that's not creating a tuple.  It's a generator expression, which 
generates the next value each time it's called for.  If you only ever 
ask for the first item, it only generates that one.


When I first came across them, I did find it a bit odd that generator 
expressions look like the tuple equivalent of list/dictionary 
comprehensions.


FWIW, if you actually wanted a tuple from that expression, you'd need to 
pass the generator to tuple's constructor:

tuple(word for word in list_ if word[0] == 'e')
(You don't need to include an extra set of brackets when passing a 
generator a the only argument to a function).


--
Mark.
--
https://mail.python.org/mailman/listinfo/python-list


Re: A missing iterator on itertools module?

2024-04-01 Thread Mark Bourne via Python-list

Stefan Ram wrote:

ast  wrote or quoted:

Why did you renamed itertools as _itertools ?


   Assume I have a module A.py:

import math
def f(): pass

   . Assume I have an additional module B.py:

import A

   . Now, when I'm editing "B.py" in IDLE and type "A.", IIRC
   IDLE will offer me two possible completions: "A.math" and
   "A.f". The "A.math" makes no sense to me.


`import math` imports the `math` module and binds it to `math` in the 
global namespace of the `A` module.  Since it doesn't have a leading 
underscore, by default it's considered to be a public attribute of the 
`A` module, and IDLE is offering all the public attributes of the `A` 
module for completion.



I want it to go
   away. Therefore, I rewrite A.py as:

import math as _math
def f(): pass

   . Now, Idle will only offer the completion "A.f".

   So, I sometimes use this "import math as _math" style. But then,
   it is simpler for me to /always/ use this style; after all: you
   can't know whether someone eventually will import your module!


You can explicitly declare the public interface of a module by defining 
`__all__`, listing the names which should be considered part of the 
module's public interface; see:

- https://docs.python.org/3/reference/simple_stmts.html#the-import-statement
- https://peps.python.org/pep-0008/#public-and-internal-interfaces

Although `from A import *` is generally discouraged, if `A` defines 
`__all__` then only the names listed in `__all__` are bound in the 
importing module's namespace.  Otherwise, all names from `A` which don't 
have a leading underscore are considered to be public and bound in the 
importing module.


I don't use IDLE, but it may be that it also uses `__all__` to determine 
a module's public API.  In that case, setting `__all__ = ["f"]` in `A` 
should prevent it from offering `math` as a completion (nor any other 
name that's not in the `__all__` list).


--
Mark.
--
https://mail.python.org/mailman/listinfo/python-list


Re: Using a background thread with asyncio/futures with flask

2024-03-22 Thread Mark Bourne via Python-list

Thomas Nyberg wrote:

Hi,

Yeah so flask does support async (when installed with `pip3 install 
flask[async]), but you are making a good point that flask in this case 
is a distraction. Here's an example using just the standard library that 
exhibits the same issue:


`app.py`
```
import asyncio
import threading
import time
from queue import Queue


in_queue = Queue()
out_queue = Queue()


def worker():
     print("worker started running")
     while True:
     future = in_queue.get()
     print(f"worker got future: {future}")
     time.sleep(5)
     print("worker sleeped")
     out_queue.put(future)


def finalizer():
     print("finalizer started running")
     while True:
     future = out_queue.get()
     print(f"finalizer got future: {future}")
     future.set_result("completed")
     print("finalizer set result")


threading.Thread(target=worker).start()
threading.Thread(target=finalizer).start()


async def main():
     future = asyncio.get_event_loop().create_future()
     in_queue.put(future)
     print(f"main put future: {future}")
     result = await future
     print(result)


if __name__ == "__main__":
     loop = asyncio.get_event_loop()
     loop.run_until_complete(main())
```

If I run that I see the following printed out (after which is just hangs):

```
$ python3 app.py
worker started running
finalizer started running
main put future: 
worker got future: 
worker sleeped
finalizer got future: 
finalizer set result
```

I believe async uses a cooperative multitasking setup under the hood, so 
I presume the way I'm doing this threading just isn't playing well with 
that (and presumably some csp yield isn't happening somewhere). Anyway 
at this point I feel like the easiest approach is to just throw away 
threads entirely and learn how to do all I want fully in the brave new 
async world, but I'm still curious why this is failing and how to make 
this sort of setup work since it points to my not understanding the 
basic implementation/semantics of async in python.


Thanks for any help!

/Thomas

On 3/22/24 08:27, Lars Liedtke via Python-list wrote:

Hey,

As far as I know (might be old news) flask does not support asyncio.

You would have to use a different framework, like e.g. FastAPI or 
similar. Maybe someone has already written "flask with asyncio" but I 
don't know about that.


Cheers

Lars


Lars Liedtke
Lead Developer

[Tel.]  +49 721 98993-
[Fax]   +49 721 98993-
[E-Mail]    l...@solute.de


solute GmbH
Zeppelinstraße 15
76185 Karlsruhe
Germany

[Marken]

Geschäftsführer | Managing Director: Dr. Thilo Gans, Bernd Vermaaten
Webseite | www.solute.de 
Sitz | Registered Office: Karlsruhe
Registergericht | Register Court: Amtsgericht Mannheim
Registernummer | Register No.: HRB 748044
USt-ID | VAT ID: DE234663798



Informationen zum Datenschutz | Information about privacy policy
https://www.solute.de/ger/datenschutz/grundsaetze-der-datenverarbeitung.php 






Am 20.03.24 um 09:22 schrieb Thomas Nyberg via Python-list:

Hello,

I have a simple (and not working) example of what I'm trying to do. 
This is a simplified version of what I'm trying to achieve (obviously 
the background workers and finalizer functions will do more later):


`app.py`

```
import asyncio
import threading
import time
from queue import Queue

from flask import Flask

in_queue = Queue()
out_queue = Queue()


def worker():
    print("worker started running")
    while True:
    future = in_queue.get()
    print(f"worker got future: {future}")
    time.sleep(5)
    print("worker sleeped")
    out_queue.put(future)


def finalizer():
    print("finalizer started running")
    while True:
    future = out_queue.get()
    print(f"finalizer got future: {future}")
    future.set_result("completed")
    print("finalizer set result")


threading.Thread(target=worker, daemon=True).start()
threading.Thread(target=finalizer, daemon=True).start()

app = Flask(__name__)


@app.route("/")
async def root():
    future = asyncio.get_event_loop().create_future()
    in_queue.put(future)
    print(f"root put future: {future}")
    result = await future
    return result


if __name__ == "__main__":
    app.run()
```

If I start up that server, and execute `curl http://localhost:5000`, 
it prints out the following in the server before hanging:


```
$ python3 app.py
worker started running
finalizer started running
* Serving Flask app 'app'
* Debug mode: off
WARNING: This is a development server. Do not use it in a production 
deployment. Use a production WSGI server instead.

* Running on http://127.0.0.1:5000
Press CTRL+C to quit
root put future: 
worker got future: 
worker sleeped
finalizer got future: 
finalizer set result
```

Judging by what's printing out, the `final result = await future` 
doesn't seem to be happy here.


Maybe someone sees something obvious I'm doing wrong here? I presume 
I'm mixing 

Re: Popping key causes dict derived from object to revert to object

2024-03-22 Thread Mark Bourne via Python-list

Loris Bennett wrote:

Hi,

I am using SQLAlchemy to extract some rows from a table of 'events'.
 From the call to the DB I get a list of objects of the type

   sqlalchemy.orm.state.InstanceState

I would like to print these rows to the terminal using the 'tabulate'
package, the documentation for which says

   The module provides just one function, tabulate, which takes a list of
   lists or another tabular data type as the first argument, and outputs
   a nicely formatted plain-text table

So as I understand it, I need to convert the InstanceState-objects to,
say, dicts, in order to print them.  However I also want to remove one
of the keys from the output and assumed I could just pop it off each
event dict, thus:
  
 event_dicts = [vars(e) for e in events]

 print(type(event_dicts[0]))
 event_dicts = [e.pop('_sa_instance_state', None) for e in event_dicts]
 print(type(event_dicts[0]))


vars() returns the __dict__ attribute of the object.  It may not be a 
good idea to modify that dictionary directly (it will also affect the 
object), although it might be OK if you're not going to do anything else 
with the original objects.  To be safer, you could copy the event objects:

event_dicts = [dict(vars(e)) for e in events]
or:
event_dicts = [vars(e).copy()]


However, this prints

   
   

If I comment out the third line, which pops the unwanted key, I get

   
   

Why does popping one of the keys cause the elements of the list to
revert back to their original class?


As Dieter pointed out, the main problem here is that pop() returns the 
value removed, not the dictionary with the rest of the values.  You 
probably want something more like:

for e in event_dicts:
del e['_sa_instance_state']
(There's not really any point popping the value if you're not going to 
do anything with it - just delete the key from the dictionary)


--
Mark.
--
https://mail.python.org/mailman/listinfo/python-list


Attaching a mock function to another mock breaks reset_mock()

2023-06-19 Thread Mark Bourne via Python-list
I've came across an issue with attaching a mock function to another mock 
object.  It looks like this might be a bug in unittest.mock, but it's 
possible I'm misunderstanding or doing something wrong.


I'm currently using Python 3.8.10, which is the default installed on 
Ubuntu 20.04.  I don't have time to install and try other versions right 
now but from a quick look at the source at 
, it 
looks like the same issue would still occur with the latest.  I have a 
workaround, but would still like to know if I'm doing something wrong 
which I can correct, or should report this as a bug.


The class I'm testing (let's call it A) is given an instance of another 
class (let's call it B).  In normal operation, some other code adds a 
callback function as an attribute to B before passing it to A, and A 
calls that callback at certain points.  I agree this isn't particularly 
nice; I didn't write the code and don't have time to sort out all the 
structural issues at the moment.


So, in setting up the mock B, I need to attach a mock callback function 
to it.  I'd like the mock function to raise an exception if it's called 
with the wrong arguments.  I can create such a mock function with, for 
example:

```
mock_callback = mock.create_autospec(lambda a, b, c: None)
```
Calls like mock_callback(1,2,3) or mock_callback(a=1,b=2,c=3) are fine, 
and the test can later check the exact values passed in, while 
mock_callback(1,2) or mock_callback(1,2,3,x=9) raise an exception at the 
time of the call - exactly what I want.


However, when I attach this mock callback to the mock instance of B, the 
reset_mock() method breaks.  For example, using object in place of B, 
since the rest of its implementation is irrelevant to the issue:

```
mock_callback = mock.create_autospec(lambda a, b, c: None)
mock_b = mock.create_autospec(object, spec_set=False, instance=True)
mock_b.attach_mock(mock_callback, 'some_callback')
mock_b.reset_mock()
```

The call to reset_mock() results in:
```
Traceback (most recent call last):
  File "", line 1, in 
  File "/usr/lib/python3.8/unittest/mock.py", line 603, in reset_mock
child.reset_mock(visited)
TypeError: reset_mock() takes 0 positional arguments but 1 was given
```

This seems to occur because, when creating a mock of a function or 
method, create_autospec calls _setup_func (via _set_signature), which 
sets the mock callback's reset_mock method to a function which doesn't 
accept any arguments.  When mock_b.reset_mock() is called, that 
recursively calls reset_mock() on all the child mocks (including the 
mock callback), passing a number of arguments - which causes the above 
exception.


I thought I might be able to just assign the mock callback to an 
attribute of the mock B, rather than using attach_mock, and avoid the 
recursive call to its reset_mock():

```
mock_b.some_callback = mock_callback
```
But mock_b.reset_mock() still raises that exception.  I think some magic 
in the mocks automatically attaches mock_callback as a child of mock_b 
even in that case.


My current workaround is to just use
```
mock_callback = mock.Mock(spec_set=lambda: None)
```
instead.  While using spec_set there prevents incorrect use of things 
like mock_b.some_callback.random_attribute, it doesn't enforce the 
arguments that the function can be called with, even if I do include 
them in the lambda (as in the first example).


Is there something I'm doing wrong here?  Or does this seem like a bug 
in unittest.mock that I should report?  Perhaps this is something that's 
not often done, so the issue hasn't been noticed before.  Trying to 
search for information generally leads back to the unittest.mock 
documentation, and general tutorials on using create_autospec, 
attach_mock, etc. without anything specific about this case.


--
Mark.

--
https://mail.python.org/mailman/listinfo/python-list


Re: f-string syntax deficiency?

2023-06-06 Thread Mark Bourne via Python-list

Roel Schroeven wrote:

Op 6/06/2023 om 16:08 schreef Chris Angelico:

On Wed, 7 Jun 2023 at 00:06, Neal Becker  wrote:
>
> The following f-string does not parse and gives syntax error on 3.11.3:
>
> f'thruput/{"user" if opt.return else "cell"} vs. elevation\n'
>
> However this expression, which is similar does parse correctly:
>
> f'thruput/{"user" if True else "cell"} vs. elevation\n'
>
> I don't see any workaround.  Parenthesizing doesn't help:
>  f'thruput/{"user" if (opt.return) else "cell"} vs. elevation\n'
>
> also gives a syntax error

Is this a problem with the f-string, or with the expression
opt.return? That's a keyword.

'return' being a keyowrd is definitely going to be the problem.

Neal, I assume you're using 'opt.return' also outside of that f-string. 
Does that work? How did you manage to do that? I tried to make a simple 
class with an attribute called 'return', but that already fails with a 
syntax error.


Just for fun, here's a class which has any attribute you like, including 
`return`:

```
class AnyAttr:
def __getattr__(self, name):
return f'This is the value of the <{name}> attribute'
```

The usual "dot" notation to access the attributes works for arbitrary 
attributes, but not for `return` because, as mentioned, that's a keyword:

```
>>> aa = AnyAttr()
>>> aa.myattribute
'This is the value of the  attribute'
>>> aa.random
'This is the value of the  attribute'
>>> aa.return
  File "", line 1
aa.return
   ^
SyntaxError: invalid syntax
```

If you really do have an instance with a `return` attribute (perhaps 
because it does fun things with `__getattr__`), you can access it if 
necessary using `getattr`:

```
>>> getattr(aa, 'return')
'This is the value of the  attribute'
```

You can even access attributes with spaces and other usually-invalid 
characters:

```
>>> getattr(aa, 'This really is an attribute name!')
'This is the value of the  attribute'
```

Using `getattr` to access the value, Neal's f-string can be made to work:
```
>>> f'thruput/{"user" if getattr(aa, "return") else "cell"} vs. 
elevation\n'

'thruput/user vs. elevation\n'
```

But if you have control over the implementation of that `opt` object, 
it's probably better to give the attribute a different name which is a 
valid identifier.  PEP-8 suggests a convention of using a single 
trailing underscore (e.g. `return_`) to avoid conflicts with keywords, 
if there's no better name.


Perhaps `opt` is the arguments returned by `argparse.ArgumentParser` and 
you want the command-line option to be `--return`.  In that case, see 
the `dest` argument to `add_argument()` which can specify a different 
name for the attribute used in code (it's almost like they thought about 
this type of problem ;o)).  If it's from `optparse`, that has a similar 
argument, but `optparse` is deprecated so consider updating to `argparse`.




(Recently there has been an effort to provide clearer and more useful 
error messages; this seems to be a case where there is still room for 
improvement: "SyntaxError: invalid syntax" doesn't immediately remind me 
of that fact that 'return' is a keyword and therefor can't be used as an 
attribute.)


--
Mark.
--
https://mail.python.org/mailman/listinfo/python-list


Re: Question regarding unexpected behavior in using __enter__ method

2023-04-26 Thread Mark Bourne

Lorenzo Catoni wrote:

Dear Python Mailing List members,

I am writing to seek your assistance in understanding an unexpected
behavior that I encountered while using the __enter__ method. I have
provided a code snippet below to illustrate the problem:

```

class X:

... __enter__ = int
... __exit__ = lambda *_: None
...

with X() as x:

... pass
...

x

0
```
As you can see, the __enter__ method does not throw any exceptions and
returns the output of "int()" correctly. However, one would normally expect
the input parameter "self" to be passed to the function.

On the other hand, when I implemented a custom function in place of the
__enter__ method, I encountered the following TypeError:

```

def myint(*a, **kw):

... return int(*a, **kw)
...

class X:

... __enter__ = myint
... __exit__ = lambda *_: None
...

with X() as x:

... pass
...
Traceback (most recent call last):
   File "", line 1, in 
   File "", line 2, in myint
TypeError: int() argument must be a string, a bytes-like object or a real
number, not 'X'
```
Here, the TypeError occurred because "self" was passed as an input
parameter to "myint". Can someone explain why this unexpected behavior
occurs only in the latter case?

I tested this issue on the following Python versions, and the problem
persists on all of them:
- Python 3.8.10 (default, Nov 14 2022, 12:59:47) [GCC 9.4.0] on linux
- Python 3.10.10 (main, Feb  8 2023, 14:50:01) [GCC 9.4.0] on linux
- Python 3.10.7 (tags/v3.10.7:6cc6b13, Sep  5 2022, 14:08:36) [MSC v.1933
64 bit (AMD64)] on win32

I appreciate any input or insights that you might have on this matter.

Thank you for your help in advance!


Aside from other explanations and suggestions, the following definition 
of X also works:


class X:
__enter__ = staticmethod(myint)
__exit__ = lambda *_: None

Wrapping `myint` in a call to `staticmethod` is the same as using 
`@staticmethod` as a decorator on a method within the class, so the 
`self` parameter doesn't get passed.  Equivalent to:


class X:
@staticmethod
def __enter__(*a, **kw):
return int(*a, **kw)
__exit__ = lambda *_: None

Which in turn is just a neater way of doing:

class X:
def __enter__(*a, **kw):
return int(*a, **kw)
__enter__ = staticmethod(__enter__)
__exit__ = lambda *_: None

Those equivalents are a bit pointless, since no arguments will be passed 
into `__enter__` anyway in normal usage, but perhaps make it a bit 
clearer that the second example behaves as would be expected for a 
method of X (where the instance is passed as the first argument), and 
that it's the first example (with `__enter__ = int`) that should be a 
bit more surprising.  (I'm not sure there's much practical use for the 
original `__enter__ = int` either, but presumably that's just used as a 
cut-down demonstration).


--
Mark.
--
https://mail.python.org/mailman/listinfo/python-list


Re: Pycharm IDE

2023-04-19 Thread Mark Bourne

Thomas Passin wrote:

On 4/19/2023 1:27 AM, Kevin M. Wilson via Python-list wrote:
Ok, I got rid of the "print (f'"I am thinking of a number between 1 to 
{LIMIT}\n")"print ("I am thinking of a number between 1 to {LIMIT}\n"),


I think you misunderstand several things at the same time here.

1. These errors originate from syntax errors.  They are basically Python 
errors.  It's possible that behind the scenes, PyCharm is running one or 
another Python program to detect them, but they are errors in your 
Python code.


2. print() doesn't care whether you give it an f-string or not, because 
a f-string is a string too.


3. All strings need to be closed with the same kind of quote they 
started with.  If one is not closed, then Python thinks the string is 
supposed to continue, and - say- the final parenthesis of the print() 
function looks like it is part of the string. So Python (or PyCharm) 
notices that the closing parenthesis of the print() expression is missing.


4. in an f-string, the expression in braces is evaluated and replaced by 
its string value.  So if you try to do this


print('{LIMIT}')

then that will be printed as is with no  substitution - because it is 
not an f-string.  So you will see "{LIMIT}" But you thought you were 
going to see "42" (if LIMIT == 42, that is). OTOH,


print(f'{LIMIT})


^ I think this one should be:

print(f'{LIMIT}')

with the closing quote ;o)

will substitute the string value of LIMIT before printing the string. 
Both are legitimate but the first is not what you seem to want.


5. As I posted earlier, you almost certainly do not need to add the "\n".

So, some suggestions:

- If you are having a problem with some piece of code, try to simplify 
it down to the smallest bit that shows the problem.


- If you are having trouble with f-strings, then think about what you 
want to achieve and look up information about f-strings with that in mind.


- If you are having trouble with the print statement, think what you 
want it to display and look up information about the print function with 
that in mind.


- If your tool - PyCharm in this case - is producing messages but you 
don't understand why they are being produced, try to look up information 
about how and when PyCharm produces error messages


Do you see a pattern here?

Also note that just because you don't see an error message does not mean 
that the code is correct.  It may be correct from the point of view of 
Python syntax but that doesn't mean that it will perform correctly nor 
how you expect.



and Pycharm stopped complaining about it... WHY??
Perplexed
"When you pass through the waters, I will be with you: and when 
you pass through the rivers, they will not sweep over you. When you 
walk through the fire, you will not be burned: the flames will not set 
you ablaze."

Isaiah 43:2

 On Tuesday, April 18, 2023 at 11:17:52 PM MDT, Kevin M. Wilson 
via Python-list  wrote:
  print (f'"I am thinking of a number between 1 to {LIMIT}\n")I had 
the impression that the format specifier 'f' was necessary for the 
print function, but the double quotes are for the string printed to 
the user, as a prompt!The Pycharm IDE is showing that it expects a 
single quotation mark or ')'! No error message is displayed.

Perplexed
"When you pass through the waters, I will be with you: and when 
you pass through the rivers, they will not sweep over you. When you 
walk through the fire, you will not be burned: the flames will not set 
you ablaze."

Isaiah 43:2

     On Tuesday, April 18, 2023 at 06:44:37 PM MDT, aapost 
 wrote:

  On 4/18/23 19:18, Kevin M. Wilson wrote:

Why complain about a 'comma', or a ')'???
       print (f'"I am thinking of a number between 1 to {LIMIT}\n")


my version says it expects ' first (to close the fstring)
then on a new line below it, it mentions the comma and )
I believe that is just showing you after ' it expects you to end the
print with ) as you have
or , to add additional arguments to print



--
https://mail.python.org/mailman/listinfo/python-list


Re: Windows installer from python source code without access to source code

2023-04-07 Thread Mark Bourne

MRAB wrote:

On 2023-04-06 23:14, Jim Schwartz wrote:

    Could someone please help Carlos?  I’m not sure how to answer his
    question

    Sent from my iPhone

  On Apr 6, 2023, at 3:53 PM, Carlos Fulqueris 
 wrote:


  
  Hello Jim,
  How can I unsubscribe to this email list?
  I'm waiting for your response.
  Thanks
  Carlos

[snip]
At the bottom of the post is a link to the page that explains how to 
unsubscribe. It's the link:



    https://mail.python.org/mailman/listinfo/python-list


I read this list via the newsgroup, so don't see those links.  However, 
I've always thought those Mailman pages are confusing for anyone not 
already familiar when it comes to subscribing.  The option to 
unsubscribe is right at the bottom of the page under the "Python-list 
Subscribers" section, which looks like it's only for list 
administrators!  Ignore the admin address and password boxes, just fill 
in your email address in the box below those and click "Unsubscribe or 
edit options".


--
Mark.
--
https://mail.python.org/mailman/listinfo/python-list


Re: Line continuation and comments

2023-02-24 Thread Mark Bourne
Personally, I don't particularly like the way you have to put multiline 
strings on the far left (rather than aligned with the rest of the scope) 
to avoid getting spaces at the beginning of each line.  I find it makes 
it more difficult to see where the scope of the class/method/etc. 
actually ends, especially if there are multiple such strings.  It's not 
too bad for strings defined at the module level (outer scope) though, 
and of course for docstrings the extra spaces at the beginning of each 
line don't matter.


However, rather than using "+" to join strings as in your examples 
(which, as you suggest, is probably less efficient), I tend to use 
string literal concatenation which I gather is more efficient (treated 
as a single string at compile-time rather than joining separate strings 
at run-time).  See 
.


For example:
  HelpText = ("Left click: Open spam\n"
  "Shift + Left click: Cook spam\n"
  "Right click:Crack egg\n"
  "Shift + Right click:Fry egg\n")

The downside is having to put an explicit "\n" at the end of each line, 
but to me that's not as bad as having to align the content to the far left.


Getting a bit more on topic, use of backslashes in strings is a bit 
different to backslashes for line continuation anyway.  You could almost 
think of "\
(newline)" in a multiline string as being like an escape sequence 
meaning "don't actually put a newline character in the string here", in 
a similar way to "\n" meaning "put a newline character here" and "\t" 
meaning "put a tab character here".


Mark.


avi.e.gr...@gmail.com wrote:

Good example, Rob, of how some people make what I consider RELIGIOUS edicts 
that one can easily violate if one wishes and it makes lots of sense in your 
example.

Let me extend that. The goal was to store a character string consisting of 
multiple lines when printed that are all left-aligned. Had you written:

  HelpText = """
Left click: Open spam
...
Shift + Right click:Fry egg
"""
Then it would begin with an extra carriage return you did not want. Your 
example also ends with a carriage return because you closed the quotes on 
another line, so a \ on the last line of text (or moving the quotes to the end 
of the line) would be a way of avoiding that.

Consider some alternatives I have seen that are in a sense ugly and may involve 
extra work for the interpreter unless it is byte compiled once.

def someFunc():
  HelpText =
  "Left click: Open spam" + "\n" +
  "Shift + Left click: Cook spam" + "\n" +
  ...

Or the variant of:
HelpText =  "Left click: Open spam\n"
HelpText +=  " Shift + Left click: Cook spam\n"
...

Or perhaps just dumping the multi-line text into a file beforehand and reading 
that into a string!

def someFunc():

The backslash is not looking like such a bad idea! LOL!

-Original Message-
From: Python-list  On 
Behalf Of Rob Cliffe via Python-list
Sent: Wednesday, February 22, 2023 2:08 PM
To: python-list@python.org
Subject: Re: Line continuation and comments



On 22/02/2023 15:23, Paul Bryan wrote:

Adding to this, there should be no reason now in recent versions of
Python to ever use line continuation. Black goes so far as to state
"backslashes are bad and should never be used":

https://black.readthedocs.io/en/stable/the_black_code_style/future_sty
le.html#using-backslashes-for-with-statements


def someFunc():
  HelpText = """\
Left click: Open spam
Shift + Left click: Cook spam
Right click:Crack egg
Shift + Right click:Fry egg
"""

The initial backslash aligns the first line with the others (in a fixed font of 
course).
Best wishes
Rob Cliffe
--
https://mail.python.org/mailman/listinfo/python-list


--
https://mail.python.org/mailman/listinfo/python-list


Re: Add angle brackets for required args in argparse

2023-02-20 Thread Mark Bourne

scruel tao wrote:

If we have the following code:
```
parser = argparse.ArgumentParser(description="test")
parser.add_argument('path')
```

Run it without args, will get error message:
```
usage: test.py [-h] path
test.py: error: the following arguments are required: path
```

However, I hope the message can be as the following:
```
usage: test.py [-h] 
test.py: error: the following arguments are required: path
```


The `metavar` argument to `add_argument` can be used to control how an 
argument is represented in the usage text:

```
import argparse
parser = argparse.ArgumentParser(description='test')
parser.add_argument('path', metavar='')
parser.parse_args()
```

Which results in:
```
usage: test.py [-h] 
test.py: error: the following arguments are required: 
```


Or might can consider to provide a way to let user have there own style, like:
```
usage: test.py [-h] path
```


It's also possible to create a custom help formatter, overriding 
appropriate methods to control the formatting.  For example:

```
import argparse

class CustomHelpFormatter(argparse.HelpFormatter):
def _get_default_metavar_for_positional(self, action):
default = super()._get_default_metavar_for_positional(action)
return f'<{default}>'

parser = argparse.ArgumentParser(
description='test',
formatter_class=CustomHelpFormatter)
parser.add_argument('path')
parser.parse_args()
```

Which results in:
```
usage: test.py [-h] 
test.py: error: the following arguments are required: path
```

That's a bit closer to what you asked for, since the required argument 
shown in the error message doesn't include the angle brackets.  It also 
avoids needing to specify a `metavar` for every positional argument. 
However, it is overriding a non-public method of the `HelpFormatter` 
class, so might not work across all Python versions if the name or 
signature of that method changes (even if it does work with all current 
versions, it might break in future).


--
Mark.
--
https://mail.python.org/mailman/listinfo/python-list


Re: ChatGPT Generated news poster code

2023-02-13 Thread Mark Bourne

Mats Wichmann wrote:
Meanwhile, I'm still wondering why I need a program to "chat" to the 
GUID Partition Table


Perhaps to keep on good terms with it so that it doesn't run away and 
hide?  I had to go looking for the GPT on one of my disks after it went 
AWOL a couple of years ago.  Eventually found it hiding in another sector.


(Turns out the USB-SATA adapter I previously used to format the disk 
misreports the logical block size for some disks.  Subsequently 
connecting the disk directly to a SATA port, the GPT was no longer found 
at logical block 1 where it should have been, because the block size was 
reported differently.)


--
Mark.
--
https://mail.python.org/mailman/listinfo/python-list


Re: A Function's name during its definition

2023-02-07 Thread Mark Bourne

Stefan Ram wrote:

Mark Bourne  writes:

In the second case, eval() only gets the globals and immediate locals,


   Yes, I think you are right. Curiously, the following program would
   mislead one to thing that eval /does/ see the intermediate names:

   main.py

def f():
 x = 22
 def g():
 print( x )
 print( eval( 'x' ))
 g()
f()

   output

22
22

   . But "print( x )" had the effect of making that x local.
   Without it, we see the error:

   main.py

def f():
 x = 22
 def g():
 print( eval( 'x' ))
 g()
f()

   error output

NameError: name 'x' is not defined


That is interesting.  I know assigning to a value creates a local 
version (and the non-local then can't be accessed, even before the new 
value was assigned), but hadn't realised just referencing it brought it 
into local scope for reading.  I guess that's to do with closures, where 
any variables referenced within the function get bound to the function's 
scope.  e.g. if g() includes a reference to x [as it does in the first 
example above], and f() returned a reference to g(), calling g() later 
[from outside of f()] by using that reference would still be able to 
access x.


With just the eval() call and no other reference to x, the interpreter 
doesn't know at that time the closure is created that there is a 
reference to x.  At that point, the 'x' is just text in a string.  It's 
only when eval() gets called that it tries to execute that string as 
Python code - and finds that x isn't in scope.


I'm not all that familiar with the concept of closures, so may have got 
some of the terminology and details wrong - but it's something along 
those lines.  I'm sure others will correct me...


--
Mark.
--
https://mail.python.org/mailman/listinfo/python-list


Re: A Function's name during its definition

2023-02-07 Thread Mark Bourne

Stefan Ram wrote:

   When one defines a function, sometimes its name is only
   half-existent.

   One can implicitly evaluate the name of the function:

   main.py

def g():
 def f():
print( f )
 f()
g()

   output

.f at ...

   , but one gets an error when one tries to evaluate it explicitly:

   main.py

def g():
 def f():
print( eval( 'f' ))
 f()
g()

   error output

NameError: name 'f' is not defined


I'm guessing that's because the name "f", referencing that function, is 
in the local scope of the "g" function.


In the first version, the interpreter searches the scope of the code 
which calls "print(f)".  It doesn't find anything named "f" there, so 
searches the next scope out, that of the function identified by "g". 
That contains "f", so it's found and can be printed.


In the second case, eval() only gets the globals and immediate locals, 
in this case the locals of "f".  The name "f" doesn't exist in the local 
scope of "f", and it doesn't exist in the global scope.  So the code 
executed by eval() can't see it.


The following does work:

def g():
def f():
   print(eval('g'))
f()
g()

...because in this case "g" is defined in the global scope, so the code 
in the eval call can see it.


The following also works:

def g():
def f():
pass
print(eval('f'))
g()

...because in this case, eval() is called from within the scope of "g", 
so it can see the function "f" defined in that scope.



--
Mark.
--
https://mail.python.org/mailman/listinfo/python-list


Re: evaluation question

2023-02-02 Thread Mark Bourne

mutt...@dastardlyhq.com wrote:

On Wed, 1 Feb 2023 18:28:04 +0100
"Peter J. Holzer"  wrote:

--b2nljkb3mdefsdhx
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline
Content-Transfer-Encoding: quoted-printable

On 2023-02-01 09:00:39 -, mutt...@dastardlyhq.com wrote:

Its not evolution, its revolution. Evolution retains old functionality.


Tell a penguin that it can fly :-)


Yeah ok :) But the ancestors of penguins didn't wake up one morning, flap
their wings and fall out the tree, it happened gradually. Python2 syntax
could have been retained for X versions of 3 just as C++ keeps old stuff
until its eventually deprecated them removed.


Python 2 *was* retained for X versions of Python 3.  From a quick check, 
Python 3.0 was released in December 2008 and Python 2 support ended in 
January 2020 - by which time Python 3 was up to 3.8 as ChrisA mentioned. 
 That's about an 11 year transition period, which is hardly sudden! 
Python 3 *was* the point at which the features deprecated in Python 2 
were removed.


The problem is, a lot seemed to ignore Python 3 for the first 12 years 
and then suddenly panic because Python 2 support had ended.


--
Mark.
--
https://mail.python.org/mailman/listinfo/python-list


Re: evaluation question

2023-01-31 Thread Mark Bourne

Greg Ewing wrote:

On 30/01/23 10:41 pm, mutt...@dastardlyhq.com wrote:

What was the point of the upheaval of converting
the print command in python 2 into a function in python 3 if as a 
function

print() doesn't return anything useful?


It was made a function because there's no good reason for it
to have special syntax in the language.


I think I saw somewhere that making print a function also had something 
to do with being able to add extra keyword arguments like sep and end. 
The syntax for printing to a specific file already seemed a bit odd with 
the print statement, and adding extra arguments would have made it even 
more clunky (yeah, I know ">>" is similar to C++ streams, but it looks 
out of place in Python).


They couldn't fully make the change from print statement to print 
function without breaking backward compatibility for existing code.  But 
there were other breaking changes being made in Python 3 anyway, so may 
as well sort print out while at it and have all the breaking changes at 
once.



Functions don't need to return things to justify their existence,
and in fact the usual convention is that functions whose purpose
is to have an effect just return None.


--
Mark.
--
https://mail.python.org/mailman/listinfo/python-list


Re: How to make argparse accept "-4^2+5.3*abs(-2-1)/2" string argument?

2023-01-29 Thread Mark Bourne

Jach Feng wrote:

Thank you for detail explanation of the role the shell is involved in this 
problem. I'm very appreciated!

It seems that a CLI app may become very complex when dealing with different 
kind of shell, and may not be possible to solve its problem. But the good thing 
in my app is that I need only to handle math equation:-)


If you want to try to tell the user how to deal with their shell's 
requirements for quoting arguments, regardless of which shell they might 
be using, yes, that explanation would become very complicated.  It 
doesn't affect the rest of the implementation of the application though 
- the user just needs to know how to use their shell to pass the 
arguments they want into the application.  That's really something they 
should look up in their shell's documentation, rather than something 
your application should attempt to document.


Since your application requires equations to be passed in, and they're 
quite likely to include characters handled specially by the shell 
(space, "*" and "^" have already come up in this thread, but there may 
be others), it may be worth highlighting that, but directing the user to 
consult the documentation for their shell rather than assuming a 
particular shell and attempting to cover all its features and limitations.



So why so much objection to explaining the need for "--"?

Because of using " to enclose a space separated string is a common convention, and adding 
a "--" is not:-)


If you don't consider use of "--" to be a common convention, that seems 
even more reason to mention it in your application's documentation.


Telling the user to use quotes around an argument containing spaces is 
nothing to do with your application, and might not even be applicable if 
they use a different shell to call your application.  In most shells 
I've come across, there are also various other characters that need 
special handling (either quoting or escaping) - but exactly which 
characters again depends on the shell.  Yet you seem quite happy to 
document that one particular case in your usage information.


Using "--" is also a common convention (as I and others have mentioned), 
although perhaps not as common in Windows (where it's more common to use 
"/" rather than "-" for options anyway).  But more to the point, it is a 
feature that is implemented under your application's control (if you 
don't want this feature, don't use argparse).  Use of "--" is applicable 
regardless of which shell your user calls it from, and other 
applications might not use that convention even if called from the same 
shell, so it seems *more* in scope for your application to document than 
using quotes around spaces.


--
Mark.
--
https://mail.python.org/mailman/listinfo/python-list


Re: How to make argparse accept "-4^2+5.3*abs(-2-1)/2" string argument?

2023-01-29 Thread Mark Bourne

Jach Feng wrote:

Jach Feng 在 2023年1月22日 星期日上午11:11:22 [UTC+8] 的信中寫道:

Fail on command line,

e:\Works\Python>py infix2postfix.py "-4^2+5.3*abs(-2-1)/2"
usage: infix2postfix.py [-h] [infix]
infix2postfix.py: error: unrecognized arguments: -4^2+5.3*abs(-2-1)/2

Also fail in REPL,

e:\Works\Python>py
Python 3.8.8 (tags/v3.8.8:024d805, Feb 19 2021, 13:08:11) [MSC v.1928 32 bit 
(Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.

import argparse
parser = argparse.ArgumentParser(description='Convert infix notation to 
postfix')
parser.parse_args("-4^2+5.3*abs(-2-1)/2")

usage: [-h]
: error: unrecognized arguments: - 4 ^ 2 + 5 . 3 * a b s ( - 2 - 1 ) / 2

Just can't figure out where is wrong!?

--Jach

OK, I take a quick try to use argv directly. I write the code in a way even get 
rid of the -h option.

import sys
if len(sys.argv) == 1:
infix = " "
print("Usage: a math equation must follow!")
else:
infix = "".join(sys.argv[1:])

Simple enough, right? But unfortunately it didn't survive. The ^ symbol was 
lost!

e:\Works\Python>py infix2postfix.py
Usage: a math equation must follow!

e:\Works\Python>py infix2postfix.py -4^2 +5.3  *  abs(-2-1)/2
-42 5.3 -2 1 - abs * 2 / +

Hmm...


I'm not certain, but I think that's a shell feature again.  In Windows' 
command prompt, "^" can be used at the end of a line to indicate that 
the command continues on the next line.  I'm not sure what happens if 
it's in the middle of a line (and not on Windows to be able to check), 
but it's quite possible that it just gets ignored.


Enclosing your argument in quotes might help.  Again, this is a feature 
of the shell, not your application, and other shells might behave 
differently.  You probably don't want to go down the line of trying to 
document this kind of thing in your applications usage information, 
because it won't work like that for someone using e.g. the bash shell 
(which can be run on Windows).


--
Mark.
--
https://mail.python.org/mailman/listinfo/python-list


Re: evaluation question

2023-01-28 Thread Mark Bourne

mutt...@dastardlyhq.com wrote:

On Sat, 28 Jan 2023 14:22:01 +1300
dn  wrote:

Do you know about the Python REPL?


Haven't learnt the acronyms yet.


REPL stands for "Read Evaluate Print Loop".  It basically refers to the 
interactive interpreter, which reads input you type, evaluates it, 
prints the result, and loops (repeatedly does that).


An interesting point from your examples is that the output from the 
first two comes from different steps in that loop.


>>> eval("1+1")
2

Here, the E (evaluation) step runs eval("1+1"), which returns 2.  The P 
(print) step then prints that result.  If this was in a script, you 
wouldn't see any output, and the statement is pretty much useless - 
you'd need to assign the result to a variable or explicitly print it.


>>> eval("print(123)")
123

Here, the E step runs eval("print(123)"), which prints 123 and returns 
None.  The P step doesn't print anything if the result is None.  You'd 
still see that output if this was in a script.


Using eval in those examples is pretty pointless, since:
>>> 1+1
>>> print(123)
would produce the same results - but of course they were just simple 
examples.


--
Mark.
--
https://mail.python.org/mailman/listinfo/python-list


Re: How to make argparse accept "-4^2+5.3*abs(-2-1)/2" string argument?

2023-01-28 Thread Mark Bourne

Jach Feng wrote:

Jach Feng 在 2023年1月22日 星期日上午11:11:22 [UTC+8] 的信中寫道:

Fail on command line,

e:\Works\Python>py infix2postfix.py "-4^2+5.3*abs(-2-1)/2"
usage: infix2postfix.py [-h] [infix]
infix2postfix.py: error: unrecognized arguments: -4^2+5.3*abs(-2-1)/2

Also fail in REPL,

e:\Works\Python>py
Python 3.8.8 (tags/v3.8.8:024d805, Feb 19 2021, 13:08:11) [MSC v.1928 32 bit 
(Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.

import argparse
parser = argparse.ArgumentParser(description='Convert infix notation to 
postfix')
parser.parse_args("-4^2+5.3*abs(-2-1)/2")

usage: [-h]
: error: unrecognized arguments: - 4 ^ 2 + 5 . 3 * a b s ( - 2 - 1 ) / 2

Just can't figure out where is wrong!?

--Jach

I have to admit that I don't know the background upon which the argparse was 
built. The good side is that I don't carry its historical knowledge ( or 
burden?), that's why I can use it in a new way which may make someone feel 
uneasy.


If you can use it in the way you want, that's great.  I thought you were 
asking here because you *couldn't* use it the way you want.


You're writing a command-line application.  Your users are either 
already familiar with the conventions of command-line applications, or 
they'll soon need to be.


If your application supports options beginning with a "-" (which is what 
argparse gives you, even if only the default "-h" and "--help" options 
are actually valid), and you also need to be able to pass positional 
arguments which begin with a "-", you need some way for the user to 
indicate whether any particular argument is an option or positional. 
Using "--" to indicate that all subsequent arguments are positional, not 
options, is a common convention.


Some of your users might already be familiar with that convention from 
other command-line tools.  By using the same convention, you'd be making 
it easier for them to use yours.  Others might not already be familiar 
with the convention, but by being consistent with other tools you'd 
still be helping them when they eventually do come across it elsewhere. 
You can always include an explanation of using "--" in your usage output 
or other documentation.


Apart from dealing with how to pass an argument beginning with "-", your 
users will also likely have to deal with the features of whichever shell 
they're using, which you have no control over.  For example, it's quite 
common to need to enclose an argument in quotes if it contains spaces. 
It may also be necessary to use quotes if certain special characters are 
used, such as "*", "?" or "$" (perhaps "%" on Windows).



The reason I am still keep on using argparse on this "one positional argument 
only" CLI app is that I can't see what advantage I can get when writing code to 
handling sys.argv directly for the following two situations,
-
e:\Works\Python>py infix2postfix.py
usage: infix2postfix.py [-h] infix
infix2postfix.py: error: the following arguments are required: infix

e:\Works\Python>py infix2postfix.py -h
usage: infix2postfix.py [-h] infix

Convert infix notation to postfix

positional arguments:
   infix   Put equations in quote if there is space in it and separate each 
one with a comma, ie.
   "-4^2+5.3*abs(-2-1)/2, abs(Abc)*(B+C)/D, (-3) * sqrt(1-(x1/7)*(y1/7)) 
* sqrt(abs((x0-4.5)/(y0-4)))"

optional arguments:
   -h, --help  show this help message and exit
-

comparing with code using the argparse below,

import argparse
sample = "-4^2+5.3*abs(-2-1)/2, abs(Abc)*(B+C)/D, (-3) * sqrt(1-(x1/7)*(y1/7)) * 
sqrt(abs((x0-4.5)/(y0-4)))"
parser = argparse.ArgumentParser(description='Convert infix notation to 
postfix')
parser.add_argument('infix',
help='Put equations in quote if there is space in it and separate each one with a comma, 
ie. "{}"'.format(sample))

import sys
if len(sys.argv) > 1 and not '-h' in sys.argv:
sys.argv.insert(1, '--')
args = parser.parse_args()


Personally, I do quite often use argparse even for simple cases, partly 
because it produces nice usage details, and deals with wrapping the 
output to whatever width the console is.  But I work with it rather than 
against it, and accept that a "--" is needed if a positional argument 
starts with a "-".


I notice you explain the need to enclose the equation in quotes if it 
contains spaces.  That's not even a feature of your application, but of 
the shell used to call it.  So why so much objection to explaining the 
need for "--"?


Depending on the shell, there are other cases where quoting might be 
needed, e.g. if the equation includes a "*" or "?" and happens to look 
like a pattern matching files in the current directory (most shells I've 
used pass the pattern unchanged if it doesn't match any files).  In 
bash, if a "$" is used I'd need to enclose that in 'single quotes' 
(can't even use "double quotes" for that one).  You can't really expect 
to document all that sort of thing, because it depends on which shell 
the user 

Re: bool and int

2023-01-27 Thread Mark Bourne

avi.e.gr...@gmail.com wrote:
...

Interestingly, I wonder if anyone has
designed an alternate object type that can be used mostly in place of
Booleans but which imposes changes and restrictions so trying to add a
Boolean to an integer, or vice versa, results in an error. Python is
flexible enough to do that and perhaps there already is a module out there


Not exactly what you describe, but I did once write a subclass of int 
for use in an SNMP interface, where true is represented as 1 and false 
as 2.  I don't recall the exact details offhand, but it involved 
overriding __bool__ so that a value of 1 returned True and anything else 
False.  The way it was used should have ensured that it only ever had 
the value 1 or 2, but doing it this way round (rather than e.g. 2 being 
False and anything else True) meant that if it somehow got the value 0, 
that would also be treated as False.  I think it also overrode __init__ 
(or perhaps __new__) to covert a bool True or False to 1 or 2 (rather 
than 1 or 0) for its own value, so it could be initialised from either 
an int or a bool and correctly converted in either direction via int() 
or bool().


So Python is even flexible enough to be made to deal with insane 
situations where False is 2!


--
Mark.
--
https://mail.python.org/mailman/listinfo/python-list


Re: How to make argparse accept "-4^2+5.3*abs(-2-1)/2" string argument?

2023-01-22 Thread Mark Bourne

Jach Feng wrote:

Fail on command line,

e:\Works\Python>py infix2postfix.py "-4^2+5.3*abs(-2-1)/2"
usage: infix2postfix.py [-h] [infix]
infix2postfix.py: error: unrecognized arguments: -4^2+5.3*abs(-2-1)/2

Also fail in REPL,

e:\Works\Python>py
Python 3.8.8 (tags/v3.8.8:024d805, Feb 19 2021, 13:08:11) [MSC v.1928 32 bit 
(Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.

import argparse
parser = argparse.ArgumentParser(description='Convert infix notation to 
postfix')
parser.parse_args("-4^2+5.3*abs(-2-1)/2")

usage: [-h]
: error: unrecognized arguments: - 4 ^ 2 + 5 . 3 * a b s ( - 2 - 1 ) / 2

Just can't figure out where is wrong!?


First, you need to add an argument to the parser, so that it expects an 
argument:

>>> parser.add_argument("expression")

Secondly, `parse_args` expects a list of arguments.  By passing a 
string, it interprets each character as a separate argument (since 
iterating over a string yields each character in turn).  Here, I 
intentionally leave off the initial hyphen because that's the next problem:

>>> parser.parse_args(["4^2+5.3*abs(-2-1)/2"])
Namespace(expression='4^2+5.3*abs(-2-1)/2')

Thirdly, an initial hyphen indicates an optional argument so, for 
example if you pass "-l" it will expect a "-l" argument to be defined as 
one of the valid options, and also complain that you haven't specified 
the required expression:

>>> parser.parse_args(["-4^2+5.3*abs(-2-1)/2"])
usage: [-h] expression
: error: the following arguments are required: expression

If you need to pass a value starting with a "-" there are a couple of 
options...


Perhaps it would be acceptable to represent it as "0-...":
>>> parser.parse_args(["0-4^2+5.3*abs(-2-1)/2"])
Namespace(expression='0-4^2+5.3*abs(-2-1)/2')

While mathematically equivalent, that might have different meaning for 
whatever you're trying to do.  Alternatively, a double hyphen indicates 
that there are no more options and that anything else is positional 
arguments even if they begin with a hyphen:

>>> parser.parse_args(["--", "-4^2+5.3*abs(-2-1)/2"])
Namespace(expression='-4^2+5.3*abs(-2-1)/2')

You wouldn't usually explicitly pass a list of arguments to `parse_args` 
like that, but it can be useful for testing and experimentation. 
Usually, you'd call `parse_args()` without any arguments, and it would 
parse the arguments passed on the command-line when calling your script. 
 e.g. you'd call (from a Windows command prompt / Linux shell / etc.):

> ./convert_infix.py -- '-4^2+5.3*abs(-2-1)/2'
(it's probably a good idea to quote the expression, in case it includes 
any characters which would be interpreted specially by the shell - e.g. 
"*" without quotes usually expands to all matching files in the current 
directory)


--
Mark.
--
https://mail.python.org/mailman/listinfo/python-list


Re: To clarify how Python handles two equal objects

2023-01-15 Thread Mark Bourne

Jen Kris wrote:

Avi,

Your comments go farther afield than my original question, but you made some 
interesting additional points.  For example, I sometimes work with the C API 
and sys.getrefcount may be helpful in deciding when to INCREF and DECREF.  But 
that’s another issue.

The situation I described in my original post is limited to a case such as x = y where both "x" and "y" are arrays – whether they are lists in Python, or from 
the array module – and the question in a compiled C extension is whether the assignment can be done simply by "x" taking the pointer to "y" rather than moving 
all the data from "y" into the memory buffer for "x" which, for a wide array, would be much more time consuming than just moving a pointer.  The other 
advantage to doing it that way is if, as in my case, we perform a math operation on any element in "x" then Python expects that the same change to be reflected in 
"y."  If I don’t use the same pointers then I would have to perform that operation twice – once for "x" and once  for "y" – in addition to the 
expense of moving all the data.

The answers I got from this post confirmed that it I can use the pointer if "y" is not re-defined 
to something else during the lifespan of "x."  If it is then "x" has to be restored to 
its original pointer.  I did it that way, and helpfully the compiler did not overrule me.


I haven't done much with C extensions, but I don't think you'd need to 
do anything with "x" in that case.  If something else is assigned to 
"y", "x" would still be a reference to the original object - why would 
it need to be "restored" to anything?  Unless I've misunderstood what's 
going on here...


--
Mark.
--
https://mail.python.org/mailman/listinfo/python-list


Re: How make your module substitute a python stdlib module.

2022-12-27 Thread Mark Bourne

Antoon Pardon wrote:



Op 27/12/2022 om 11:37 schreef Chris Angelico:

On Tue, 27 Dec 2022 at 21:29, Antoon Pardon  wrote:

OK, I am writing an alternative for the threading module. What I would
like to know is how I can get some library modules call my alternative
instead of the threading module.

For instance there is the logging module, it can log the thread name. So
I would like to know how I can get the logging module to call the
function from my module to get the current_thread, instead of it calling
"current_thread" from the threading module.

Easy: make sure your module is called "threading.py" and is earlier in
the path than the standard library. In fact, it's so easy that people
do it unintentionally all the time... Generally, the current directory
(or the script directory) is the first entry in sys.path, so that's a
good place to put it.


Well I had hope for a somewhat more selective solution. The intention is
to have a number of modules collected in a package where this module is
one of and the package is available via the "installed" search path.

So the programmer should just be able to choose to write his code with
either:

     from threading import Thread

or

     from QYZlib.threaders import Thread 


In that case, it sounds like you don't want to always replace the 
logging module's behaviour, since code might use the standard threading 
module.  You might also need to consider that a mix of both the standard 
threading module and yours might be used (particularly if using other 
libraries which create their own threads using the standard module).


It might be better for your module to provide a custom `logging.Filter` 
subclass, which replaces the `thread` and `threadName` attributes of the 
`LogRecord` with your threading module's idea of the thread ID.  Then 
applications using your threading module would configure the `logging` 
module to use your filter, while code using the standard threading 
module can continue as normal.  Despite the name, logging filters can 
also modify log records, not just filter whether or not they're logged.


It might even be desirable to leave the original `thread` and 
`threadName` attributes unchanged and add new attributes to the 
`LogRecord` for your module's idea of threads (e.g. `myThread` and 
`myThreadName`).  I think using `%(myThread)d` and `%(myThreadName)s` in 
the log format string would use those attributes, without needing a 
custom formatter.  That would allow both thread IDs to be logged, in 
case a mix of standard threads and your threads is used.


--
Mark.
--
https://mail.python.org/mailman/listinfo/python-list


Re: How to enter escape character in a positional string argument from the command line?

2022-12-19 Thread Mark Bourne

Jach Feng wrote:

I have a script using the argparse module. I want to enter the string 
"step\x0A" as one of its positional arguments. I expect this string has a 
length of 5, but it gives 8. Obviously the escape character didn't function correctly. 
How to do it?


That depends on the command-line shell you're calling your script from.

In bash, you can include a newline in a quoted string:
./your_script 'step
'
(the closing quote is on the next line)

Or if you want to do it on a single line (or use other escape 
sequences), you can use e.g.:

./your_script $'step\x0a'
(dollar sign before a single-quoted string which contains escape sequences)

--
Mark.
--
https://mail.python.org/mailman/listinfo/python-list


Re: How to make a variable's late binding crosses the module boundary?

2022-08-31 Thread Mark Bourne

Jach Feng wrote:

Mark Bourne 在 2022年8月29日 星期一下午6:40:59 [UTC+8] 的信中寫道:

Jach Feng wrote:

Chris Angelico 在 2022年8月29日 星期一下午1:58:58 [UTC+8] 的信中寫道:

On Mon, 29 Aug 2022 at 15:54, Jach Feng  wrote:


Richard Damon 在 2022年8月29日 星期一上午10:47:08 [UTC+8] 的信中寫道:

On 8/27/22 7:42 AM, Mark Bourne wrote:

Jach Feng wrote:

I have two files: test.py and test2.py
--test.py--
x = 2
def foo():
print(x)
foo()

x = 3
foo()

--test2.py--
from test import *
x = 4
foo()

-
Run test.py under Winows8.1, I get the expected result:
e:\MyDocument>py test.py
2
3

But when run test2.py, the result is not my expected 2,3,4:-(
e:\MyDocument>py test2.py
2
3
3

What to do?


`from test import *` does not link the names in `test2` to those in
`test`. It just binds objects bound to names in `test` to the same
names in `test2`. A bit like doing:

import test
x = test.x
foo = test.foo
del test

Subsequently assigning a different object to `x` in one module does
not affect the object assigned to `x` in the other module. So `x = 4`
in `test2.py` does not affect the object assigned to `x` in `test.py`
- that's still `3`. If you want to do that, you need to import `test`
and assign to `test.x`, for example:

import test
test.x = 4
test.foo()


Yes, fundamental issue is that the statement

from x import y

makes a binding in this module to the object CURRECTLY bound to x.y to
the name y, but if x.y gets rebound, this module does not track the changes.

You can mutate the object x.y and see the changes, but not rebind it.

If you need to see rebindings, you can't use the "from x import y" form,
or at a minimum do it as:


import x

from x import y

then later to get rebindings to x.y do a

y = x.y

to rebind to the current x.y object.

--
Richard Damon

Yes, an extra "import x" will solve my problem too! Sometimes I am wondering why 
"from x import y" hides x? hum...can't figure out the reason:-)


"from x import y" doesn't hide x - it just grabs y. Python does what
you tell it to. :)

ChrisA

But I had heard people say that "from x import y" did import the whole x module into memory, just 
as "import x" did, not "grabs y" only. Is this correct?

`from x import y` does import the whole module x into memory, and adds
it to `sys.modules`. But it only binds the name `y` in the namespace of
module doing the import (and it binds it to the value of `x.y` at the
time the import is done - it doesn't magically keep them in sync if one
or the other is later reassigned).

The point about the whole module being imported is that you don't save
any memory by using `from x import y` to avoid importing some very large
object `z` from `x`. Those other large objects might be needed by
functions which have been imported (e.g. your `foo` function still needs
`x` even if you haven't imported `x` - so it still needs to be loaded
into memory) or might be imported and used by other modules importing
`x`, so they still have to be loaded when any part of `x` is imported -
they just don't have to be bound to names in the importing module's
namespace.

As Richard mentioned, if `x.y` is a mutable object (such as a list) you
can still mutate that object (e.g. add/remove items) and those changes
will be seen in both modules. That's because both are still bound to
the same object and you're mutating that existing object. If you assign
a new list to either, that won't be seen by the other.

--
Mark.

When using dot notation to change variable, no matter if 'x.y' is a mutable or 
immutable object, the change
will be seen in both modules except those early bindings.

--Jach


Yes, sorry, I'd used `x.y` as a way of referring to the variable `y` in 
module `x` as opposed to `y` in the current module.  It doesn't help 
that I added the second paragraph and didn't notice that the third was 
then out of context.


If you use `import x` and assign to `x.y`, that will as you say be seen 
in both modules.  On the other hand, if you use `from x import y`, then 
(as has been discussed) assigning to `y` in the module which has the 
import won't affect the value seen in module `x`.  However, if `y` is 
mutable (e.g. a list), and no new object is assigned to it, then `y` 
still points to the same object in both modules, so mutating that 
existing object (e.g. `y.append(123)`) *will* affect what's seen in both 
modules - they're both referencing the same object, and you've modified 
that object, as opposed to assigning a new object to `y` in one of the 
modules.


--
Mark.
--
https://mail.python.org/mailman/listinfo/python-list


Re: What can I do about this?

2022-08-29 Thread Mark Bourne

gene heskett wrote:

On 8/29/22 12:50, Mark Bourne wrote:

Roel Schroeven wrote:

Op 29/08/2022 om 2:55 schreef gene heskett:

On 8/28/22 19:39, Peter J. Holzer wrote:

On 2022-08-28 18:40:17 -0400, gene heskett wrote:
Persuant to my claim the py3.10 is busted, here is a sample. This 
is me,

trying to make
pronterface, inside a venv: When the package manager version will 
only run

the gui-less "pronsole"
but nothing else from that all python kit runs as it should or at 
all.
 From the package-managers install in 
/usr/share/doc/printrun-common/ I

copied requirements.txt
into the venv, and ran this command line:

gene@rock64:~/venv$ pip3 install -r requirements.txt

You are almost certainly *not* in a venv here. First, your prompt
doesn't show the name of the venv,
I've created that several times, as octoprint won''t run without it 
either.
I found a way to autostart it on reboots and octoprint seems happy 
with it
I agree with Peter: it doesn't look as if you are invoking the pip3 
in the venv. Just making the venv-directory the current directory 
doesn't activate it.


As a diagnostic, ask the OS which pip3 is actually used:

$ type -a pip3

Does that show the pip3 installed in the venv? Or the system-wide 
one? If it's not the pip3 in the venv, well, then that's the problem 
(or at least part of the problem). Solution: first check whether the 
venv really contains 'pip3' (as opposed to eg. just 'pip'): list the 
contents of the bin subdirectory of the venv. If not, use 'pip' or 
whatever instead. Then to make sure you use the one in the venv, 
either activate the venv or explicitly specify the path when invoking 
pip/pip3 (and likewise for python/python3).


So either (assuming you're using bash):

$ source {path_to_venv}/bin/pip3  # activate the venv


I think this first line should probably be:

$ source {path_to_venv}/bin/activate  # activate the venv

i.e. with `activate` rather than `pip3`?


$ type -a pip3  # check whether now the correct pip3 is used
$ pip3 install -r requirements.txt  # finally invoke pip3

or:

$ {path_to_venv}/bin/pip3 install -r requirements.txt

That got me to showstopper #2: (lengthy warniing)

(venv) gene@rock64:~/printrun/Printrun$ ./venv/bin/pip3 install -r 
requirements.txt
Ignoring pyobjc-framework-Cocoa: markers 'sys_platform == "darwin"' 
don't match your environment
Ignoring pyreadline: markers 'sys_platform == "win32"' don't match your 
environment
Requirement already satisfied: pyserial>=3.0 in 
./venv/lib/python3.10/site-packages (from -r requirements.txt (line 1)) 
(3.5)

Collecting wxPython==4.1.0
   Using cached wxPython-4.1.0.tar.gz (65.8 MB)
   Preparing metadata (setup.py) ... done
Collecting numpy>=1.8.2
   Using cached 
numpy-1.23.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (13.9 
MB)
Requirement already satisfied: pyglet>=1.1 in 
./venv/lib/python3.10/site-packages (from -r requirements.txt (line 4)) 
(1.5.26)

Collecting cffi
   Using cached 
cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl 
(449 kB)

Collecting cairocffi
   Using cached cairocffi-1.3.0.tar.gz (88 kB)
   Preparing metadata (setup.py) ... done
Collecting cairosvg>=1.0.9
   Using cached CairoSVG-2.5.2-py3-none-any.whl (45 kB)
Collecting psutil>=2.1
   Using cached psutil-5.9.1-cp310-cp310-linux_aarch64.whl
Collecting lxml>=2.9.1
   Using cached 
lxml-4.9.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl 
(6.6 MB)

Collecting appdirs>=1.4.0
   Using cached appdirs-1.4.4-py2.py3-none-any.whl (9.6 kB)
Collecting dbus-python>=1.2.0
   Using cached dbus-python-1.2.18.tar.gz (578 kB)
   Preparing metadata (setup.py) ... done
Collecting pillow
   Using cached Pillow-9.2.0-cp310-cp310-manylinux_2_28_aarch64.whl (3.1 
MB)

Collecting six
   Using cached six-1.16.0-py2.py3-none-any.whl (11 kB)
Collecting pycparser
   Using cached pycparser-2.21-py2.py3-none-any.whl (118 kB)
Collecting cssselect2
   Using cached cssselect2-0.6.0-py3-none-any.whl (15 kB)
Collecting tinycss2
   Using cached tinycss2-1.1.1-py3-none-any.whl (21 kB)
Collecting defusedxml
   Using cached defusedxml-0.7.1-py2.py3-none-any.whl (25 kB)
Requirement already satisfied: webencodings in 
./venv/lib/python3.10/site-packages (from 
cssselect2->cairosvg>=1.0.9->-r requirements.txt (line 7)) (0.5.1)
Using legacy 'setup.py install' for wxPython, since package 'wheel' is 
not installed.
Using legacy 'setup.py install' for cairocffi, since package 'wheel' is 
not installed.
Using legacy 'setup.py install' for dbus-python, since package 'wheel' 
is not installed.
Installing collected packages: dbus-python, appdirs, tinycss2, six, 
pycparser, psutil, pillow, numpy, lxml, defusedxml, wxPython, 
cssselect2, cffi, cairocffi, cairosvg

   Running setup.py install for dbus-python ... error
   error: subprocess-exited-with-error

   × Running setup.py install for dbus-python did not run successfully

Re: What can I do about this?

2022-08-29 Thread Mark Bourne

Roel Schroeven wrote:

Op 29/08/2022 om 2:55 schreef gene heskett:

On 8/28/22 19:39, Peter J. Holzer wrote:

On 2022-08-28 18:40:17 -0400, gene heskett wrote:
Persuant to my claim the py3.10 is busted, here is a sample. This is 
me,

trying to make
pronterface, inside a venv: When the package manager version will 
only run

the gui-less "pronsole"
but nothing else from that all python kit runs as it should or at all.
 From the package-managers install in /usr/share/doc/printrun-common/ I
copied requirements.txt
into the venv, and ran this command line:

gene@rock64:~/venv$ pip3 install -r requirements.txt

You are almost certainly *not* in a venv here. First, your prompt
doesn't show the name of the venv,
I've created that several times, as octoprint won''t run without it 
either.
I found a way to autostart it on reboots and octoprint seems happy 
with it
I agree with Peter: it doesn't look as if you are invoking the pip3 in 
the venv. Just making the venv-directory the current directory doesn't 
activate it.


As a diagnostic, ask the OS which pip3 is actually used:

$ type -a pip3

Does that show the pip3 installed in the venv? Or the system-wide one? 
If it's not the pip3 in the venv, well, then that's the problem (or at 
least part of the problem). Solution: first check whether the venv 
really contains 'pip3' (as opposed to eg. just 'pip'): list the contents 
of the bin subdirectory of the venv. If not, use 'pip' or whatever 
instead. Then to make sure you use the one in the venv, either activate 
the venv or explicitly specify the path when invoking pip/pip3 (and 
likewise for python/python3).


So either (assuming you're using bash):

$ source {path_to_venv}/bin/pip3  # activate the venv


I think this first line should probably be:

$ source {path_to_venv}/bin/activate  # activate the venv

i.e. with `activate` rather than `pip3`?


$ type -a pip3  # check whether now the correct pip3 is used
$ pip3 install -r requirements.txt  # finally invoke pip3

or:

$ {path_to_venv}/bin/pip3 install -r requirements.txt

Activating the venv is easier if you're going to use multiple commands 
in the venv. Note that activating the venv only has effect on the 
current shell; other shells are unaffected, and when you close the 
current shell the venv is not activated anymore.
Explicitly using the path is easier for one-off calls, or in things like 
crontab.



--
https://mail.python.org/mailman/listinfo/python-list


Re: How to make a variable's late binding crosses the module boundary?

2022-08-29 Thread Mark Bourne

Jach Feng wrote:

Chris Angelico 在 2022年8月29日 星期一下午1:58:58 [UTC+8] 的信中寫道:

On Mon, 29 Aug 2022 at 15:54, Jach Feng  wrote:


Richard Damon 在 2022年8月29日 星期一上午10:47:08 [UTC+8] 的信中寫道:

On 8/27/22 7:42 AM, Mark Bourne wrote:

Jach Feng wrote:

I have two files: test.py and test2.py
--test.py--
x = 2
def foo():
print(x)
foo()

x = 3
foo()

--test2.py--
from test import *
x = 4
foo()

-
Run test.py under Winows8.1, I get the expected result:
e:\MyDocument>py test.py
2
3

But when run test2.py, the result is not my expected 2,3,4:-(
e:\MyDocument>py test2.py
2
3
3

What to do?


`from test import *` does not link the names in `test2` to those in
`test`. It just binds objects bound to names in `test` to the same
names in `test2`. A bit like doing:

import test
x = test.x
foo = test.foo
del test

Subsequently assigning a different object to `x` in one module does
not affect the object assigned to `x` in the other module. So `x = 4`
in `test2.py` does not affect the object assigned to `x` in `test.py`
- that's still `3`. If you want to do that, you need to import `test`
and assign to `test.x`, for example:

import test
test.x = 4
test.foo()


Yes, fundamental issue is that the statement

from x import y

makes a binding in this module to the object CURRECTLY bound to x.y to
the name y, but if x.y gets rebound, this module does not track the changes.

You can mutate the object x.y and see the changes, but not rebind it.

If you need to see rebindings, you can't use the "from x import y" form,
or at a minimum do it as:


import x

from x import y

then later to get rebindings to x.y do a

y = x.y

to rebind to the current x.y object.

--
Richard Damon

Yes, an extra "import x" will solve my problem too! Sometimes I am wondering why 
"from x import y" hides x? hum...can't figure out the reason:-)


"from x import y" doesn't hide x - it just grabs y. Python does what
you tell it to. :)

ChrisA

But I had heard people say that "from x import y" did import the whole x module into memory, just 
as "import x" did, not "grabs y" only. Is this correct?


`from x import y` does import the whole module x into memory, and adds 
it to `sys.modules`.  But it only binds the name `y` in the namespace of 
module doing the import (and it binds it to the value of `x.y` at the 
time the import is done - it doesn't magically keep them in sync if one 
or the other is later reassigned).


The point about the whole module being imported is that you don't save 
any memory by using `from x import y` to avoid importing some very large 
object `z` from `x`.  Those other large objects might be needed by 
functions which have been imported (e.g. your `foo` function still needs 
`x` even if you haven't imported `x` - so it still needs to be loaded 
into memory) or might be imported and used by other modules importing 
`x`, so they still have to be loaded when any part of `x` is imported - 
they just don't have to be bound to names in the importing module's 
namespace.


As Richard mentioned, if `x.y` is a mutable object (such as a list) you 
can still mutate that object (e.g. add/remove items) and those changes 
will be seen in both modules.  That's because both are still bound to 
the same object and you're mutating that existing object.  If you assign 
a new list to either, that won't be seen by the other.


--
Mark.
--
https://mail.python.org/mailman/listinfo/python-list


Re: How to make a variable's late binding crosses the module boundary?

2022-08-28 Thread Mark Bourne

Jach Feng wrote:

I have two files: test.py and test2.py
--test.py--
x = 2
def foo():
 print(x)
foo()

x = 3
foo()

--test2.py--
from test import *
x = 4
foo()

-
Run test.py under Winows8.1, I get the expected result:
e:\MyDocument>py test.py
2
3

But when run test2.py, the result is not my expected 2,3,4:-(
e:\MyDocument>py test2.py
2
3
3

What to do?


`from test import *` does not link the names in `test2` to those in 
`test`.  It just binds objects bound to names in `test` to the same 
names in `test2`.  A bit like doing:


import test
x = test.x
foo = test.foo
del test

Subsequently assigning a different object to `x` in one module does not 
affect the object assigned to `x` in the other module.  So `x = 4` in 
`test2.py` does not affect the object assigned to `x` in `test.py` - 
that's still `3`.  If you want to do that, you need to import `test` and 
assign to `test.x`, for example:


import test
test.x = 4
test.foo()

--
Mark.
--
https://mail.python.org/mailman/listinfo/python-list