sasuke
sasuke

Reputation: 6751

Class variables of same type as the class

Messing around with the typical Point class example when learning Python, I noticed that for some reason I can't have a class level (static variable) of the same type as that of the class. E.g.

class Point:

  ORIGIN = Point() # doesn't work

  def __init__(self, x=0, y=0):
    self.x = x
    self.y = y

while the same works in Java:

class Point {
    private static final Point ORIGIN = new Point(0, 0);
    
    private int x;
    private int y;
    
    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }
}

The question is: is there any way of achieving the same in Python. Right now I am relying on module level variables and I'm not liking that solution. Also, is there any reason why it can't be done in the body of the class?

Upvotes: 19

Views: 7161

Answers (5)

Howard Lovatt
Howard Lovatt

Reputation: 998

15 years after last response!

People arm't acknowledging how strange it is that you can call methods, including the constructor, from other methods but can't do it for a static field.

There is a work around - that you might hate!

class Point:

  __ORIGIN = None

  def __new__(cls, x=0, y=0):
    return Point.__ORIGIN if x == 0 and y == 0 and Point.__ORIGIN is not None else super().__new__(cls)

  def __init__(self, x=0, y=0):
    if self is Point.__ORIGIN:
      return
    self.x = x
    self.y = y

Point._Point__ORIGIN = Point()

print('\nPoint test')
print(f'{Point._Point__ORIGIN=}')
print(f'{Point() is Point()=}')

The last line prints True, demonstrating that it works.

The user of Point uses Point() when they want the origin and it always returns the cached version (from Point_Point__ORIGIN).

Upvotes: 0

lqc
lqc

Reputation: 7328

You can't create an instance of a class, until that class is actually created, which is after the class body is evaluated (note: it's executed like normal Python code).

The same goes for your Java example: ClassLoader creates the Point class and then executes the code from static fields.

A rough equivalent of a class loader in Python is the metaclass, so you could do something like this:

def class_with_static(name, bases, body):
    static_block = body.pop("__static__", None)
    klass = type(name, bases, body)
    if static_block:
        static_block(klass)
    return klass

class Point(object):
    __metaclass__ = class_with_static

    def __static__(cls):
        cls.ORIGIN = cls()

    def __init__(self, x=0, y=0):
        self.x = x
        self.y = y

assert isinstance(Point.ORIGIN, Point)
assert Point.ORIGIN.x == Point.ORIGIN.y == 0
assert not hasattr(Point, "__static__")

Of course this will have some other consequences, like: all subclasses of Point will have an ORIGIN attribute of their own. So you probably just want to do it like others shown :)

Upvotes: 5

martineau
martineau

Reputation: 123393

You could do this with a class decorator, although I'm not sure what a good name for it would be. Here's how:

def add_class_var(name, *args, **kwrds):
    def decorator(cls):
        setattr(cls, name, cls(*args, **kwrds))
        return cls
    return decorator

@add_class_var('ORIGIN')
class Point:
    def __init__(self, x=0, y=0):
        self.x = x
        self.y = y

print Point.ORIGIN, Point.ORIGIN.x, Point.ORIGIN.y
# <__main__.Point instance at 0x00B5B418> 0 0

Although not utilized in the above code, you can also pass arguments to the class's __init__() constructor indirectly through the decorator.

Upvotes: 4

Karl Knechtel
Karl Knechtel

Reputation: 61478

Assign it after the fact:

class Point:
  def __init__(self, x=0, y=0):
    self.x = x
    self.y = y


Point.ORIGIN = Point()

Upvotes: 9

Ignacio Vazquez-Abrams
Ignacio Vazquez-Abrams

Reputation: 798456

class Point(object):
  pass

Point.ORIGIN = Point()

Upvotes: 16

Related Questions