1

I have this code:

class Singleton(type):

  def __call__(cls,*args,**kwargs):
    if cls.created is None :
      print('called')
      cls.created = super().__call__(*args,**kwargs)
      return cls.created
    else:
      return cls.created
  def __new__(cls,name,base,attr,**kwargs):
    return super().__new__(cls,name,base,attr,**kwargs)


class OnlyOne(metaclass=Singleton):
  created = None
  def __init__(self,val):
      self.val = val

class OnlyOneTwo(OnlyOne):
  pass


k = OnlyOne(1)
a = OnlyOneTwo(2)



print(a.val)
print(k.val)
print('a.created: {0} - b.created: {1}'.format(id(a.created),id(k.created)))

I'm new to Python 3 so I decided to do some little experiment and playing around Python's metaclasses.

Here, I attempted to make a metaclass that will strict a class to a single instance when set.

I'm not sure yet if this works but whenever I try to do:

k = OnlyOne(1)
a = OnlyOneTwo(2)

the output will be:

called
1
1

which means that OnlyOneTwo wasn't set but when I try to do:

a = OnlyOneTwo(2)
k = OnlyOne(1)

the output will be:

called
called
2
1

Can someone help me traceback? I'm somehow confused but here are my initial questions/thoughts:

  1. Does OnlyOneTwo's created property the same as OnlyOne's ? because I get different results through id() depending on which one I defined first. It's different if it's OnlyOneTwo first but it's the same if it's OnlyOne first.

  2. How come created is still None if I will run a = OnlyOneTwo(2) print(OnlyOne.created) ?

wwii
  • 19,802
  • 6
  • 32
  • 69
Carlos Miguel Colanta
  • 2,285
  • 3
  • 23
  • 45
  • Maybe start by adding ```print('__new__\tcls:{}\n\tname:{}\n\tbase:{}\n\tattr:{}'.format(cls,name,base,attr))``` as the first line of ```__new__```. – wwii Dec 15 '16 at 04:12

1 Answers1

1

I'll give this a shot. I think the symptom is due to the assignment, cls.created = ... in __call__.

When the class objects are first created OnlyOneTwo.created points to OnlyOne.created. They both have the same id, which is the same as None.

>>> id(None)
506773144
>>> id(OnlyOne.created), id(OnlyOneTwo.created)
(506773144, 506773144)

If you make an instance of OnlyOne first, the instance is assigned to OnlyOne.created (it no longer points to None) but OnlyOneTwo.created still points to OnlyOne.created - they are still the same thing so that when OnlyOneTwo is called, the else clause of the conditional is executed.

>>> a = OnlyOne('a')
called
>>> id(OnlyOne.created), id(OnlyOneTwo.created)
(54522152, 54522152)
>>> z = OnlyOneTwo('z')
>>> id(OnlyOne.created), id(OnlyOneTwo.created)
(54522152, 54522152)
>>> id(a)
54522152

When you make an instance of OnlyOneTwo first, that instance is assigned to OnlyOneTwo.created, it no longer points to OnlyOne.created. OnlyOne.created still points to None.

>>> id(None)
506773144
>>> id(OnlyOne.created), id(OnlyOneTwo.created)
(506773144, 506773144)
>>> z = OnlyOneTwo('z')
called
>>> id(OnlyOne.created), id(OnlyOneTwo.created)
(506773144, 54837544)
>>> id(z)
54837544

Now when you make an instance of OnlyOne the if condition is True and the instance is assigned to OnlyOne.created

>>> a = OnlyOne('a')
called
>>> id(OnlyOne.created), id(OnlyOneTwo.created)
(54352752, 54837544)
>>> id(a)
54352752

I often find myself re-reading Binding of Names and Resolution of Names - also A Word About Names and Objects and Python Scopes and Namespaces


I feel that I haven't actually explained the mechanism - I don't really understand how the child class attribute points to the base class attribute - it is something different than a = b = None.

Maybe:

  • Initially the child class doesn't actually have the attribute, the points to mechanism is how inheritance is implemented, since it doesn't have the attribute, the attribute is searched for in its parent(s).
  • Even though the parent class attribute changes with instantiation, the child class still doesn't have the attribute and has to search for it and it finds the new thing.
  • If the child class is instantiated first the assignment in the metaclass gives the attribute to the child class - now it has it and it doesn't have to search for it.

Re-reading the Custom Classes section of the Standard Type Hierarchy in the docs triggered that brain dump.

I feel like I've been here before, maybe even in SO. Hope I remember it this time and don't have to figure it out again.


If you want to fix your Singleton there are numerous options if you search for them. Using a metaclass it seems the instance is held in the metaclass, not the the class itself. Creating a singleton in Python, SO Q&A, is a good start.

Community
  • 1
  • 1
wwii
  • 19,802
  • 6
  • 32
  • 69