Python Abstract class with concrete methods - python

I'm wondering if in python (3) an abstract class can have concrete methods.
Although this seems to work I'm not sure this is the proper way to do this in python:
from abc import ABCMeta, abstractclassmethod, abstractmethod
class MyBaseClass:
__metaclass__ = ABCMeta
#property
#abstractmethod
def foo_prop(self):
"""Define me"""
pass
#abstractclassmethod
def get_something(cls, param1, param2, param3):
"""This is a class method, override it with #classmethod """
pass
#classmethod
def get(cls, param1, param2):
"""Concrete method calling an abstract class method and an abstract property"""
if param1 < cls.foo_prop:
raise Exception()
param3 = param1 + 42
item = cls.get_something(param1, param2, param3)
return item
class MyConcreteClassA(MyBaseClass):
"""Implementation """
foo_prop = 99
#classmethod
def get_something(cls, param1, param2, param3):
return cls.foo_prop + param1 + param2 + param3
class MyConcreteClassB(MyBaseClass):
"""Implementation """
foo_prop = 255
#classmethod
def get_something(cls, param1, param2, param3):
return cls.foo_prop - param1 - param2 - param3
In the example the abstract class MyBaseClass has:
an abstract property foo_prop that will be defined in the subclasses
the only way I could find to declare this was to create an abstract "property method"
an abstract class method get_something that will be implemented in the subclasses
a concrete method get that in turns uses the (not yet defined) abstract method and property mentioned above.
Questions:
Is there a better way to define an abstract property? Would it make more sense to define a concrete property in MyBaseClass set to None and just redefine it in the subclasses?
Can I mix abstract and concrete methods in an abstract class as shown in the example?
If yes, does it always makes sense to declare the class abstract or can a concrete class have abstract methods (in this case it should never be instantiated directly anyway).
Thanks

