5

I'm attempting to learn Python on my own,so, I got a piece of software I had written in C#, and attempted to re-write it in Python. Given the following class I have a few questions:

C#

sealed class Message
{
    private int messageID;
    private string message;
    private ConcurrentBag <Employee> messageFor;
    private Person messageFrom;
    private string calltype;
    private string time;


    public Message(int iden,string message, Person messageFrom, string calltype,string time)
    {
        this.MessageIdentification = iden;
        this.messageFor = new ConcurrentBag<Employee>();
        this.Note = message;
        this.MessageFrom = messageFrom;
        this.CallType = calltype;
        this.MessageTime = time;
    }

    public ICollection<Employee> ReturnMessageFor
    {
        get
        {
            return messageFor.ToArray();
        }

    }
  1. In my class I have a thread-safe collection called messageFor, is there an equivalent in Python? If so, how do I implement it in a python class?

  2. I also have a getter for my thread-safe collection? How would I go about doing the same in Python?

  3. Does Python have an EqualsTo method to test equality between objects? Or the equivalent of this in Python?

        public override bool Equals(object obj)
        {
            if (obj == null)
            {
                return false;
            }
    
            Message testEquals = obj as Message;
    
            if((System.Object)testEquals == null)
            {
                return false;
            }
    
            return (this.messageID == testEquals.messageID) && (this.message == testEquals.message) && (this.messageFor == testEquals.messageFor) && (this.messageFrom == testEquals.messageFrom) && (this.calltype == testEquals.calltype);
    
        }
    
        public bool Equals(Message p)
        {
            if ((Object)p == null)
            {
                return false;
            }
    
            return (this.messageID == p.messageID) && (this.message == p.message) && (this.messageFor == p.messageFor) && (this.messageFrom == p.messageFrom) && (this.calltype == p.calltype);
    
        }
    
  4. Can you make it sealed, so that no one can inherit from it?

What I've got so far:

class Message:


   def __init__(self, messageID, message, callType):

