Is it allowed to use subclass constructor in Python - python

As in the title. In example:
class A:
def __init__(self,name):
self.name=name
class B(A):
def __init__(self,surname):
self.surname=surname
obj=B('somename','somesurname')
If it's allowed, in which order shall I pass parameters in object instantiation?

You need to explicitly call the constructor of the super-class from your derived one:
class A:
def __init__(self,name):
self.name=name
class B(A):
def __init__(self, name, surname):
super().__init__(name)
self.surname=surname
obj=B('somename','somesurname')

You should use super builtin function:
class A:
def __init__(self, name):
self.name=name
class B(A):
def __init__(self, name, surname):
super().__init__(name)
self.surname=surname
obj=B('somename','somesurname')

Your new constructor should call the parent constructor if the old constructor must be executed. You can call the parents constructor using super:
class A:
def __init__(self, name):
self.name = name
class B(A):
def __init__(self, name, surname):
super().__init__(name)
self.surname = surname
obj = B('somename', 'somesurname')
In python 2, you must specify the current class and self as arguments to super(): super(B, self), in python 3 it can be called without arguments like as shown above.

Related

Overriding an inherited property setter (without fset)

This question arises from reading the answer to Overriding an inherited property setter. Assume the case where I have the base Person class:
class Person:
def __init__(self, name):
self.name = name
#property
def name(self):
return self._name
#name.setter
def name(self, value):
self._name = value
Following the top-voted answer the correct way to override the setter would be:
class Superhero(Person):
def __init__(self, name):
super().__init__(name)
#Person.name.setter
def name(self, value):
Person.name.fset(self, "Super " + value)
Would there be a difference if instead, the Superhero class updated the _name attribute directly? i.e.
class Superhero(Person):
def __init__(self, name):
super().__init__(name)
#Person.name.setter
def name(self, value):
self._name = "Super " + value
(I know, a comment in the referenced question would have been enough but due to reputation I do not have the privileges to ask.)

How to use class methods in python?

I have this code
class Person(object):
def __init__(self, name):
self.name = name
#classmethod
def from_classmethod(cls, name):
return cls(name)
p = Person.from_classmethod("Moctar")
p.name
But it shows the following error:
AttributeError: 'Person' object has no attribute 'name'
What could be going wrong here, or am i using wrongly the python #classmethod feature ?
As #furas says, what I think you want is:
class Person(object):
def __init__(self, name):
self.name = name
#classmethod
def from_classmethod(cls, name):
return cls(name)
p = Person.from_classmethod("Moctar")
print(p.name)
Result:
Moctar
It is the assignment to self.name that creates that attribute on the instance of Person that you are creating.

Override property

In a base class I'm creating a property with property() function:
from abc import ABCMeta, abstractmethod
class Person(metaclass=ABCMeta):
#abstractmethod
def __init__(self, name, telephone):
self.name = name
self.telephone = telephone
def get_name(self) -> str:
return self.__name
def set_name(self, value: str):
self.__name = value
name = property(fget=get_name, fset=set_name)
And in the derived class I need to override the set_name method, that is, Person.name.fset should point to a modified version of the method. I tried this but it did not work:
class Employee(Person):
def __init__(self, name, telephone, email):
super().__init__(name, telephone)
self.email = email
def set_name(self, value):
super().set_name('override')
Person.name.fset = set_name
Is there any way to override property in python?

How to write to an abstract property in Python 3.4+

