edgegraph.structure.singleton.semi_singleton_metaclass

edgegraph.structure.singleton.semi_singleton_metaclass#

edgegraph.structure.singleton.semi_singleton_metaclass(hashfunc=None)#

Generate and return a metaclass for semi-singletons.

See also

The design of this metaclass is heavily inspired by this excellent StackOverflow answer by user @agf. Thanks!!

https://stackoverflow.com/a/6798042

The default hash function also uses an approach for dictionary hashing from StackOverflow user Jack O’Connor. Thanks!!

https://stackoverflow.com/a/22003440

Danger

Though potentially less bad than true singletons, usage of this metaclass alongside a Vertex can lead to surprising side-effects. As a quick example, creating a vertex under one universe may actually return you a reference to a pre-existing vertex from elsewhere. This will be completely silent, and probably very hard to debug.

Be careful!

This function creates metaclasses for so-called semi-singletons; classes that act as if they have an instantiation cache. In other words, creating duplicate instances of these classes is not possible, but creating different instances is (as determined by their arguments). This may be easiest to explain with an example:

>>> from edgegraph.singleton import semi_singleton_metaclass
>>> class SemiSingleton(metaclass=semi_singleton_metaclass()):
...     def __init__(self, foo, bar=False):
...         self.foo = foo
...         self.bar = bar
...
>>> s1 = SemiSingleton(1, False)
>>> s1
<__main__.SemiSingleton object at 0xdeadbeef>
>>> s2 = SemiSingleton(1, False)  # same arguments -- we'll get same object
>>> s2
<__main__.SemiSingleton object at 0xdeadbeef>
>>> s1 is s2
True
>>> s3 = SemiSingleton(37, True)  # different arguments -- different object
>>> s3
<__main__.SemiSingleton object at 0x01234567>
>>> s2 is s3
False

Customization of how arguments are understood to be “different” may be done via the hashfunc argument. If provided, it must be a callable object:

edgegraph.structure.singleton.hashfunc(args: tuple, kwargs: dict) Hashable

This function is given the args and kwargs of a class instantiator (a call to __init__) and expected to return a hashable type, most commonly an integer. It is the determining factor in whether two attempts to instantiate an object should act as a singleton or actually create a new object.

Parameters:
  • args – A tuple containing the positional arguments given. Often seen as *args, though not starred in this case (you get the actual tuple).

  • kwargs – A dictionary containing keyword arguments given. Often seen as **kwargs, though not starred in this case (you get the actual dictionary).

Returns:

Some hashable data time. Most often, an int.

If not specified, the default hashfunc inspects all positional and keyword arguments, and hashes them all. This causes a new object to be created if any argument is different. So long as they have the same value, order of keyword arguments is not accounted for.

Note

Python dict is not a hashable data type; therefore, special care must often be taken when hashing kwargs. The default hashfunc uses json.dumps() to accomplish this, transforming (recursively) the dictionary into a string, which is hashable. This has some side effects, though:

  • All the keys of the dictionary must be strings (which is the case with kwargs, but may not be the case if passing another dictionary into a keyword argument)

  • The JSON encoder may handle character escaping differently on different platforms and/or Python versions. I believe this to be a nonissue for the semi-singleton application, but attempting to pickle and unpickle these objects may have undefined behavior.

  • Passing non-JSON-ify-able data types may cause issues (things other than strings, ints, bools, and lists/dictionaries of them)

See also

Parameters:

hashfunc (Callable) – Callable object to identify unique calls to this class by arguments provided.

Returns:

A class suitable for use as a metaclass of another class.

Return type:

type