      self.messageID = messageID
      self.message = message
      self.callType = callType
  • I'm not familiar with C#, so I *might* be misunderstanding what you mean by sealed. Python doesn't generally block you from accessing things. There are no private members of objects, you can even overwrite builtin functions without errors (at least, unless you muck up and create errors when you do it). – SuperBiasedMan Jul 28 '15 at 16:37
  • @SuperBiasedMan - It stops you from subclassing. So for example, String is a sealed class, I can't do mynewClass:String –  Jul 28 '15 at 16:38
  • @SuperBiasedMan - How then would you write this the Python way? I'm especially interested in the thread-safe collection and its getter implementation. –  Jul 28 '15 at 16:39
  • 3
    In Python, you prevent subclassing by leaving a comment saying `#dear future maintainers: pretty please don't subclass this` ;-) – Kevin Jul 28 '15 at 16:40
  • @Nexusfactor I have heard the term 'thread-safe' many times but still don't know what it means nor have I had to use it. I think you'll need better programmers than I to answer this question. – SuperBiasedMan Jul 28 '15 at 16:42
  • @Kevin - How would you go about writing the class in Python? –  Jul 28 '15 at 16:47
  • 1
    Python does have [`__eq__`](https://docs.python.org/2/reference/datamodel.html) – NightShadeQueen Jul 28 '15 at 17:01
  • If methods rely on attributes that may be modified concurrently, you might implement a few different locks: `threading.lock` to prevent things from going wrong. you might look at giving each instance it's own lock, and wrap your methods in `with self.lock:` statements. You also could wrap the [magic methods](https://docs.python.org/2/reference/datamodel.html#special-method-names) in locks to prevent assignment, comparison, etc. from occurring at a bad time. – Aaron Jul 28 '15 at 17:06
  • 1
    I would recommend reading http://www.rafekettler.com/magicmethods.html a great overview of Python's magic methods. It doesn't specifically address preventing subclassing that I recall, but great stuff to know. – Scott Jul 28 '15 at 17:06

2 Answers2

4

In my class I have a thread-safe collection called messageFor, is there an equivalent in Python? If so, how do I implement it in a python class?

In general, CPython's Global Interpreter Lock prevents crashes due to simultaneous access/modification, since you can't achieve truly simultaneous evaluation at all. So an ordinary list might be suitable, or you possibly may need a Queue to prevent higher-level race conditions. See Are lists thread-safe for more information.

I also have a getter for my thread-safe collection? How would I go about doing the same in Python?

You can emulate the behavior of C# getters/setters using the @property decorator. See Python @property versus getters and setters for more information.

Does Python have an EqualsTo method to test equality between objects? Or the equivalent of this in Python?

You can define equality behavior using the __eq__ method. For example, your code might translate to something like:

class Thing:
    #other methods go here...
    def __eq__(self, other):
        if not isinstance(other, Thing):
            return False
        return self.messageID == other.messageID and self.message == other.message #and... etc

Can you make it sealed, so that no one can inherit from it?

As far as I know, this is impossible. Python generally follows a "we're all adults here" philosophy. If a coder wants to inherit from a class or access an object's attributes, then they can; the best you can do is give a stern suggestion not to in the documentation (or put up small speed bumps, in the case of name mangling for "private" attributes).

Community
  • 1
  • 1
Kevin
  • 74,910
  • 12
  • 133
  • 166
  • 1
    It's possible to prevent your class from being subclassed if you [write it in C and use CPython](http://stackoverflow.com/questions/10061752/which-classes-cannot-be-subclassed). – TigerhawkT3 Jul 28 '15 at 17:15
  • Don't return False from `__eq__` because of the rhs being the wrong type: return `NotImplemented` instead. Think of it as three value logic: 'yes', 'no' or 'I don't know' - and if you don't know, python might be able to ask the other object if it knows. – lvc Jul 28 '15 at 22:34
0

You have several questions, so let us take a look at them in the order they were asked:

  1. The closest thing to a Bag that you can find in Python's standard library is collections.Counter. However, it is not designed for concurrent usage and has different methods than the ConcurrentBag class you are familiar with. By inheriting from collection.Counter and specifying a custom metaclass designed to make classes thread-safe, it is possible to easily recreate the ConcurrentBag class for use in Python.
  2. The getter for your thread-safe collection can be duplicated in Python using the property decorator. Since you are making the information read-only, this is the easiest method to implement. You can see the property in the code below just below the initializer for the Message class.
  3. Yes, it is possible to check if two objects are equal to each other in Python. You have to define a special __eq__ method as part of the class. The version shown in the example code is just below the aforementioned property. It first checks the type of the instance being compared, and then looks through all the other attributes of each instance except for __time.
  4. Python has no native concept of sealing a class, but a simple metaclass can provide very similar functionality. By setting the metaclass of a class to SealedMeta, any classes that try to inherit from from the "sealed" class will cause a RuntimeError to be raised. Please be aware, though, that anyone with access to the source code of your class could remove its sealed designation.

The following classes and metaclasses should solve the issues you were facing. They are as follows:

  • SealedMeta is a metaclass that can be used to mark a class as being sealed. Any class that tries to inherit from a sealed class will fail to be constructed since a RuntimeError exception will be generated. Because the requirements are so simple, this metaclass is significantly simpler than the second shown in the example code.
  • Message is an attempt at recreating the class you wrote in C#. The arguments and attributes have slightly different names, but the same basic idea is present. Since types are not statically specified in Python, only one equality-checking method is needed in this class. Note how the class is marked as sealed with (metaclass=SealedMeta) at the top.
  • AtomicMeta was a challenging metaclass to write, and I am not sure that it is completely correct. Not only does it have to make the methods of a class thread-safe, it must process all bases that the class inherits from. Since old-style super calls may be present in those bases, the metaclass attempts to fix the modules they are located in.
  • ConcurrentBag inherits from collections.Counter since it is most like a bag from other languages. Since you wanted the class to be thread-safe, it must use the AtomicMeta metaclass so that the methods of it and its parents are changed into atomic operations. A to_array method is introduced to improve its API.

Without further delay, here is the code referenced above. Hopefully, it will be a help both to you and others:

#! /usr/bin/env python3
import builtins
import collections
import functools
import inspect
import threading


class SealedMeta(type):
    """SealedMeta(name, bases, dictionary) -> new sealed class"""
    __REGISTRY = ()

    def __new__(mcs, name, bases, dictionary):
        """Create a new class only if it is not related to a sealed class."""
        if any(issubclass(base, mcs.__REGISTRY) for base in bases):
            raise RuntimeError('no class may inherit from a sealed class')
        mcs.__REGISTRY += (super().__new__(mcs, name, bases, dictionary),)
        return mcs.__REGISTRY[-1]


class Message(metaclass=SealedMeta):
    """Message(identifier, message, source, kind, time) -> Message instance"""

    def __init__(self, identifier, message, source, kind, time):
        """Initialize all the attributes in a new Message instance."""
        self.__identifier = identifier
        self.__message = message
        self.__source = source
        self.__kind = kind
        self.__time = time
        self.__destination = ConcurrentBag()

    @property
    def destination(self):
        """Destination property containing serialized Employee instances."""
        return self.__destination.to_array()

    def __eq__(self, other):
        """Return if this instance has the same data as the other instance."""
        # noinspection PyPep8
        return isinstance(other, type(self)) and \
               self.__identifier == other.__identifier and \
               self.__message == other.__message and \
               self.__source == other.__source and \
               self.__kind == other.__kind and \
               self.__destination == other.__destination


class AtomicMeta(type):
    """AtomicMeta(name, bases, dictionary) -> new thread-safe class"""
    __REGISTRY = {}

    def __new__(mcs, name, bases, dictionary, parent=None):
        """Create a new class while fixing bases and all callable items."""
        final_bases = []
        # Replace bases with those that are safe to use.
        for base in bases:
            fixed = mcs.__REGISTRY.get(base)
            if fixed:
                final_bases.append(fixed)
            elif base in mcs.__REGISTRY.values():
                final_bases.append(base)
            elif base in vars(builtins).values():
                final_bases.append(base)
            else:
                final_bases.append(mcs(
                    base.__name__, base.__bases__, dict(vars(base)), base
                ))
        class_lock = threading.Lock()
        # Wrap all callable attributes so that they are thread-safe.
        for key, value in dictionary.items():
            if callable(value):
                dictionary[key] = mcs.__wrap(value, class_lock)
        new_class = super().__new__(mcs, name, tuple(final_bases), dictionary)
        # Register the class and potentially replace parent references.
        if parent is None:
            mcs.__REGISTRY[object()] = new_class
        else:
            mcs.__REGISTRY[parent] = new_class
            source = inspect.getmodule(parent)
            *prefix, root = parent.__qualname__.split('.')
            for name in prefix:
                source = getattr(source, name)
            setattr(source, root, new_class)
        return new_class

    # noinspection PyUnusedLocal
    def __init__(cls, name, bases, dictionary, parent=None):
        """Initialize the new class while ignoring any potential parent."""
        super().__init__(name, bases, dictionary)

    @staticmethod
    def __wrap(method, class_lock):
        """Ensure that all method calls are run as atomic operations."""

        @functools.wraps(method)
        def atomic_wrapper(self, *args, **kwargs):
            with class_lock:
                try:
                    instance_lock = self.__lock
                except AttributeError:
                    instance_lock = self.__lock = threading.RLock()
            with instance_lock:
                return method(self, *args, **kwargs)

        return atomic_wrapper


# noinspection PyAbstractClass
class ConcurrentBag(collections.Counter, metaclass=AtomicMeta):
    """ConcurrentBag() -> ConcurrentBag instance"""

    def to_array(self):
        """Serialize the data in the ConcurrentBag instance."""
        return tuple(key for key, value in self.items() for _ in range(value))

After doing a little research, you might find that there are alternatives for sealing a class. In Java, classes are marked final instead of using a sealed keyword. Searching for the alternative keyword could bring you to Martijn Pieters' answer which introduces a slightly simpler metaclass for sealing classes in Python. You can use the following metaclass inspired by his the same way that SealedMeta is currently used in the code:

class Final(type):
    """Final(name, bases, dictionary) -> new final class"""

    def __new__(mcs, name, bases, dictionary):
        """Create a new class if none of its bases are marked as final."""
        if any(isinstance(base, mcs) for base in bases):
            raise TypeError('no class may inherit from a final class')
        return super().__new__(mcs, name, bases, dictionary)

The Final metaclass is useful if you want to seal a class and prevent others from inheriting from it, but a different approach is needed if attributes of a class are intended to be final instead. In such a case, the Access metaclass can be rather useful. It was inspired by this answer to the question Prevent function overriding in Python. A modified approach is taken in the metaclass shown below that is designed to take into account attributes of all kinds.

import collections
import threading


# noinspection PyProtectedMember
class RLock(threading._RLock):
    """RLock() -> RLock instance with count property"""

    @property
    def count(self):
        """Count property showing current level of lock ownership."""
        return self._count


class Access(type):
    """Access(name, bases, dictionary) -> class supporting final attributes"""
    __MUTEX = RLock()
    __FINAL = []

    @classmethod
    def __prepare__(mcs, name, bases, **keywords):
        """Begin construction of a class and check for possible deadlocks."""
        if not mcs.__MUTEX.acquire(True, 10):
            raise RuntimeError('please check your code for deadlocks')
        # noinspection PyUnresolvedReferences
        return super().__prepare__(mcs, name, bases, **keywords)

    @classmethod
    def final(mcs, attribute):
        """Record an attribute as being final so it cannot be overridden."""
        with mcs.__MUTEX:
            if any(attribute is final for final in mcs.__FINAL):
                raise SyntaxError('attributes may be marked final only once')
            mcs.__FINAL.append(attribute)
            return attribute

    def __new__(mcs, class_name, bases, dictionary):
        """Create a new class that supports the concept of final attributes."""
        classes, visited, names = collections.deque(bases), set(), set()
        # Find all attributes marked as final in base classes.
        while classes:
            base = classes.popleft()
            if base not in visited:
                visited.add(base)
                classes.extend(base.__bases__)
                names.update(getattr(base, '__final_attributes__', ()))
        # Verify that the current class does not override final attributes.
        if any(name in names for name in dictionary):
            raise SyntaxError('final attributes may not be overridden')
        names.clear()
        # Collect the names of all attributes that are marked as final.
        for name, attribute in dictionary.items():
            for index, final in enumerate(mcs.__FINAL):
                if attribute is final:
                    del mcs.__FINAL[index]
                    names.add(name)
                    break
        # Do a sanity check to ensure this metaclass is being used properly.
        if mcs.__MUTEX.count == 1 and mcs.__FINAL:
            raise RuntimeError('final decorator has not been used correctly')
        mcs.__MUTEX.release()
        dictionary['__final_attributes__'] = frozenset(names)
        return super().__new__(mcs, class_name, bases, dictionary)

As a demonstration of how to use the Access metaclass, this example will raise a SyntaxError:

class Parent(metaclass=Access):
    def __init__(self, a, b):
        self.__a = a
        self.__b = b

    @Access.final
    def add(self):
        return self.__a + self.__b

    def sub(self):
        return self.__a - self.__b


class Child(Parent):
    def __init__(self, a, b, c):
        super().__init__(a, b)
        self.__c = c

    def add(self):
        return super().add() + self.__c
Noctis Skytower
  • 21,433
  • 16
  • 79
  • 117