How to avoid repeating self when defining subclasses in python? - python

I'm trying to build an algorithm which constructs funny but grammatical sentences. As a part of this, I need to classify words into different categories, and want to explore using OOP to achieve this, but I am struggling to avoid violating DRY. Below is a reduced example.
class word:
def __init__(self,name):
self.name = name
self.firstLetter = self.name[0]
self.length = len(name)
class noun(word):
def __init__(self, name):
self.name=name
super().__init__(name)
class agent(noun):
def __init__(self, name):
self.name=name
super().__init__(name)
I would like all words to share certein methods and properties, e.g. having a "length" equal to the length of their name. However, I only need an object to be named at the very lowest level (in this case agent), and thus it seems superflous to feature a name in the init of each subclass.
In summary, is there any way of acheive the same end result of the above code without having to write the following ad nauseam?
def __init__(self, name):
self.name=name
super().__init__(name)

The following dryes up your code:
class word:
firstLetter: str = ''
def __init__(self, name: str) -> None:
self.name = name
self.length: int = len(name)
if self.length:
self.firstLetter = self.name[0]
class noun(word):
pass
class agent(noun):
pass

Related

UML diagrams in python?

I just got started learning about UML and I am currently trying to implement functionality for this diagram using python.
I have wrote the inheritance part, but I don't know yet how to deal with the association part of it. Could anyone give me some pointers on how to do this? Especially the circular reference.
My implementation so far:
class RailCar():
def __init__(self, name):
if len(name) >= 2:
self.__name = name
self.locomotive = None
else:
raise Exception("Name length of", type(self), " object must be at least 2 characters long.")
class PassengerCar(RailCar):
def __init__(self, capacity):
super().__init__(self, name)
self.__capacity = capacity
class RestaurantCar(RailCar):
def __init__(self, first):
super().__init__(self, name)
self.__first = first
class Locomotive():
def __init__(self, railCar):
self.railCar = railCar
rc01 = RailCar('01')
rc01.locomotive = "It works!!"
print(rc01.locomotive)
Supposedly the self reference is a doppelganger of the private next attribute. It should rather be written with the role name next to the association like so:
Anyhow, you would code it with self._next holding a reference to a RailCar instance. The getter/setter would then reference this private property.
class RailCar():
def __init__(self, name):
# as above
self._next = None
def setNext(self, next):
self._next = next
# you might check if the instance is RailCar.
#property
def getNext(self):
return self._next
P.S. The Locomotive-RailCar relation would be similar (with the same modeling issue). Just a self._first in Locomotive.

Python Inheritence from constructor

person.py
class Person:
"""---A class representing a person---"""
# Person constructor
def __init__(self,n,a):
self.full_name = n
self.age = a
class Student(Person):
# Student constructor
def __init__(self,n,a,s):
Person.__init__(self,n,a)
self.school = s
driver.py
from person import *
a = Student("Alice", 19, "Univ")
It throws TypeError: __init__() takes 3 positional arguments but 4 were given
I tried to change Student class to the following:
class Student(Person):
# Student constructor
def __init__(self,n,a,s):
super().__init__(n,a)
self.school = s
The error still exists.
Why does this happen? Is super() keyword required to add new attributes?
EDIT: The problem is solved. There was an indentation issue in the source code rendering this strange behavior, hence the question should be closed.
This line:
Person.__init__(self,n,a)
Is the problem. Recall that methods are automatically passed a reference to themselves, so you just passed a second one.
There's also a well-established pattern for this:
class Person
def __init__(self, name, age):
self.name = name
self.age = age
class Student(Person):
def __init__(self, school, *args):
super().__init__(*args)
self.school = school
student = Student('Washington Elementary', "Johnny Go'gettem", 10)
although note that simply removing your reference to self in the Person.__init__ call inside Student.__init__ would be sufficient.
Note that you can override the default method behavior with a couple of decorators that become quite useful in certain situations. Neither apply here, but just a bit of knowledge to tease your brain a bit:
def SomeClass:
attr = "class-scoped"
def __init__(self):
self.attr = "instance-scoped"
def some_method(self):
return self.attr == "instance-scoped"
#classmethod
def some_classmethod(cls):
return cls.attr == "class-scoped"
#staticmethod
def some_staticmethod():
return "I'm not given a \"self\" parameter at all!"
classmethods are particularly useful as alternate constructors
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
#classmethod
def from_tuple(cls, tup) -> "Person":
"""Expects a tuple of (name, age) and constructs a Person"""
name, age = tup
return cls(name, age)
#classmethod
def from_dict(cls, dct) -> "Person":
"""Expects a dictionary with keys "name" and "age" and constructs a Person"""
try:
name = dct['name']
age = dct['age']
except KeyError:
raise ValueError(f"Dictionary {dct} does not have required keys 'name' and 'age'")
else:
return cls(name, age)

