type annotation vs working code

2023-09-30 Thread Karsten Hilbert via Python-list
A type annotation isn't supposed to change what code does,
or so I thought:

#
class Borg:
_instances:dict = {}

def __new__(cls, *args, **kargs):
# look up subclass instance cache
if Borg._instances.get(cls) is None:
Borg._instances[cls] = object.__new__(cls)
return Borg._instances[cls]


class WorkingSingleton(Borg):

def __init__(self):
print(self.__class__.__name__, ':')
try:
self.already_initialized
print('already initialized')
return

except AttributeError:
print('initializing')

self.already_initialized = True
self.special_value = 42


class FailingSingleton(Borg):

def __init__(self):
print(self.__class__.__name__, ':')
try:
self.already_initialized:bool
print('already initialized')
return

except AttributeError:
print('initializing')

self.already_initialized = True
self.special_value = 42

s = WorkingSingleton()
print(s.special_value)

s = FailingSingleton()
print(s.special_value)

#

Notice how Working* and Failing differ in the type annotation
of self.already_initialized only.

Output:

WorkingSingleton :
initializing
42

FailingSingleton :
already initialized <== 
Huh ?
Traceback (most recent call last):
  File 
"/home/ncq/Projekte/gm/git/gnumed/gnumed/client/testing/test-singleton.py", 
line 48, in 
print(s.special_value)
  ^^^
AttributeError: 'FailingSingleton' object has no attribute 
'special_value'


Where's the error in my thinking (or code) ?

Thanks,
Karsten
--
GPG  40BE 5B0E C98E 1713 AFA6  5BC0 3BEA AC80 7D4F C89B
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: type annotation vs working code

2023-09-30 Thread Mats Wichmann via Python-list

On 9/30/23 13:00, Karsten Hilbert via Python-list wrote:

A type annotation isn't supposed to change what code does,
or so I thought:

#
class Borg:
_instances:dict = {}

def __new__(cls, *args, **kargs):
# look up subclass instance cache
if Borg._instances.get(cls) is None:
Borg._instances[cls] = object.__new__(cls)
return Borg._instances[cls]


class WorkingSingleton(Borg):

def __init__(self):
print(self.__class__.__name__, ':')
try:
self.already_initialized
print('already initialized')
return

except AttributeError:
print('initializing')

self.already_initialized = True
self.special_value = 42


class FailingSingleton(Borg):

def __init__(self):
print(self.__class__.__name__, ':')
try:
self.already_initialized:bool
print('already initialized')
return

except AttributeError:
print('initializing')

self.already_initialized = True
self.special_value = 42

s = WorkingSingleton()
print(s.special_value)

s = FailingSingleton()
print(s.special_value)

#

Notice how Working* and Failing differ in the type annotation
of self.already_initialized only.


What happens here is in the second case, the line is just recorded as a 
variable annotation, and is not evaluated as a reference, as you're 
expecting to happen, so it just goes right to the print call without 
raising the exception.  You could change your initializer like this:


def __init__(self):
print(self.__class__.__name__, ':')
self.already_initialized: bool
try:
self.already_initialized
print('already initialized')
return

The syntax description is here:

https://peps.python.org/pep-0526/#global-and-local-variable-annotations


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


Re: type annotation vs working code

2023-09-30 Thread dn via Python-list

On 01/10/2023 08.00, Karsten Hilbert via Python-list wrote:

A type annotation isn't supposed to change what code does,
or so I thought:

#
class Borg:
_instances:dict = {}

def __new__(cls, *args, **kargs):
# look up subclass instance cache
if Borg._instances.get(cls) is None:
Borg._instances[cls] = object.__new__(cls)
return Borg._instances[cls]


class WorkingSingleton(Borg):

def __init__(self):
print(self.__class__.__name__, ':')
try:
self.already_initialized
print('already initialized')
return

except AttributeError:
print('initializing')

self.already_initialized = True
self.special_value = 42


class FailingSingleton(Borg):

