简体   繁体   中英

Assigning an attribute value in a PYTHON class with class methods

This is a very basic question relating to setting attributes that I don't find a definitive answer to. I have a python class as follows

class circle():
    
    PI = 3.14
    
    def __init__(self,radius):
        self.radius   = radius
        # 1. first way of assigning an atribute
        self.area     = self._area()
        # 2. second way of assigning an attribute (running a method)
        self._squarearea(self.radius,self.area)
        # 3. third way of assigning an attribute
        self.diameter = self._diameter(self.radius)
        
    def _area(self):
        return self.radius * self.PI * self.PI
    
    def _squarearea(self,radius,area):
        self.sqarea = radius * radius * 4 - area
        
    def _diameter(self,radius):
        return self.radius *2

    def giveme4timesradius(self):
        return 4*self.radius

I listed 4 ways to assign attributes or calculate data. In my particular real example every of the methods performs complicated calculations that are better to keep compartmentalised for reading code purposes.

Every of the assignments I do comes with certain disadvantages

  • way 1: self.area = self._area() . The reader does not now in the init method how many variables takes area to make the calculation. A class method is run but the reader would have to dig into the code to see what is calculated and how

  • way 2: self._squarearea(self.radius,self.area) The reader in this case does not know what the function does, not even if the function sets a new attribute as it is the case. On the contrary at least it is indicated that radius and area are passed to the function. Actually the two variables passed are only an indication since within the class method all the class attributes are available.

  • way 3: self.diameter = self._diameter(self.radius) Here the reader knows that the radius is pass and also knows that an attribute is assigned (diameter)

  • way 4: A last way can be thought offering the consumer of the class to call a method giveme4timesradius() , but this is inconvenient since I want all to be previously calculated. And it would have to be calculated every time it is called.

There are multiple SO questions about attributes and classes and tutorials (see some bellow), but I did not see this comparison anywhere. I think I don't need any setter and getter because I just want to run all from the start, depending on multiples conditions some attributes will be calculated with some class methods or others.

So what of the ways is the "pythonic" way? Or any of those?

References:
https://www.toptal.com/python/python-class-attributes-an-overly-thorough-guide
https://realpython.com/lessons/adding-attributes-python-class/
https://www.python-course.eu/python3_class_and_instance_attributes.php
https://medium.com/shecodeafrica/managing-class-attributes-in-python-c42d501c5ee0

The second option is confusing, as you said, because you would need to go to the method definition to understand there is an assignment to a new variable in the class instance. The first option is somewhat clearer in that sense, but I would still like to know what is the necessary input arguments to that method.

The third option however is wrong for two reasons. One, you pass the radius but you still access self.radius, so the argument is redundant. Second, even if you use the argument, it's supposed to be a static method decorated with @staticmethod, because you don't use the class instance (self).

Therefore, I would go with a static method that utilizes the proper input variables it needs. Something like:

class Circle:

    PI = 3.14

    def __init__(self, radius):
        self.radius = radius
        self.area = self.compute_area(self.radius)

    @staticmethod
    def compute_area(radius):
        return radius * (Circle.PI**2)

This is a good use case for @property decorator.

class Circle():
    
    PI = 3.14
    
    def __init__(self,radius):
        self.radius   = radius
    
    @property
    def area(self):
        return self.radius * (self.PI ** 2)
    
    @property
    def squarearea(self):
        return (self.radius ** 2)  * 4 - self.area
    
    @property
    def diameter(self):
        return self.radius *2
    
    @property
    def giveme4timesradius(self):
        return 4*self.radius

Also as my preference I would use @dataclass:

from dataclasses import dataclass
from typing import ClassVar

@dataclass
class Circle():

    radius: float
    PI: ClassVar[float] = 3.14

    # No need __init__ -> handled by dataclass
    
    @property
    def area(self):
        return self.radius * (self.PI ** 2)
    
    @property
    def squarearea(self):
        return (self.radius ** 2)  * 4 - self.area
    
    @property
    def diameter(self):
        return self.radius *2
    
    @property
    def giveme4timesradius(self):
        return 4*self.radius

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM