Re: type annotation vs working code

2023-10-04 Thread Karsten Hilbert via Python-list
Am Wed, Oct 04, 2023 at 05:25:04PM +1300 schrieb dn via Python-list:

> The first question when dealing with the Singleton Pattern is what to do when 
> more than
> one instantiation is attempted:
>
> - silently return the first instance

This, in my case.

> and so, returning to the matter of 'readability':
>
> - the name "Borg" de-railed comprehension
>
> - _instances:dict = {} implied the tracking of more than one

Child classes, yes, each being a Singleton.

> or a Singleton() class defined, which is then sub-classed, ie
>
> class Something( Singleton ):

Could have been but the legacy codebase came with Borg ...

> - from there, plenty of 'templates' exist for Singletons,

...  which was taken from the Web ages ago.

> - this article (https://python-patterns.guide/gang-of-four/singleton/)

Reading.

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-10-04 Thread dn via Python-list

On 04/10/2023 19.41, Chris Angelico via Python-list wrote:

On Wed, 4 Oct 2023 at 15:27, dn via Python-list  wrote:

- should the class have been called either;

  class SomethingSingleton():

or a Singleton() class defined, which is then sub-classed, ie

  class Something( Singleton ):

in order to better communicate the coder's intent to the reader?


TBH, I don't think it's right to have a Singleton class which is
subclassed by a bunch of different singletons. They aren't really
subclasses of the same class. I could imagine Singleton being a
metaclass, perhaps, but otherwise, they're not really similar to each
other.


I'm with you on this - should have made Singleton() an ABC.

Yes, would only be a skeleton around which concrete singleton classes 
could be built.


Like you, I v.rarely use them - but which means that the ABC is useful 
because it would save me from having to remember the curly-bits 
all-over-again...


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


Re: type annotation vs working code

2023-10-04 Thread Chris Angelico via Python-list
On Wed, 4 Oct 2023 at 17:47, Greg Ewing via Python-list
 wrote:
>
> On 4/10/23 5:25 pm, dn wrote:
> > The first question when dealing with the Singleton Pattern is what to do
> > when more than one instantiation is attempted
>
> My preferred way of handling singletons is not to expose the class
> itself, but a function that creates an instance the first time it's
> called, and returns that instance subsequently. The problem then
> doesn't arise.
>

That's one option. Personally, I don't use them very much, but if I
do, it's usually actually as a class that never gets instantiated:

class PileOfAttributes:
x = 1
y = 2
spam = "ham"

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


Re: type annotation vs working code

2023-10-04 Thread Greg Ewing via Python-list

On 4/10/23 5:25 pm, dn wrote:
The first question when dealing with the Singleton Pattern is what to do 
when more than one instantiation is attempted


My preferred way of handling singletons is not to expose the class
itself, but a function that creates an instance the first time it's
called, and returns that instance subsequently. The problem then
doesn't arise.

--
Greg

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


Re: type annotation vs working code

2023-10-04 Thread Chris Angelico via Python-list
On Wed, 4 Oct 2023 at 15:27, dn via Python-list  wrote:
> - should the class have been called either;
>
>  class SomethingSingleton():
>
> or a Singleton() class defined, which is then sub-classed, ie
>
>  class Something( Singleton ):
>
> in order to better communicate the coder's intent to the reader?

TBH, I don't think it's right to have a Singleton class which is
subclassed by a bunch of different singletons. They aren't really
subclasses of the same class. I could imagine Singleton being a
metaclass, perhaps, but otherwise, they're not really similar to each
other.

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


Re: type annotation vs working code

2023-10-03 Thread dn via Python-list

On 02/10/2023 00.57, Karsten Hilbert via Python-list wrote:

Sorry for having conflated the core of the matter with all
the Borg shenanigans, that's where I found the problem in my
real code, so there :-)


The first question when dealing with the Singleton Pattern is what to do 
when more than one instantiation is attempted:


- silently return the first instance
- raise an exception


