Python Forum
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Abstraction Module (ABC)
#1
Greetings,

I started learning python about 6 months ago. I came from various other languages. So, the things that cause me the most difficulty are Abstraction, protected, private methods and properties and decorators.


1. In my example below,
from abc import ABC, abstractmethod
is the module I am using the standard for creating abstract classes?

2. How would I ensure that the 'name' property was set before it is called?


3. What do the combined decorators below do exactly?
    @property
    @abstractmethod
    def diet(self):
        pass
# Create Abstract class

from abc import ABC, abstractmethod

# Abstract Class
# Use protected variable self._name, self._food
class Animal(ABC):

    @property
    def food_eaten(self):
        return self._food

    @food_eaten.setter
    def food_eaten(self, food):
        if food in self.diet:
            self._food = food
        else:
            raise ValueError(f"You can't feed this animal with {food}.")


    @property
    @abstractmethod
    def diet(self):
        pass

    @abstractmethod
    def feed(self, time):
        pass

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, name):
        self._name = name


class Dog(Animal):

    @property
    def diet(self):
        return ['Dog Food', 'Steak', 'Pork', 'Chicken']


    def feed(self, time):
        print(f"Feeding {self._name} with {self._food} meat! At {time}")



class Cat(Animal):

    @property
    def diet(self):
        return ['Cat Food', 'Fish', 'Soup']

    def feed(self, time):
        print(f"Feeding {self._name} with {self._food} meat! At {time}")

nico = Dog()
nico.name = 'Nico'
nico.food_eaten = 'Dog Food'
print(nico.food_eaten)
print(nico.name)
nico.feed("10:10 AM")
Output:
Dog Food Nico Feeding Nico with Dog Food meat! At 10:10 AM
Thank you in advance,
Matt
Reply
#2
Read this to determine if you need abstract base classes at all. ABC was designed for a very particular need and most Python classes, even some that would be "abstract base classes" in the C++ sense, do not use it.

https://www.python.org/dev/peps/pep-3119/

The decorators are not combined. @property does something and @abstractmethod does something. They do the same something used together or individually. You should read about the property decorator as it is used quite extensively (too extensively in my opinion). You will find no shortage of articles describing how the decorator works and how to use it.

https://www.geeksforgeeks.org/python-property-function/

A method decorated using @abstractmethod must be defined in a subclass to create an instance of the class. Here I forget to give Bird a "feed()" method.
class Bird(Animal):

    @property
    def diet(self):
        return ['Worms', 'Seed', 'Insects']

pet = Bird()
Output:
TypeError: Can't instantiate abstract class Bird with abstract method feed
Give your class an __init__() method if you want to initialize attributes. __init__() is called automatically when you create an instance of the class.
class Animal(ABC):
    def __init__(self, name=None):
        self._name = self.__class__.__name__ if name is None else name
        self._food = self.diet[0]
Reply
#3
So, even if the class is Abstract, I should give it a constructor to initialize some attributes. I like using abstract classes to cut down on code reuse. However, in a derived class, I do like the fact that if I call a method that is not in the derived class, it accesses the one in the base class.

The @property and @abstractmethod are used here and other places I saw.

Thanks for the answer on how to enforce the name attribute. Maybe I'll raise an error if it's empty.

Basically, I want a base class, that allows me to override or use it's own methods and properties. That's why I choose abstract classes. Sometimes I just want to use the property or method in the base class. Other times I want to override it.

Is there a better way to do this?

Abstract classes in Python look very similar to interfaces in other languages.
Reply
#4
Here is a different example, which does many things. Abstract base class (with no constructor), derived class with constructor and super method. This gives me access to properties and methods that are not abstract as well as the abstract ones. I don't really need a constructor in abstract class but I can override it with one in my child class and call the phantom constructor as well with "super"



from abc import ABC, abstractmethod

# Abstract class. # info Call abstract methods from derived class.
class Aircraft(ABC):

    @abstractmethod
    def fly(self):
        pass

    @abstractmethod
    def land(self):
        print("All checks completed")

    @property
    def speed(self):
        return self.__speed

    @speed.setter
    def speed(self, speed):
        self.__speed = speed

class Jet(Aircraft):

    def __init__(self):
        super().__init__()

    def fly(self):
        print("My jet is flying")

    def land(self):
        super().land() # Call abstract class abstract method, then add print statement locally. Both are displayed.
        print("My jet has landed")

jet1 = Jet()
jet1.speed = 600 # Call speed setter from parent
print(jet1.speed)  # print speed getter from parent
jet1.land()
Output:
600 All checks completed My jet has landed
Reply
#5
You don't need ABC to make a base class. Any Python class can be the superclass for another class. Python's inheritance model is very flexible. You can even change inheritance during runtime! A lot of things that are done in strictly typed languages (abstract classes, interfaces) aren't needed in Python.

