Property and method with the same name in Cython
print sprite.scale # 1. Read the current scale
sprite.scale = (2, 2) # 2. Set a new scale
sprite.scale(3, 4) # 3. Call the scale() method
As you can see, in practice it shouldn't be a problem that the property and the method have the same name: we can infer which one the user is calling from the context.
However, both Python and Cython don't directly allow you to define a property and a method with the same name.
Due to Python's dynamic nature, the latter definition will simply override the former.
Methods actually are attributes like any other, they just happen to be some kind of function that allows the object.method()
syntax.
Obviously, it's important to be as consistent as possible with SFML's original API and my naming convention, so I tried to find a trick to achieve the desired effect.
The result isn't really beautiful, but it's quite nifty.
Basically, the scale
property's getter now returns an object that can behave in two ways:
- The object itself behaves like a tuple, since its class inherits
tuple
. This gives access to the current scale of the drawable. - The object's class also overrides the
__call__
operator, which will now call theScale()
method on the underlying C++ object. This means thatsprite.scale(5, 5)
will behave as expected.
The property setter doesn't require anything special, it simply sets the current scale. The actual code looks like this:
cdef class Drawable:
property scale:
def __get__(self):
cdef decl.Vector2f scale = self.p_this.GetScale()
return ScaleWrapper(self, scale.x, scale.y)
def __set__(self, value):
cdef decl.Vector2f v = convert_to_vector2f(value)
self.p_this.SetScale(v.x, v.y)
# Can't make this an extension type because Cython currently doesn't
# allow them to inherit from tuple. Inheriting from int would work,
# for example.
class ScaleWrapper(tuple):
def __new__(cls, Drawable drawable, float x, float y):
return tuple.__new__(cls, (x, y))
def __init__(self, Drawable drawable, float x, float y):
self.drawable = drawable
def __call__(self, float x, float y):
self.drawable._scale(x, y)
The goal is achieved, at the cost of adding some more complicated code. Beside the fact that it's more difficult to understand, it's also probably slightly slower than direct attribute access. If that causes any performance problem in practice, a quick and dirty solution will be to provide other methods that give direct access to the C++ equivalent.