Re: type annotation vs working code
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
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
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
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
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
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
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
> 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
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
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
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
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
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
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
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
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