In Python 3.6, Let's say I have an abstract class MyAbstractClass
from abc import ABC, abstractmethod
class MyAbstractClass(ABC):
#property
#abstractmethod
def myProperty(self):
pass
and a class MyInstantiatableClass inherit from it. So how do I write to the property myProperty on instantiation of an object from this class? I'd like to be able to both set and get myProperty. Below doesn't work.
from MyAbstractClass import MyAbstractClass
class MyInstantiatableClass(MyAbstractClass):
def __init__(self, desiredValueOfMyProperty):
????
#myProperty.setter
def myProperty(self, desiredValueOfMyProperty): # value coming from __init__
self._myProperty = desiredValueOfMyProperty
And a main function, say,
from MyInstantiatableClass import MyInstantiatableClass
def main():
MyInstantiatableClass(3) # 3 is the desiredValueOfMyProperty for this instantiation
MyInstantiatableClass(5) # 5 is the desiredValueOfMyProperty for this instantiation
It seems there's a discrepancy here; using #property along with #abstractmethod doesn't seem to enforce classes that inherit from your abc to need to define both setter and getter. Using this:
#property
#abstractmethod
def myProperty(self):
pass
#myProperty.setter
#abstractmethod
def myProperty(self):
pass
and then providing an implementation only for the getter in the class works and allows for instantiation:
#property
def myProperty(self):
return self._myProperty
This is due to the fact that only one name (myProperty) appears in the namespace of the ABC, when you override in the base class, you only need to define this one name.
There's a way around that enforces it. You can create separate abstract methods and pass them on to property directly:
class MyAbstractClass(ABC):
#abstractmethod
def getProperty(self):
pass
#abstractmethod
def setProperty(self, val):
pass
myAbstractProperty = property(getProperty, setProperty)
Providing an implementation for this abc now requires both getter and setter to have an implementation (both names that have been listed as abstractmethods in MyAbstractClass namespace need to have an implementation):
class MyInstantiatableClass(MyAbstractClass):
def getProperty(self):
return self._Property
def setProperty(self, val):
self._Property = val
myAbstractProperty = property(getProperty, setProperty)
Implementing them is exactly the same as any old property. There's no difference there.
For example, you can define the abstract getter, setter and deleter in Person abstract class, override them in Student class which extends Person abstract class as shown below. *#abstractmethod must be the innermost decorator otherwise error occurs:
from abc import ABC, abstractmethod
class Person(ABC):
#property
#abstractmethod # The innermost decorator
def name(self): # Abstract getter
pass
#name.setter
#abstractmethod # The innermost decorator
def name(self, name): # Abstract setter
pass
#name.deleter
#abstractmethod # The innermost decorator
def name(self): # Abstract deleter
pass
class Student(Person):
def __init__(self, name):
self._name = name
#property
def name(self): # Overrides abstract getter
return self._name
#name.setter
def name(self, name): # Overrides abstract setter
self._name = name
#name.deleter
def name(self): # Overrides abstract deleter
del self._name
Then, you can instantiate Student class and call the getter, setter and deleter as shown below:
obj = Student("John") # Instantiates "Student" class
print(obj.name) # Getter
obj.name = "Tom" # Setter
print(obj.name) # Getter
del obj.name # Deleter
print(hasattr(obj, "name"))
Output:
John
Tom
False
You can see my answer which explains more about abstract property.

How to create abstract properties in python abstract classes?