def __init__(self):
print(self.__class__.__name__, ':')
try:
self.already_initialized:bool
print('already initialized')
return

except AttributeError:
print('initializing')

self.already_initialized = True
self.special_value = 42

s = WorkingSingleton()
print(s.special_value)

s = FailingSingleton()
print(s.special_value)

#

Notice how Working* and Failing differ in the type annotation
of self.already_initialized only.

Output:

WorkingSingleton :
initializing
42

FailingSingleton :
already initialized <== 
Huh ?
Traceback (most recent call last):
  File 
"/home/ncq/Projekte/gm/git/gnumed/gnumed/client/testing/test-singleton.py", line 48, 
in 
print(s.special_value)
  ^^^
AttributeError: 'FailingSingleton' object has no attribute 
'special_value'


Where's the error in my thinking (or code) ?


What is your thinking?
Specifically, what is the purpose of testing self.already_initialized?

Isn't it generally regarded as 'best practice' to declare (and define a 
value for) all attributes in __init__()? (or equivalent) In which case, 
it will (presumably) be defined as False; and the try-except reworded to 
an if-else.


Alternately, how about using hasattr()? eg

if hasattr( self.already_initialized, 'attribute_name' ):
  # attribute is defined, etc


As the code current stands, the:

try:
self.already_initialized

line is flagged by the assorted linters, etc, in my PyCharm as:

Statement seems to have no effect.
Unresolved attribute reference 'already_initialized' for class 
'WorkingSingleton'.


but:

self.already_initialized:bool

passes without comment (see @Mats' response).


Question: is it a legal expression (without the typing)?

--
Regards,
=dn
--
https://mail.python.org/mailman/listinfo/python-list


Re: type annotation vs working code

2023-09-30 Thread Karsten Hilbert via Python-list
Am Sun, Oct 01, 2023 at 09:04:05AM +1300 schrieb dn via Python-list:

> >class WorkingSingleton(Borg):
> >
> > def __init__(self):
> > print(self.__class__.__name__, ':')
> > try:
> > self.already_initialized
> > print('already initialized')
> > return
> >
> > except AttributeError:
> > print('initializing')
> >
> > self.already_initialized = True
> > self.special_value = 42

> >Where's the error in my thinking (or code) ?
>
> What is your thinking?
> Specifically, what is the purpose of testing self.already_initialized?

The purpose is to check whether the singleton class has been
... initialized :-)

The line

self.already_initialized = True

is misleading as to the fact that it doesn't matter at all
what self.already_initialized is set to, as long as is
exists for the next time around.

> Isn't it generally regarded as 'best practice' to declare (and define a value 
> for) all
> attributes in __init__()? (or equivalent) In which case, it will (presumably) 
> be defined
> as False; and the try-except reworded to an if-else.

I fail to see how that can differentiate between first-call
and subsequent call.

> Alternately, how about using hasattr()? eg
>
> if hasattr( self.already_initialized, 'attribute_name' ):

That does work. I am using that idiom in other children of
Borg. But that's besides the point. I was wondering why it
does not work the same way with and without the type
annotation.

> try:
> self.already_initialized
>
> line is flagged by the assorted linters, etc, in my PyCharm as:
>
> Statement seems to have no effect.

Well, the linter simply cannot see the purpose, which is
test-of-existence.

> Question: is it a legal expression (without the typing)?

It borders on the illegal, I suppose, as the self-
introspection capabilities of the language are being
leveraged to achieve a legal purpose.

Which seems akin constructs for generating compatibility
between versions.

It seems the answer is being pointed to in Matts response.

It just mightily surprised me.

Karsten
--
GPG  40BE 5B0E C98E 1713 AFA6  5BC0 3BEA AC80 7D4F C89B
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: type annotation vs working code

2023-09-30 Thread dn via Python-list

On 01/10/2023 11.25, Karsten Hilbert via Python-list wrote:

Am Sun, Oct 01, 2023 at 09:04:05AM +1300 schrieb dn via Python-list:


class WorkingSingleton(Borg):

