Where's the error in my thinking (or 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.
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 <module>
print(s.special_value)
^^^^^^^^^^^^^^^
AttributeError: 'FailingSingleton' object has no attribute 'special_value'
Where's the error in my thinking (or code) ?
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?
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' ):
try:
self.already_initialized
line is flagged by the assorted linters, etc, in my PyCharm as:
Statement seems to have no effect.
Question: is it a legal expression (without the typing)?
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.
...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
Which seems akin constructs for generating compatibility
between versions.
versions of ?
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 ...)?
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 ?
On 1 Oct 2023, at 19:36, Richard Damon via Python-list <python-list@python.org> wrote:I would use a class variable not an instance variable.
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.
On 1 Oct 2023, at 19:36, Richard Damon via Python-list <python-list@python.org> 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
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.
The first question when dealing with the Singleton Pattern is what to do
when more than one instantiation is attempted
- 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?
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.
On Wed, 4 Oct 2023 at 15:27, dn via Python-list <python-list@python.org> 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.
- should the class have been called either;
class SomethingSingleton():
or a Singleton() class defined, which is then sub-classed, ie
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
and so, returning to the matter of 'readability':
- the name "Borg" de-railed comprehension
- _instances:dict = {} implied the tracking of more than one
or a Singleton() class defined, which is then sub-classed, ie
class Something( Singleton ):
- from there, plenty of 'templates' exist for Singletons,
- this article (https://python-patterns.guide/gang-of-four/singleton/)
Sysop: | Keyop |
---|---|
Location: | Huddersfield, West Yorkshire, UK |
Users: | 300 |
Nodes: | 16 (2 / 14) |
Uptime: | 77:46:30 |
Calls: | 6,716 |
Files: | 12,247 |
Messages: | 5,357,739 |