What is the difference between readable property method and a callable function that is just returns the data as a property can?

I have a property that returns list of names with "ash" in it
class BaseClass(object):
def __init__(self):
self.filter_key = ""
self.name = ""
def filter_names(self, filter_key):
self.filter_key = filter_key
#property
def student_names(self):
return self.names
def callable_function_names(self):
return names
and then student class that inherits BaseClass
class StudentClass(BaseClass):
#property
def student_names(self):
names = super(StudentClass, self).student_names
return [name for name in names if self.filter_students in name]
#property
def filter_key(self):
"""Gets """
return self.filter_key
#slot_key.setter
def filter_key(self, key):
"""Sets name filter"""
self.filter_names(key)
# or by doing :
def callable_function_names(self):
names = super(StudentClass, self).callable_function_names()
return [name for name in names if self.filter_students in name]
So if I create obj of the student class.
studentclsObj = StudentClass()
studentclsObj.filter_key = "ash"
print studentclsObj.student_names
print studentclsObj.callable_function_names()
I can achieve the same result with both above prints, is there any difference and what is preferred and right way to do ?
One use case of properties is not breaking API. This is one of main strengths of python IMO. You can take a function, make transform it in a callable object, add new functionality without breaking old code, now the property
I see three main uses of properties over attributes,
Read only attributes
Is easy to create read only attributes with properties. They are non verbose, self documenting and simple
class Foo:
def __init__(self, bar):
self._bar = bar
#property
def bar(self):
return self._bar
Validation on writable properties
class Foo:
def __init__(self, bar):
self._bar = bar
#property
def bar(self):
return self._bar
#bar.setter
def bar(self, val):
if valid(val):
self._bar = val
This is a kind of defensive programming
Keep API compatibility
Imagine that you have a class for a bank account, with
a balance property
class BankAccount:
def __init__(self):
self.balance = 0
You have this code and it works fine. But know your client
says, I need you to log every balance lookup. You can replace
the attribute by a property without breaking old code
class BankAccount:
def __init__(self):
self._balance = 0
#property
def balance(self):
self.log_balance_read()
return self._balance
There is no difference between a property and a method which return the same value. Go for the simpler, use method for actions and state changes and attributes for real attributes, if you need to add logic to attribute lookup, python will let you do it

Base class accessing super class variables

