Posts: 138
Threads: 26
Joined: Nov 2020
Aug-03-2021, 01:08 PM
(This post was last modified: Aug-03-2021, 01:09 PM by muzikman.)
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
Posts: 6,802
Threads: 20
Joined: Feb 2020
Aug-03-2021, 02:42 PM
(This post was last modified: Aug-03-2021, 02:42 PM by deanhystad.)
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]
Posts: 138
Threads: 26
Joined: Nov 2020
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.
Posts: 138
Threads: 26
Joined: Nov 2020
Aug-03-2021, 04:21 PM
(This post was last modified: Aug-03-2021, 04:21 PM by muzikman.)
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
Posts: 6,802
Threads: 20
Joined: Feb 2020
Aug-03-2021, 04:48 PM
(This post was last modified: Aug-03-2021, 04:48 PM by deanhystad.)
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.
Posts: 138
Threads: 26
Joined: Nov 2020
Aug-03-2021, 04:58 PM
(This post was last modified: Aug-03-2021, 04:58 PM by muzikman.)
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.
Posts: 6,802
Threads: 20
Joined: Feb 2020
Aug-03-2021, 05:58 PM
(This post was last modified: Aug-03-2021, 06:02 PM by deanhystad.)
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.
Posts: 138
Threads: 26
Joined: Nov 2020
(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
Posts: 138
Threads: 26
Joined: Nov 2020
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.
Posts: 6,802
Threads: 20
Joined: Feb 2020
Aug-03-2021, 10:00 PM
(This post was last modified: Aug-03-2021, 10:00 PM by deanhystad.)
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?
|