10

Why python does not provide UserSetclass to extend and define user defined Set. It does provide UserDict,UserList, UserString but no UserSet.

I'm referring to Python Documentation

Jyotirup
  • 2,882
  • 9
  • 30
  • 38
  • 1
    Take this with a grain of salt, but `"The need for this class has been partially supplanted by the ability to subclass directly from dict/list/string"` (in the docs you linked to) suggests that these classes existed before it was possible to directly subclass the built-in datatypes. It is possible that `set` was introduced later so `UserSet` was never needed – DeepSpace Jul 21 '19 at 11:26
  • 2
    @DeepSpace If you refer to Fluent Python by Luciano Ramalho, He clearly says not to extend from Dict directly but to extend from UserDict. I quote "The main reason why it’s preferable to subclass from UserDict rather than from dict is that the built-in has some implementation shortcuts that end up forcing us to override methods that we can just inherit from UserDict with no problems." – Jyotirup Jul 21 '19 at 11:30
  • Some threads about UserDict: https://stackoverflow.com/questions/25464647/list-vs-userlist-and-dict-vs-userdict and https://stackoverflow.com/questions/7148419/subclass-dict-userdict-dict-or-abc say that UserDict is useful, because it has basic implementation of all methods. So having UserSet defined in the same manner can be really nice. – sanyassh Oct 11 '19 at 10:56
  • 3
    @Jyotirup: That's a pretty weak argument. Neither `dict` nor `UserDict` has any documentation regarding what methods rely on what other methods, but `dict` has a simple rule: they're all independent. In contrast, `UserDict` has a much more complex and Python-version-dependent relationship between its methods, making it much harder to correctly override anything without causing infinite recursion or affecting other methods in unexpected ways. – user2357112 Oct 12 '19 at 21:37
  • 3
    The bigger problem with overriding dict methods isn't having to override more methods than you'd like. It's the cases where other core language code sees a dict and goes straight for the dict internals without calling your methods at all. No amount of overriding will help with that. (The language core should be using more `PyDict_CheckExact` and less `PyDict_Check`.) – user2357112 Oct 12 '19 at 21:39
  • As an example of infinite recursion and version dependence, say you want a dict-like object that prints any value deleted from the dict with `del d[key]`. You subclass UserDict and implement `__delitem__` as `print self.pop(key)`. [It works!](http://ideone.com/JBdbot) Then you try to upgrade to Python 3. [It goes into infinite recursion and overflows the stack.](http://ideone.com/jFwOWh) This doesn't happen if you subclass `dict`. – user2357112 Oct 12 '19 at 22:41

2 Answers2

6

PEP 3108 provides some insight into why no UserSet or similar implementations were added in similar fashion to UserDict and the like.

Guido pronounced that "silly old stuff" is to be deleted from the stdlib for Py3K. This is open-ended on purpose. Each module to be removed needs to have a justification as to why it should no longer be distributed with Python.

UserDict, UserList, and UserString are all listed in the "Obsolete" section of PEP 3108 with the following reason noted and an indication that they were moved to the collections module.

Not as useful since types can be a superclass

Given that they were considered "obsolete", it is unsurprising that similar classes were not implemented for other types such as sets.

Since types can be superclasses and abstract base classes provide a reasonable alternative for creating subclasses of various set, sequence, mapping, and other types with significant changes that would be difficult to implement by extending the built-in types themselves, it seems unnecessary to include these "starter" classes for each and every type beyond what is already provided in collections.abc.

Following is a very basic implementation of UserSet with a data attribute that stores the contents of the class in a real set (similar to the approach taken with UserDict and the like). While it might be useful to you to have such an implementation included in the standard library, it is not terribly difficult to implement yourself (and if everything that was potentially "useful" to someone was included in the standard library, Python would become awfully bloated).

from collections.abc import Hashable, MutableSet

class UserSet(Hashable, MutableSet):
    __hash__ = MutableSet._hash

    def __init__(self, iterable=()):
        self.data = set(iterable)

    def __contains__(self, value):
        return value in self.data

    def __iter__(self):
        return iter(self.data)

    def __len__(self):
        return len(self.data)

    def __repr__(self):
        return repr(self.data)

    def add(self, item):
        self.data.add(item)

    def discard(self, item):
        self.data.discard(item)


s = UserSet([1,1,2,3])
print(s)
# {1, 2, 3}
benvc
  • 14,448
  • 4
  • 33
  • 54
  • Can you please also address my comment: https://stackoverflow.com/questions/57132624/why-there-is-no-userset-class-defined-in-python#comment103034922_57132624. I think that having UserSet will be useful because with it we don't need to reimplement all methods, just those we really want. – sanyassh Oct 11 '19 at 22:34
  • 6
    @sanyash - almost any code that is already written for us would be "useful", but I am not sure that is a good enough reason for it to be included in the standard library. See answer edit. – benvc Oct 12 '19 at 21:17
  • yes, it makes sense, thanks. I will keep the question bounty-ed for some time, maybe there can appear another interesting answer. – sanyassh Oct 12 '19 at 21:21
-2

I'm not sure whether it is widely known or not but I managed to create and successfully use a subclass of the built-in set without the need to manually manipulate with the underlying data using the self.data attribute or similar. Working with the data is mediated using the self attribute itself. I'm yet to find the drawbacks of such solution.

class IntegerSet(set):
    def sum(self) -> int:
        return sum(self)


iset = IntegerSet((1, 1, 2, 3))
iset.add(4)
print(iset.sum())  # 10
Jeyekomon
  • 2,878
  • 2
  • 27
  • 37