def __init__(self):
print(self.__class__.__name__, ':')
try:
self.already_initialized
print('already initialized')
return

except AttributeError:
print('initializing')

self.already_initialized = True
self.special_value = 42



Where's the error in my thinking (or code) ?


What is your thinking?
Specifically, what is the purpose of testing self.already_initialized?


Apologies, my tending to use the "Socratic Method" with trainees (and 
avoiding any concept of personal-fault with others), means it can be 
difficult to tell if (personal*) introspection is being invited, or if I 
don't know the answer (and want to).


* personal cf Python code introspection (hah!)



The purpose is to check whether the singleton class has been
... initialized :-)

The line

self.already_initialized = True

is misleading as to the fact that it doesn't matter at all
what self.already_initialized is set to, as long as is
exists for the next time around.


Isn't it generally regarded as 'best practice' to declare (and define a value 
for) all
attributes in __init__()? (or equivalent) In which case, it will (presumably) 
be defined
as False; and the try-except reworded to an if-else.


I fail to see how that can differentiate between first-call
and subsequent call.


+1



Alternately, how about using hasattr()? eg

if hasattr( self.already_initialized, 'attribute_name' ):


That does work. I am using that idiom in other children of
Borg. But that's besides the point. I was wondering why it
does not work the same way with and without the type
annotation.


Annotations are for describing the attribute. In Python we don't have 
different instructions for declaring an object and defining it, eg


INTEGER COUNTER
COUNTER = 0

Thus, Python conflates both into the latter, ie

counter = 0
or
counter:int = 0

(both have the same effect in the Python Interpreter, the latter aims to 
improve documentation/reading/code-checking)


Typing defines (or rather labels) the object's type. Accordingly, occurs 
when the object is on the LHS (Left-hand Side) of an expression (which 
includes function-argument lists).


In this 'test for existence': in the case of WorkingSingleton(), the 
code-line is effectively only a RHS - see 'illegal' (below).


However, the annotation caused the code-line to be re-interpreted as 
some sort of LHS in FailingSingleton().
- as explained (@Mats) is understood as a 'typing expression' rather 
than 'Python code'.


Apologies: fear this a rather clumsy analysis - will welcome improvement...



 try:
 self.already_initialized

line is flagged by the assorted linters, etc, in my PyCharm as:

Statement seems to have no effect.


Well, the linter simply cannot see the purpose, which is
test-of-existence.




Question: is it a legal expression (without the typing)?

It borders on the illegal, I suppose, as the self-
introspection capabilities of the language are being
leveraged to achieve a legal purpose.



...and so we're addressing the important question: the try-test is for 
existence, cf for some value.


This can also be achieved by using the attribute in a legal expression, eg:

self.already_initialized == True


When introspecting code, if type-checkers cannot determine the purpose, 
is there likely to be a 'surprise factor' when a human reads it?

(that's Socratic! I already hold an opinion: right or wrong)


Might this remove the confusion (ref: @Mats):

self.already_initialized:bool == True

(not Socratic, don't know, haven't tested)




Which seems akin constructs for generating compatibility
between versions.


versions of ?



It seems the answer is being pointed to in Matts response.

It just mightily surprised me.


Me too!


I am slightly confused (OK, OK!) and probably because I don't have a 
good handle on "Borg" beyond knowing it is a Star Wars?Trek reference 
(apologies to the reader sucking-in his/her breath at such an utterance!).


What is the intent: a class where each instance is aware of every other 
instance - yet the word "Singleton" implies there's only one (cf a dict 
full of ...)?



Second move (also, slightly) off-topic:
I'm broadly in-favor of typing; additionally noting that trainees find 
it helpful whilst developing their code-reading skills. However, am not 
particularly zealous in my own code, particularly if the type-checker 
starts 'getting picky' with some construct and taking-up 
time/brain-power. (which is vitally-required for writing/testing Python 
code!)


So, (original code-sample, second line), seeing we ended-up talking 
about a type-definition cf attribute-defini