I'm using python 3.6.
My goal is to make a base class that would be able to somehow access through polymorphism - one of the child class variables.
I know it sounds somewhat 'not oop', so if what im describing can't be done with python - I would like to know what is the best practice for this case.
Following wikipedia's example:
class Animal:
def __init__(self, name): # Constructor of the class
self.name = name
def talk(self): # Abstract method, defined by convention only
raise NotImplementedError("Subclass must implement abstract method")
class Cat(Animal):
def talk(self):
return 'Meow!'
class Dog(Animal):
def talk(self):
return 'Woof! Woof!'
animals = [Cat('Missy'),
Cat('Mr. Mistoffelees'),
Dog('Lassie')]
for animal in animals:
print animal.name + ': ' + animal.talk()
Prints the following:
Missy: Meow!
Mr. Mistoffelees: Meow!
Lassie: Woof! Woof!
I would like to achieve the exactly same output - using
variable overloading (is that a thing?) instead of method overloading.
The reason is that in the programm im working on - dog, cat, and every other kind of animal will talk exactly the same way - influenced only by the data member, such as:
class Animal:
def __init__(self, name): # Constructor of the class
self.name = name
self.vocabulary = [] # so called abstract data member
def talk(self): # Non Abstract method, all animals would talk
for word in self.vocabulary: print (word)
class Cat(Animal):
vocabulary = ["Meow", "Muuuew", "Maow"]
class Dog(Animal):
vocabulary = ["Woof", "Waf", "Haw"]
animals = [Cat('Missy'),
Cat('Mr. Mistoffelees'),
Dog('Lassie')]
for animal in animals:
print animal.name + ': ' + animal.talk()
Prints the following:
Missy: Meow Muuuew Maow
Mr. Mistoffelees: Meow Muuuew Maow
Lassie: Woof Waf Haw
Clearly, this won't work since vocabulary will be empty, as it is in the base class.
I tried to find a solution using super, e.g:
class Cat(Animal):
vocabulary = ["Meow", "Muuuew", "Maow"]
def talk(self):
super(Animal,Cat).talk()
But the result would be AttributeError: 'super' object has no attribute 'talk'
Am I using super wrong?
There are a few unresolved issues in your code, but since python is so dynamic, it will find the subclass instance attribute through normal lookup:
class Animal:
def __init__(self, name):
self.name = name
def talk(self):
for word in self.vocabulary: print (word)
class Cat(Animal):
def __init__(self, name):
super().__init__(name)
self.vocabulary = ["Meow", "Muuuew", "Maow"]
class Dog(Animal):
def __init__(self, name):
super().__init__(name)
self.vocabulary = ["Woof", "Waf", "Haw"]
animals = [Cat('Missy'),
Cat('Mr. Mistoffelees'),
Dog('Lassie')]
for animal in animals:
print(animal.name, end=': ')
animal.talk()
If you want something to enforce this requirement more explicitly in the code, you can make Animal an abstract base class and make an abstruct property named vocabulary:
import abc
class Animal(abc.ABC):
def __init__(self, name):
self.name = name
#property
#abc.abstractmethod
def vocabulary(self):
...
def talk(self):
for word in self.vocabulary: print (word)
class Cat(Animal):
#property
def vocabulary(self):
return ["Meow", "Muuuew", "Maow"]
here is a live link
Python is dynamically typed. There is no need to somehow declare an "abstract data member" in Animal for Animal methods to refer to self.vocabulary; in fact, your attempts to declare an "abstract data member" are causing your problems.
Just remove self.vocabulary = [], and talk will automatically find the subclass vocabulary when it tries to access self.vocabulary.

Mutually Reference-able Instances in Python

Say I have a pair of instances that reference one another mutually. Is there a preferable manner to structure this relationship than the following.
class Human():
def __init__(self, name):
self.name = name
self.pet = Dog('Sparky', self)
def pet(self, animal):
self.pet.receive_petting()
class Dog(Pet):
def __init__(self, name, owner):
self.name = name
self.owner = owner
def receive_petting(self):
pass
def bark_at(self, person):
"do something"
The thing I don't like is that the relationship needs to be specified in two places. Any ideas on how to make this dryer?
I would break this into three classes:
class Human():
def __init__(self, name):
self.name = name
class Dog(Pet):
def __init__(self, name):
self.name = name
def bark_at(self, person):
"do something"
class OwnerPetRelation():
def __init__(self, dog, human):
self.owner=human
self.pet=dog
Now, one owner can also have many dogs, we just need to define as many OwnerPetRelations.
Similarly, a dog can also belong to multiple owners now.
I would create a method on Human that allows you to add pets (since a human might have many pets):
class Human():
def __init__(self, name):
self.name = name
self.pets = []
def add_pet(self, pet):
pet.owner = self
self.pets.append(pet)
def pet(self, animal):
for pet in self.pets:
pet.receive_petting()
class Dog(Pet):
def __init__(self, name):
self.name = name
self.owner = None
def receive_petting(self):
pass
def bark_at(self, person):
"do something"
This can be used as follows
human = Human('Jim')
human.add_pet(Dog('Woof'))
This approach can of course also be used for just a single pet and one could also extend it to allow pets to be owned by many humans.
There's nothing really Python-specific here; this is just a limitation of constructor-based dependency injection. It's hard to inject a reference to another object that cannot have been created yet. Instead, you can create an object that has a reference to something that will have a reference to the other object. For instance, you can pass a function to the constructor that will be able to return the value:
class Human():
def __init__(self,name,dog):
self.name = name
self._dog = dog
#property
def dog(self):
return self._dog()
class Dog():
def __init__(self,name,human):
self.name = name
self._human = human
#property
def human(self):
return self._human()
Then you can use it like this:
human = None
dog = Dog('fido',lambda: human)
human = Human('john',lambda: dog)
print(dog.human.name)
print(human.dog.name)
john
fido
It is not hard to update this so that the property function caches the value, of course. E.g.:
class Dog():
def __init__(self,name,human):
self.name = name
self._human = human
#property
def human(self):
try:
return self._human_
except AttributeError:
self._human_ = self._human()
return self._human_

Categories

Resources