Inheriting behaviours for set and frozenset seem to differ
Asked Answered
O

1

7

Can someone explain the following behaviour:

class derivedset1(frozenset):
    def __new__(cls,*args):
        return frozenset.__new__(cls,args)  

class derivedset2(set):
    def __new__(cls,*args):
        return set.__new__(cls,args)    

a=derivedset1('item1','item2') # WORKS 
b=derivedset2('item1','item2') # DOESN'T WORK

Traceback (most recent call last):
  File "inheriting-behaviours.py", line 12, in <module>
    b=derivedset2('item1','item2') # DOESN'T WORK
TypeError: derivedset2 expected at most 1 arguments, got 2

This is surprising to me that you can alter the constructor of a frozen set whereas it is not possible for the constructor of a mutable set.

Omor answered 31/1, 2011 at 11:32 Comment(1)
Interesting data point: b=derivedset2(['item1','item2']) works.Betteann
B
4

From the Python documentation:

If __new__() returns an instance of cls, then the new instance’s __init__() method will be invoked like __init__(self[, ...]), where self is the new instance and the remaining arguments are the same as were passed to __new__().

set.__init__ only takes one argument, an iterable specifying the initial set contents. Therefore, you should add your own initializer which takes all additional arguments and supplies them as the initial set values:

class derivedset2(set):
    def __new__(cls,*args):
        return set.__new__(cls,*args)

    def __init__(self, *initial_values):
        set.__init__(self, initial_values)

Notice that you should overwrite __init__ and refrain from implementing __new__ unless you want to implement object caching, singletons, or similar weird stuff. Your subclassing works for frozenset precisely because frozenset does profit from object caching, i.e. the Python interpreter only needs one frozenset instance for two frozenset objects with the same content.

In general, you should refrain from sub-classing built-in classes, especially if your semantics are incompatible (in this case, set([]) and derivedset2([]) return totally different results).

Billie answered 31/1, 2011 at 11:56 Comment(2)
The reason frozenset uses __new__ isn't caching, but because it is immutable. If the elements were consumed by __init__, the class would have to be somewhat-mutable. Then fs = frozenset.__new__(frozenset) would create an empty frozenset that could be filled (mutated) with fs.__init__([1, 2, 3]). This would happen each time during subclassing.Conney
The value for immutable objects has to be established at the point of creation -- in the __new__() method -- precisely because it can't be done later in the __init__() method.Matriarchy

© 2022 - 2024 — McMap. All rights reserved.