The 'problem' interpreting the original code was that the 'Borg 
Pattern', is not limited in number, but is where some class-attribute 
list (or dict) is used to enable all instances to be aware of each of 
the others (IIRC).


Is choosing names as important as selecting/implementing smart algorithms?



Consider this:

#
class Surprise:
def __init__(self, with_type_annotation=False):
if with_type_annotation:
try:
self.does_not_exist:bool
print('does_not_exist does exist')
except AttributeError:
print('does_not_exist does not exist')
return

try:
self.does_not_exist
print('does_not_exist does exist')
except AttributeError:
print('does_not_exist does not exist')

Surprise(with_type_annotation = False)
Surprise(with_type_annotation = True)
#

Is this how it is supposed to be ?


Wasn't this answered earlier? (@Mats)

That self.does_not_exist:bool isn't interpreted by Python to mean the 
same as self.does_not_exist.




...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:

...

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

 self.already_initialized:bool == True


Not for me as that would _create_ already_initialized on the
instance. It would not allow me to test for it.


Which seems akin constructs for generating compatibility
between versions.


versions of ?


Of the language. Sometimes one tests for existence of a given
class in a module and defines said class oneself if it does
not exist. But that's leading astray.


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 ...)?


The latter.


and so, returning to the matter of 'readability':

- the name "Borg" de-railed comprehension

- _instances:dict = {} implied the tracking of more than one

- should the class have been called either;

class SomethingSingleton():

or a Singleton() class defined, which is then sub-classed, ie

class Something( Singleton ):

in order to better communicate the coder's intent to the reader?

- from there, plenty of 'templates' exist for Singletons, so why do 
something quite different/alien to the reader?

(thus concurring with @Richard: "tricky" subverts 'readability')

- is it better to use a technique which 'we' will recognise, or to ask 
'us' to comprehend something 'new'?
(unless the 'new' is taking-advantage of a recent extension to the 
language, eg switch; to justify 'trail-blazing' a 
new/improved/replacement 'best practice')


- part of the 'tricky' seems to be an attempt to assess using an 
instance-attribute, rather than a class-attribute. If the :bool (or 
whichever) typing-annotation is added to a class-attribute (eg 
_instance), will the problem arise?


- does the sequence

_instance = False
...
if not cls._instance:
ie the explicit version
if cls._instance == False:

measure 'existence' or a value?

- does the sequence

_instance = None
...
if not cls._instance:
ie the explicit version:
if cls._instance is None:

measure 'existence' or identity?
(indeed, are they more-or-less the same concept?)

- do the *attr() functions test for 'existence'?

(that said, most of the code-examples I spotted, in reading-up on this, 
use either None or False - et tu Brute!)



Speaking of reading-up:

- am wondering where PEP 661 - Sentinel Values is 'going'?