According to the docs (text in brackets and code formatting mine):
[#abstractproperty is] Deprecated since version 3.3: It is now possible to use property, property.getter(), property.setter() and property.deleter() with abstractmethod(), making this decorator redundant.
so I think you're doing it right.
You can do this, or at least in my experience it has not been an issue. Maybe somebody else can offer other advice, but that advice will probably take the form of "inheritance is bad". Although I don't see anything explicitly about it in the docs, this section shows an example wherein an abstract method, concrete method, and class method are all defined within an ABC.
I believe you still have to declare the class abstract in order to use #abstractmethod and similar decorators. By setting the metaclass as ABC, the class cannot be instantiated until all abstract methods are defined, which sounds like the behavior that you want, so I think you need to declare it abstract unless you just want to rely on documentation to enforce the "you shall not instantiate" rule on this class. As an aside, you can declare your abstract class with:
from abc import ABC
class MyBaseClass(ABC):
# ...
inheriting from abc instead of manually setting the metaclass. I think this construction is preferred.

Related

How to refer to subclass property in abstract shared implementation in abstract class method

For classes:
class Base(ABC):
def __init__(self, param1):
self.param1 = param1
#abstractmethod
def some_method1(self):
pass
# #abstractmethod
# def potentially_shared_method(self):
# ????
class Child(Base):
def __init__(self, param2):
self.param1 = param1
self.param2 = param2
def some_method1(self):
self.object1 = some_lib.generate_object1(param1, param2)
def potentially_shared_method(self):
return object1.process()
I want to move the potentially_shared_method to be shared in abstract calss, however it uses object1 that is initialized in some_method1 and needs to stay there.
If it's only potentially shared, it doesn't belong in the base class. You'd be breaking a few design principles.
What is a child class supposed to do for which the sharing doesn't make sense?
Also, you're introducing some temporal coupling; you can only call potentially_shared_method after some_method1 has been called. That's not ideal because the users of your class might not realize that.
Also, if the method is shared, you probably don't want it to be abstract in your base class; with an abstract method you're really only sharing the signature; but it seems you'll want to share functionality.
Anyway. Here's some options:
Using Python's multiple inheritance, move potentially_shared_method into a SharedMixin class and have those children who share it inherit from Base and from SharedMixin. You can then also move some_method1 into that SharedMixin class because it seems to me that those go together. Or maybe not...
Hide the access to object1 behind a getter. Make the getter have a dummy implementation in the base class and a proper implementation in those child classes who actually create an object1. Then potentially_shared_method can be moved to Base and just refer to the getter.

Refer to a superclass from the class body

I've got some code where I need to refer to a superclass when defining stuff in a derived class:
class Base:
def foo(self):
print('foo')
def bar(self):
print('bar')
class Derived_A(Base):
meth = Base.foo
class Derived_B(Base):
meth = Base.bar
Derived_A().meth()
Derived_B().meth()
This works, but I don't like verbatim references to Base in derived classes. Is there a way to use super or alike for this?
You can't do that.
class keyword in Python is used to create classes which are instances of type type. In it's simplified version, it does the following:
Python creates a namespace and executes the body of the class in that namespace so that it will be populated with all methods and attributes and so on...
Then calls the three-arguments form of type(). The result of this call is your class which is then assign to a symbol which is the name of your class.
The point is when the body of the class is being executed. It doesn't know about the "bases". Those bases are passed to the type() after that.
I also explained the reasons why you can't use super() here.
Does this work for you?
class Base:
def foo(self):
print('foo')
def bar(self):
print('bar')
class Derived_A(Base):
def __init__(self):
self.meth = super().foo
class Derived_B(Base):
def __init__(self):
self.meth = super().bar
a = Derived_A().meth()
b = Derived_B().meth()
You'll need to lookup the method on the base class after the new type is created. In the body of the class definition, the type and base classes are not accessible.
Something like:
class Derived_A(Base):
def meth(self):
return super().foo()
Now, it is possible to do some magic behind the scenes to expose Base to the scope of the class definition as its being executed, but that's much dirtier, and would mean that you'd need to supply a metaclass in your class definition.
Since you want "magic", there is still one sane option we can take before diving into metaclasses. Requires Python 3.9+
def alias(name):
def inner(cls):
return getattr(cls, name).__get__(cls)
return classmethod(property(inner))
class Base:
def foo(self):
...
class Derived_A(Base):
meth = alias("foo")
Derived_A().meth() # works
Derived_A.meth() # also works
Yes, this does require passing the method name as a string, which destroys your IDE and typechecker's ability to reason about it. But there isn't a good way to get what you are wanting without some compromises like that.
Really, a bit of redundancy for readability is probably worth it here.

How to define an abstract metaclass in python

When defining an abstract metaclass in python and instantiating it like this:
from abc import ABC, abstractmethod
class AbstractMetaClass(type, ABC):
#abstractmethod
def func(self):
pass
class MyClass(metaclass=AbstractMetaClass):
pass
I would have expected my code to fail, since MyClass is an instance of an abstract class. Instead it runs with no problems.
What is happening and how can I do this?
Well, you simply found out it does not work. What you are thinking about makes sense: maybe it should fail. It is just that abstract classes are not designed to work as metaclasses, and work collaboratively with "type". I actually find incredible as most Python object mechanisms happen to "just work" when used with metaclasses - including properties, special dunder methods like __getitem__ and operator methods and so on. You just hit one thing that happened not to work.
If your design really makes sense, you may just want to manually make the check for abstract methods on your "abstract metaclass" __init__ method:
from abc import classmethod
class AbstractMetaClass(type):
def __init__(cls, name, bases, ns, **kwargs):
for meth_name, meth in cls.__class__.__dict__.items():
if getattr(meth, "__isabstractmethod__", False):
raise TypeError(f"Can't create new class {name} with no abstract classmethod {meth_name} redefined in the metaclass")
return super().__init__(name, bases, ns, **kwargs)
#abstractmethod
def func(cls):
pass
note that for clarity, it is better that ordinary methods on a metaclass have "cls" as the first argument rather than "self" (althought that might be a personal taste)

Must a class implement all abstract methods?

Suppose you have the following class:
class Base(object):
def abstract_method(self):
raise NotImplementedError
Can you then implement a inheriting class, which does not implement the abstract method? For example, when it does not need that specific method. Will that give problems or is it just bad practice?
If you're implementing abstract methods the way you show, there's nothing enforcing the abstractness of the class as a whole, only of the methods that don't have a concrete definition. So you can create an instance of Base, not only of its subclasses.
b = Base() # this works following your code, only b.abstract_method() raises
def Derived(Base):
... # no concrete implementation of abstract_method, so this class works the same
However, if you use the abc module from the standard library to designate abstract methods, it will not allow you to instantiate an instance of any class that does not have a concrete implementation of any abstract methods it has inherited. You can leave inherited abstract methods unimplemented in an intermediate abstract base class (e.g. a subclass of the original base, that is itself intended to still be abstract), but you can't make any instances.
Here's what using abc looks like:
from abc import ABCMeta, abstractmethod
class ABCBase(metaclass=ABCMeta):
#abstractmethod
def abstract_method(self, arg):
...
class ABCDerived(ABCBase):
... # we don't implement abstract_method here, so we're also an abstract class
d = ABCDerived() # raises an error

Do ABCs enforce method decorators?

I'm trying to figure out how to ensure that a method of a class inheriting from an ABC is created using the appropriate decorator. I understand (hopefully) how ABCs work in general.
from abc import ABCMeta, abstractmethod
class MyABC(metaclass=ABCMeta):
#abstractmethod
def my_abstract_method(self):
pass
class MyClass(MyABC):
pass
MyClass()
This gives "TypeError: Can't instantiate abstract class MyClass with abstract methods my_abstract_method". Great, makes sense. Just create a method with that name.
class MyClass(MyABC):
def my_abstract_method(self):
pass
MyClass()
Boom. You're done. But what about this case?
from abc import ABCMeta, abstractmethod
class MyABC(metaclass=ABCMeta):
#property
#abstractmethod
def my_attribute(self):
pass
class MyClass(MyABC):
def my_attribute(self):
pass
MyClass()
The MyClass() call works even though my_attribute is not a property. I guess in the end all ABCs do is ensure that a method with a given name exists. Thats it. If you want more from it, you have to look at MyABC's source code and read the documentation. The decorators and comments there will inform you of how you need to construct your sub-class.
Do I have it right or am I missing something here?
You're correct that ABCs do not enforce that. There isn't a way to enforce something like "has a particular decorator". Decorators are just functions that return objects (e.g., property returns a property object). ABCMeta doesn't do anything to ensure that the defined attributes on the class are anything in particular; it just makes sure they are there. This "works" without errors:
class MyABC(metaclass=ABCMeta):
#abstractmethod
def my_abstract_method(self):
pass
class MyClass(MyABC):
my_abstract_method = 2
MyClass()
That is, ABCMeta doesn't even ensure that the abstract method as provided on the subclass is a method at all. There just has to be an attribute of some kind with that name,
You could certainly write your own metaclass that does more sophisticated checking to ensure that certain attributes have certain kinds of values, but that's beyond the scope of ABCMeta.

Categories