2

I wrote a class that can handle integers with arbitrary precision (just for learning purposes). The class takes a string representation of an integer and converts it into an instance of BigInt for further calculations.

Often times you need the numbers Zero and One, so I thought it would be helpfull if the class could return these. I tried the following:

class BigInt():
    zero = BigInt("0")

    def __init__(self, value):
        ####yada-yada####

This doesn't work. Error: "name 'BigInt' is not defined"

Then I tried the following:

class BigInt():

    __zero = None

    @staticmethod
    def zero():
        if BigInt.__zero is None:
            BigInt.__zero = BigInt('0')
        return BigInt.__zero


    def __init__(self, value):
        ####yada-yada####

This actually works very well. What I don't like is that zero is a method (and thus has to be called with BigInt.zero()) which is counterintuitive since it should just refer to a fixed value.

So I tried changing zero to become a property, but then writing BigInt.zero returns an instance of the class property instead of BigInt because of the decorator used. That instance cannot be used for calculations because of the wrong type.

Is there a way around this issue?

coder2k
  • 47
  • 2
  • 7
  • Can't you create a class, that inherits that class. And thus instanciating your subclass it can return itself with a active instance of parent with `"0"` as value? `class initator(BigInt): def __init__(self): super(initator, self).__init__("8")`. – Torxed Feb 04 '19 at 16:41

2 Answers2

6

A static property...? We call a static property an "attribute". This is not Java, Python is a dynamically typed language and such a construct would be really overcomplicating matters.

Just do this, setting a class attribute:

class BigInt: 
    def __init__(self, value): 
        ... 

BigInt.zero = BigInt("0")

If you want it to be entirely defined within the class, do it using a decorator (but be aware it's just a more fancy way of writing the same thing).

def add_zero(cls):
    cls.zero = cls("0")
    return cls

@add_zero
class BigInt:
    ...
wim
  • 338,267
  • 99
  • 616
  • 750
  • I'd generally agree, `static` in combination with `property` is just an attribute of fixed value. However, the more general issue of having real `@property` properties (descriptor protocol attributes) at the class level is intriguing, and should be accomplishable by assigning such a descriptor object to the metaclass. (I have not tested this, though. It'd also make it only accessible from the class level, not instance level.) – amcgregor Feb 04 '19 at 17:38
  • Thank you! Didn't think it was **this** obvious, but coming from other languages the python approach is sometimes hard to understand. Can you explain how it would work with a decorator? I tried the following which results in a recursion depth error: ``def big_int_decorator(cl): def wrapper(*args): value = cl(*args) value.z = BigInt("0") return value return wrapper`` – coder2k Feb 04 '19 at 17:54
  • @wim Thanks alot! – coder2k Feb 04 '19 at 19:21
1

The question is contradictory: static and property don't go together in this way. Static attributes in Python are simply ones that are only assigned once, and the language itself includes a very large number of these. (Most strings are interred, all integers < a certain value are pre-constructed, etc. E.g. the string module.). Easiest approach is to statically assign the attributes after construction as wim illustrates:

class Foo:
    ...

Foo.first = Foo()
...

Or, as he further suggested, using a class decorator to perform the assignments, which is functionally the same as the above. A decorator is, effectively, a function that is given the "decorated" function as an argument, and must return a function to effectively replace the original one. This may be the original function, say, modified with some annotations, or may be an entirely different function. The original (decorated) function may or may not be called as appropriate for the decorator.

def preload(**values):
    def inner(cls):
        for k, v in values.items():
            setattr(cls, k, cls(v))

        return cls

    return inner

This can then be used dynamically:

@preload(zero=0, one=1)
class Foo:
    ...

If the purpose is to save some time on common integer values, a defaultdict mapping integers to constructed BigInts could be useful as a form of caching and streamlined construction / singleton storage. (E.g. BigInt.numbers[27])

However, the problem of utilizing @property at the class level intrigued me, so I did some digging. It is entirely possible to make use of "descriptor protocol objects" (which the @property decorator returns) at the class level if you punt the attribute up the object model hierarchy, to the metaclass.

class Foo(type):
    @property
    def bar(cls):
        print("I'm a", cls)
        return 27

class Bar(metaclass=Foo):
    ...

>>> Bar.bar
I'm a <class '__main__.Bar'>
<<< 27

Notably, this attribute is not accessible from instances:

>>> Bar().bar
AttributeError: 'Bar' object has no attribute 'bar'

Hope this helps!

amcgregor
  • 1,228
  • 12
  • 29
  • 1
    What you describing with the metaclass stuff is more like a property for a classmethod than a property for a staticmethod. This practice and all the associated pitfalls are discussed in detail over here: [Using property() on classmethods](https://stackoverflow.com/q/128573/674039) – wim Feb 04 '19 at 19:29
  • @wim There are 14 answers to that question, top answer mentions Python 2.2 (beyond ancient) and doesn't provide full functionality (no setters/deleters), second most voted answer _does_ mention metaclasses, doesn't point out important issue of access via instances, a number utilize `@property` w/ access from the instance, which is explicitly the opposite of what I'm going for here, etc. Are there specific notes of caution you can point at? (Though yes, this is technically more of a property `classmethod` than `staticmethod`.) – amcgregor Feb 04 '19 at 20:22
  • The [currently top-voted](https://stackoverflow.com/a/1383402/674039) answer is broken, for reasons discussed in the comments there. This [2016 answer](https://stackoverflow.com/a/39542816/674039) looks good. – wim Feb 04 '19 at 20:27