In the following code, I create a base abstract class Base. I want all the classes that inherit from Base to provide the name property, so I made this property an #abstractmethod.
Then I created a subclass of Base, called Base_1, which is meant to supply some functionality, but still remain abstract. There is no name property in Base_1, but nevertheless python instatinates an object of that class without an error. How does one create abstract properties?
from abc import ABCMeta, abstractmethod
class Base(object):
# class Base(metaclass = ABCMeta): <- Python 3
__metaclass__ = ABCMeta
def __init__(self, str_dir_config):
self.str_dir_config = str_dir_config
#abstractmethod
def _do_stuff(self, signals):
pass
#property
#abstractmethod
def name(self):
"""This property will be supplied by the inheriting classes
individually.
"""
pass
class Base1(Base):
__metaclass__ = ABCMeta
"""This class does not provide the name property and should
raise an error.
"""
def __init__(self, str_dir_config):
super(Base1, self).__init__(str_dir_config)
# super().__init__(str_dir_config) <- Python 3
def _do_stuff(self, signals):
print "Base_1 does stuff"
# print("Base_1 does stuff") <- Python 3
class C(Base1):
#property
def name(self):
return "class C"
if __name__ == "__main__":
b1 = Base1("abc")
Since Python 3.3 a bug was fixed meaning the property() decorator is now correctly identified as abstract when applied to an abstract method.
Note: Order matters, you have to use #property above #abstractmethod
Python 3.3+: (python docs):
from abc import ABC, abstractmethod
class C(ABC):
#property
#abstractmethod
def my_abstract_property(self):
...
Python 2: (python docs)
from abc import ABC, abstractproperty
class C(ABC):
#abstractproperty
def my_abstract_property(self):
...
Until Python 3.3, you cannot nest #abstractmethod and #property.
Use #abstractproperty to create abstract properties (docs).
from abc import ABCMeta, abstractmethod, abstractproperty
class Base(object):
# ...
#abstractproperty
def name(self):
pass
The code now raises the correct exception:
Traceback (most recent call last):
File "foo.py", line 36, in
b1 = Base_1('abc')
TypeError: Can't instantiate abstract class Base_1 with abstract methods name
Based on James answer above
def compatibleabstractproperty(func):
if sys.version_info > (3, 3):
return property(abstractmethod(func))
else:
return abstractproperty(func)
and use it as a decorator
#compatibleabstractproperty
def env(self):
raise NotImplementedError()
In python 3.6+, you can also anotate a variable without providing a default. I find this to be a more concise way to make it abstract.
class Base():
name: str
def print_name(self):
print(self.name) # will raise an Attribute error at runtime if `name` isn't defined in subclass
class Base_1(Base):
name = "base one"
it may also be used to force you to initialize the variable in the __new__ or __init__ methods
As another example, the following code will fail when you try to initialize the Base_1 class
class Base():
name: str
def __init__(self):
self.print_name()
class Base_1(Base):
_nemo = "base one"
b = Base_1()
AttributeError: 'Base_1' object has no attribute 'name'
Using the #property decorator in the abstract class (as recommended in the answer by James) works if you want the required instance level attributes to use the property decorator as well.
If you don't want to use the property decorator, you can use super(). I ended up using something like the __post_init__() from dataclasses and it gets the desired functionality for instance level attributes:
import abc
from typing import List
class Abstract(abc.ABC):
"""An ABC with required attributes.
Attributes:
attr0
attr1
"""
#abc.abstractmethod
def __init__(self):
"""Forces you to implement __init__ in 'Concrete'.
Make sure to call __post_init__() from inside 'Concrete'."""
def __post_init__(self):
self._has_required_attributes()
# You can also type check here if you want.
def _has_required_attributes(self):
req_attrs: List[str] = ['attr0', 'attr1']
for attr in req_attrs:
if not hasattr(self, attr):
raise AttributeError(f"Missing attribute: '{attr}'")
class Concrete(Abstract):
def __init__(self, attr0, attr1):
self.attr0 = attr0
self.attr1 = attr1
self.attr2 = "some value" # not required
super().__post_init__() # Enforces the attribute requirement.
For example, you can define the abstract getter, setter and deleter with #abstractmethod and #property, #name.setter or #name.deleter in Person abstract class as shown below. *#abstractmethod must be the innermost decorator otherwise error occurs:
from abc import ABC, abstractmethod
class Person(ABC):
#property
#abstractmethod # The innermost decorator
def name(self): # Abstract getter
pass
#name.setter
#abstractmethod # The innermost decorator
def name(self, name): # Abstract setter
pass
#name.deleter
#abstractmethod # The innermost decorator
def name(self): # Abstract deleter
pass
Then, you can extend Person abstract class with Student class, override the abstract getter, setter and deleter in Student class, instantiate Student class and call the getter, setter and deleter as shown below:
class Student(Person):
def __init__(self, name):
self._name = name
#property
def name(self): # Overrides abstract getter
return self._name
#name.setter
def name(self, name): # Overrides abstract setter
self._name = name
#name.deleter
def name(self): # Overrides abstract deleter
del self._name
obj = Student("John") # Instantiates "Student" class
print(obj.name) # Getter
obj.name = "Tom" # Setter
print(obj.name) # Getter
del obj.name # Deleter
print(hasattr(obj, "name"))
Output:
John
Tom
False
Actually, even if you don't override the abstract setter and deleter in Student class and instantiate Student class as shown below:
class Student(Person): # Extends "Person" class
def __init__(self, name):
self._name = name
#property
def name(self): # Overrides only abstract getter
return self._name
# #name.setter
# def name(self, name): # Overrides abstract setter
# self._name = name
# #name.deleter
# def name(self): # Overrides abstract deleter
# del self._name
obj = Student("John") # Instantiates "Student" class
# ...
No error occurs as shown below:
John
Tom
False
But, if you don't override the abstract getter, setter and deleter in Student class and instantiate Student class as shown below:
class Student(Person): # Extends "Person" class
def __init__(self, name):
self._name = name
# #property
# def name(self): # Overrides only abstract getter
# return self._name
# #name.setter
# def name(self, name): # Overrides abstract setter
# self._name = name
# #name.deleter
# def name(self): # Overrides abstract deleter
# del self._name
obj = Student("John") # Instantiates "Student" class
# ...
The error below occurs:
TypeError: Can't instantiate abstract class Student with abstract methods name
And, if you don't override the abstract getter in Student class and instantiate Student class as shown below:
class Student(Person): # Extends "Person" class
def __init__(self, name):
self._name = name
# #property
# def name(self): # Overrides only abstract getter
# return self._name
#name.setter
def name(self, name): # Overrides abstract setter
self._name = name
#name.deleter
def name(self): # Overrides abstract deleter
del self._name
obj = Student("John") # Instantiates "Student" class
# ...
The error below occurs:
NameError: name 'name' is not defined
And, if #abstractmethod is not the innermost decorator as shown below:
from abc import ABC, abstractmethod
class Person(ABC):
#abstractmethod # Not the innermost decorator
#property
def name(self): # Abstract getter
pass
#name.setter
#abstractmethod # The innermost decorator
def name(self, name): # Abstract setter
pass
#name.deleter
#abstractmethod # The innermost decorator
def name(self): # Abstract deleter
pass
The error below occurs:
AttributeError: attribute 'isabstractmethod' of 'property' objects is not writable
Another possible solution is to use metaclasses.
A minimal example can look like this:
class BaseMetaClass(type):
def __new__(mcls, class_name, bases, attrs):
required_attrs = ('foo', 'bar')
for attr in required_attrs:
if not attr in attrs:
raise RunTimeError(f"You need to set {attr} in {class_name}")
return super().__new__(mcls, class_name, bases, attrs)
class Base(metaclass=BaseMeta):
foo: str
bar: int
One advantage of this approach is that the check will happen at definition time (not instantiation).
Also, setting class attributes in child classes is a bit easier than declaring properties (as long as they are simple values known in advance) and your final classes will look more concise

Categories