Class Decorators

python

#1

OK this isn’t specific to any task on here, but I’ve done a bit of research and found and experimented with class decorators - specifically @property and @classmethod, and I was wondering why this section of code wouldn’t work?:

@property
@classmethod
def coordinates(cls):
    ''' Returns a list of existing coordinates '''
    return [f"{var}({value.x}, {value.y})" for var, value in globals().items() if isinstance(value, cls)]

Basically what I’m trying to do here is search for variables (by using globals()) that are part of the class which this method is inside of (Coordinate), and return a list of the Coordinate instances that were found in globals(). I’m not sure why this isn’t working as

@property
def coordinates(self):
    ''' Returns a list of existing coordinates '''
    return [f"{var}({value.x}, {value.y})" for var, value in globals().items() if isinstance(value, self.__class__)]

Does exactly what I wanted the previous method to do, but I hate this format as you can’t easily see that it’s supposed to be a @classmethod as it needs self as a parameter, and uses self.__class__ instead of cls to specify the class which the variables need to be a part of to be put in the list. I’m guessing it’s just decorator conflict but I’m hoping I’ve made a mistake.

The method with the @classmethod decorator returns

>>> property object at 0x05CEAAE0

Whereas the other method does what I expected: (Two instances of the class were created)

>>> ['point_a(3, 4)', 'point_b(0, 0)']

Just in case you require the whole class template I’ll post it aswell:

class Coordinate(object):

    def __init__(self, x=0, y=0):
        ''' Creates new coordinate. Set to origin by default '''
        self.x = x
        self.y = y

    def __repr__(self):
        return f"Coordinate({self.x}, {self.y})"

    def __str__(self):
        return f"({self.x}, {self.y})"

    
##  @property
##  @classmethod
##  def coordinates(cls):
##      ''' Returns a list of existing coordinates '''
##      return [f"{var}({value.x}, {value.y})" for var, value in globals().items() if isinstance(value, cls)]

    @property
    def coordinates(self):
        ''' Returns a list of existing coordinates '''
        return [f"{var}({value.x}, {value.y})" for var, value in globals().items() if isinstance(value, self.__class__)]
        
    @classmethod
    def from_array(cls, array):
        ''' Initialises an instance from a list/tuple '''
        assert len(array) == 2
        return cls(*array)

    @classmethod
    def from_dict(cls, dct):
        ''' Initialises an instance from a dictionary '''
        assert isinstance(dct, dict)
        assert sorted(dct.keys()) == ['x', 'y']
        return cls(**dct)

    def distance(self, other):
        ''' Returns the distance (of the line) between the coordinates (self) and (other) '''
        return (abs(self.x - other.x)**2 + abs(self.y - other.y)**2) ** 0.5

    def gradient(self, other):
        ''' Returns the gradient of the line between the coordinates (self) and (other) '''
        return (self.y - other.y) / (self.x - other.x)

    def translate(self, x=0, y=0):
        ''' Translates the coordinate by x and y '''
        self.x += x
        self.y += y

    def reset(self):
        self.x = self.y = 0

Thanks in advance


#2

i put your code here:

https://repl.it/repls/RustyDefiantLizard

why combine @property and @classmethod?

@property is only a getter function to get a class property (for example x or y)

if only using @classmethod, its all working fine, yes we have to call it as method (using parentheses), but that is not really a problem

both decorators are designed to do very different things


#3

Well I would like it to be a class attribute rather than method but sure why not


#4

then you should go for the @property you already had, without the @classmethod

its conflicting each other, @property wants be a property, @classmethod wants to be a method. It can’t be both.

You can have (like i did, see the repl i posted), a method and a property, and decide which you want to use on the class or instance


#5

Oh that makes a lot more sense now. I guess I thought what I expected to work should work because it looks like you pass a method to @property, so I saw no reason why you couldn’t pass a class method to it


#6

we could this:

https://repl.it/repls/RustyDefiantLizard

now we make a property of the class method

but this only works on class instance (point_a or point_b) not on the class itself, for this you could then use the @classmethod.

EDIT: of course, properties (with @property of course only work on instances, that was your plan along, right? )

look here:

https://www.programiz.com/python-programming/property

scroll down to: deeper into @property, you will see its just a function which we can use as decorator (sugar coated syntax)


#7

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.