Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create Classes in Python with highly constrained instances

Tags:

python

math

class

In Python, there are examples of built-in classes with highly constrained instances. For example, "None" is the only instance of its class, and in the bool class there are only two objects, "True" and "False" (I hope I am more-or-less correct so far).

Another good example are integers: if a and b are instances of the int type then a == b implies that a is b.

Two questions:

  1. How does one create a class with similarly constrained instances? For example we could ask for a class with exactly 5 instances. Or there could be infinitely many instances, like type int, but these are not arbitrary.

  2. if integers form a class, why does int() give the 0 instance? compare this to a user-defined class Cl, where Cl() would give an instance of the class, not a specific unique instance, like 0. Shouldn't int() return an unspecified integer object, i.e. an integer without a specified value?

like image 461
Marco Avatar asked Jun 30 '26 23:06

Marco


2 Answers

You're talking about giving a class value semantics, which is typically done by creating class instances in the normal way, but remembering each one, and if a matching instance would be created, give the already created instance instead. In python, this can be achieved by overloading a classes __new__ method.

Brief example, say we wanted to use pairs of integers to represent coordinates, and have the proper value semantics.

class point(object):
    memo = {}
    def __new__(cls, x, y):
        if (x, y) in cls.memo:         # if it already exists, 
            return cls.memo[(x, y)]    # return the existing instance
        else:                          # otherwise, 
            newPoint = object.__new__(cls) # create it, 
            newPoint.x = x             # initialize it, as you would in __init__
            newPoint.y = y             
            cls.memo[(x, y)] = newPoint # memoize it, 
            return newPoint            # and return it!

like image 182
SingleNegationElimination Avatar answered Jul 02 '26 11:07

SingleNegationElimination


Looks like #1 has been well answered already and I just want to explain a principle, related to #2, which appears to have been missed by all respondents: for most built-in types, calling the type without parameters (the "default constructor") returns an instance of that type which evaluates as false. That means an empty container for container types, a number which compares equal to zero for number types.

>>> import decimal
>>> decimal.Decimal()
Decimal("0")
>>> set()
set([])
>>> float()
0.0
>>> tuple()
()
>>> dict()
{}
>>> list()
[]
>>> str()
''
>>> bool()
False

See? Pretty regular indeed! Moreover, for mutable types, like most containers, calling the type always returns a new instance; for immutable types, like numbers and strings, it doesn't matter (it's a possible internal optimization to return new reference to an existing immutable instance, but the implementation is not required to perform such optimization, and if it does it can and often will perform them quite selectively) since it's never correct to compare immutable type instances with is or equivalently by id().

If you design a type of which some instances can evaluate as false (by having __len__ or __nonzero__ special methods in the class), it's advisable to follow the same precept (have __init__ [or __new__ for immutables], if called without arguments [[beyond self for __init__ and 'cls' for __new__ of course]], prepare a [[new, if mutable]] "empty" or "zero-like" instance of the class).

like image 37
Alex Martelli Avatar answered Jul 02 '26 13:07

Alex Martelli