- this article (https://python-patterns.guide/gang-of-four/singleton/) 
mentions that the original GoF Singleton Pattern preceded Python 
(particularly Python 3 classes). Also, that Python doesn't have 
complications present in C++. It further discusses "several drawbacks", 
which also champion 'readability' over 'trick' or 'sophistication'. I 
think you'll enjoy it!


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


Re: type annotation vs working code

2023-10-01 Thread Chris Angelico via Python-list
On Mon, 2 Oct 2023 at 09:10, Barry via Python-list
 wrote:
>
>
>
> > On 1 Oct 2023, at 19:36, Richard Damon via Python-list 
> >  wrote:
> >
> > Perhaps a better method would be rather than just using the name and 
> > catching the exception, use a real already_initialized flag (set to True 
> > when you initialize), and look it up with getattr() with a default value of 
> > False.
> I would use a class variable not an instance variable.
>
> class OnlyOne:
>sole_instance = None
>def __init__(self):
>   assert OnlyOne.sole_instance is None
>   OnlyOne.sole_instance = self
>

Agreed, except that this should be an if-raise rather than an assert.

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


Re: type annotation vs working code

2023-10-01 Thread Barry via Python-list



> On 1 Oct 2023, at 19:36, Richard Damon via Python-list 
>  wrote:
> 
> Perhaps a better method would be rather than just using the name and catching 
> the exception, use a real already_initialized flag (set to True when you 
> initialize), and look it up with getattr() with a default value of False.
I would use a class variable not an instance variable.

class OnlyOne:
   sole_instance = None
   def __init__(self):
  assert OnlyOne.sole_instance is None
  OnlyOne.sole_instance = self

Barry


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


Re: type annotation vs working code

2023-10-01 Thread Richard Damon via Python-list
My view of the issue is that the "trick" of "evaluating" a name to see 
if the object has been initialized is just a tad on the "tricky" side, 
and the annotation/value is really incorrect.


The name at the point you are annotating it, isn't really a "bool" 
because a bool will always have either the value "True" or "False", 
while for this variable, you are really testing if it exists or not.


Perhaps a better method would be rather than just using the name and 
catching the exception, use a real already_initialized flag (set to True 
when you initialize), and look it up with getattr() with a default value 
of False.


--
Richard Damon

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


Re: type annotation vs working code

2023-10-01 Thread Chris Angelico via Python-list
On Sun, 1 Oct 2023 at 22:58, Karsten Hilbert via Python-list
 wrote:
>
> Sorry for having conflated the core of the matter with all
> the Borg shenanigans, that's where I found the problem in my
> real code, so there :-)
>
> Consider this:
>
> #
> class Surprise:
> def __init__(self, with_type_annotation=False):
> if with_type_annotation:
> try:
> self.does_not_exist:bool
> print('does_not_exist does exist')
> except AttributeError:
> print('does_not_exist does not exist')
> return
>
> try:
> self.does_not_exist
> print('does_not_exist does exist')
> except AttributeError:
> print('does_not_exist does not exist')
>
> Surprise(with_type_annotation = False)
> Surprise(with_type_annotation = True)
> #
>
> Is this how it is supposed to be ?

The class isn't even significant here. What you're seeing is simply
that an annotation does not evaluate the expression.

https://peps.python.org/pep-0526/

It's basically a coincidence that your two versions appear nearly
identical. They are quite different semantically. Note that annotating
the expression "self.does_not_exist" is not particularly meaningful to
Python, and I've no idea what different type checkers will do with it;
you normally only annotate variables that you own - so, in a function,
that's function-local variables. Instead, class and instance
attributes should be annotated at the class level, which would remove
this apparent similarity.

This is a very good reason NOT to arbitrarily add type hints to code.
Type hints do not inherently improve code, and making changes just for
the sake of adding them risks making semantic changes that you didn't
intend. Python uses a system of gradual typing for very good reason;
you should be able to add hints only to the places where they're
actually useful, leaving the rest of the code untouched.

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


Re: type annotation vs working code

2023-10-01 Thread Karsten Hilbert via Python-list
Sorry for having conflated the core of the matter with all
the Borg shenanigans, that's where I found the problem in my
real code, so there :-)

Consider this:

#
class Surprise:
def __init__(self, with_type_annotation=False):
if with_type_annotation:
try:
self.does_not_exist:bool
print('does_not_exist does exist')
except AttributeError:
print('does_not_exist does not exist')
return

try:
self.does_not_exist
print('does_not_exist does exist')
except AttributeError:
print('does_not_exist does not exist')

Surprise(with_type_annotation = False)
Surprise(with_type_annotation = True)
#

Is this how it is supposed to be ?


> ...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:
...
> Might this remove the confusion (ref: @Mats):
>
> self.already_initialized:bool == True

Not for me as that would _create_ already_initialized on the
instance. It would not allow me to test for it.

> >Which seems akin constructs for generating compatibility
> >between versions.
>
> versions of ?

Of the language. Sometimes one tests for existence of a given
class in a module and defines said class oneself if it does
not exist. But that's leading astray.

> 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 ...)?

The latter.

Regards,
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 

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 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 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


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