Reputation: 6751
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
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
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
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
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
Reputation: 798456
class Point(object):
pass
Point.ORIGIN = Point()
Upvotes: 16