I strongly suggest more study on the @property decorator. When I first found it I used it everywhere. Now I use it almost nowhere. Even when I need a getter or a setter (or both) I find making a method provides more flexibility and often improved readability over using a property. For example, in a GUI program I can bind a button press to a method, but I cannot bind it to a property.

And remember that when you use the @abstract decorator you are forcing all subclasses to implement that method. If it isn't required, don't make the method abstract. It might be better to try calling the method and catch the name error. Don't make your code more restrictive than it has to be. The Pythonic way to do most things is try and catch the exception instead of designing for every possible need. This philosophy should also be used when designing classes.
Reply
#6
Actually, I just did some research and I think you're right. We shouldn't really use the base class abstract as defined by ABC. We can just use inheritance and decorators and achieve the same thing. The only thing it offers is the protection of trying to instantiate the base class and the use of the @abstractmethod property.

Instead of using @property, there should be a way to get and set a property as in other languages. Something that inherits from the Object class such as
set.name = 'Matt'
print(get.name)
or as you're suggesting,
set_name, get_name,
correct?

Also, Abstract classes via ABC are different depending on the python version. So, keep it simple. Thanks for the insight.
Reply
#7
You can get and set instance attributes without any methods at all.
class Things:
    def __init__(self, value=0):
        self.value = value

thing = Things(5)
print(thing.value)
thing.value = thing.value * 2
print(thing.value)
thing.squared = thing.value**2
print(thing.squared)
Output:
5 10 100
For most instance variables that is all I do. The only time I use a setter or getter is when there is additional processing associated with the act of setting or getting the attribute.
Reply
#8
(Aug-03-2021, 04:48 PM)deanhystad Wrote: You don't need ABC to make a base class. Any Python class can be the superclass for another class. Python's inheritance model is very flexible. You can even change inheritance during runtime! A lot of things that are done in strictly typed languages (abstract classes, interfaces) aren't needed in Python.

I strongly suggest more study on the @property decorator. When I first found it I used it everywhere. Now I use it almost nowhere. Even when I need a getter or a setter (or both) I find making a method provides more flexibility and often improved readability over using a property. For example, in a GUI program I can bind a button press to a method, but I cannot bind it to a property.

And remember that when you use the @abstract decorator you are forcing all subclasses to implement that method. If it isn't required, don't make the method abstract. It might be better to try calling the method and catch the name error. Don't make your code more restrictive than it has to be. The Pythonic way to do most things is try and catch the exception instead of designing for every possible need. This philosophy should also be used when designing classes.

How is this, better?

class Aircraft():

    def __init__(self, speed = None):
        self.__speed = 'No Speed Entered' if speed is None else speed

    def fly(self):
        pass


    def land(self):
        print("All checks completed")

   
    def get_speed(self):
        return self.__speed

    
    def set_speed(self, speed):
        self.__speed = speed

class Jet(Aircraft):

    def fly(self):
        print("My jet is flying")

    def land(self):
        super().land() # Call abstract class abstract method, then add print statement locally. Both are displayed.
        print("My jet has landed")



jet1 = Jet(333)
jet1.set_speed = 344
print(jet1.get_speed)
jet1.land()
Output:
344 All checks completed My jet has landed
Reply
#9
This is based on the constructor though. I have used this before. Then what about private or protected attributes?

(Aug-03-2021, 05:58 PM)deanhystad Wrote: You can get and set instance attributes without any methods at all.
class Things:
    def __init__(self, value=0):
        self.value = value

thing = Things(5)
print(thing.value)
thing.value = thing.value * 2
print(thing.value)
thing.squared = thing.value**2
print(thing.squared)
Output:
5 10 100
For most instance variables that is all I do. The only time I use a setter or getter is when there is code associated with the act of setting or getting the attribute.
Reply
#10
There is no such thing as private or protected attributes. In Python everything is visible to everyone. There is a convention that attributes starting with an underscore should not be used external to the class, but it is only a convention.

I have thought about using the property() function instead of the @property decorator. As I see it you get the benefits of the property syntax and still have a regular method for binding events and the like.
class Things():
    def __init__(self, value=0):
        self._a = value

    def get_a(self):
        return self._a

    def set_a(self, value):
        self._a = value
    
    a = property(get_a, set_a)

thing = Things()
print(thing.a)
thing.set_a(5)
print(thing.a)
Output:
0 5
My concern is that this might be confusing.
    a = property(get_a, set_a)  # Why is a class variable getting defined here?
Reply


Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020