type annotation vs working code

dn PythonList at DancesWithMice.info
Sun Oct 1 00:41:29 EDT 2023


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-definition, do you (gentle reader, 
as well as @OP, if inclined) feel an excess of 'boiler-plate' in the 
likes of:

     _instances:dict = {}

we write ":dict", yet doesn't the RHS's "{}" communicate exactly the 
same (to us, and to dev.tools)?
NB for reasons described, I'll habitually type the typing!
But...


Thanks for the thought-provoking question!

-- 
Regards,
=dn


More information about the Python-list mailing list