I am running into an issue with Liskov Substitution Principle and am not quite sure what would be the best way to go around it.
Code in question
class BaseModel:
def run(self, base_model_input: BaseModelInput) -> BaseModelOutput:
"""Throws NotImplemented or #abstractmethod"""
pass
class SpecificModel(BaseModel):
def run(self, specific_input: SpecificModelInput) -> SpecificModelOutput:
# do things...
I understand well why is this not a great code, and why it violates the Liskov Substitution Principle. I am wondering how to design my system better to avoid this problem in the first place.
Fundamentally I have a BaseModel class that acts like an interface, providing some methods like run that the extending classes must implement. But extending classes also deal with specific input/output, that are also extensions of the base input/output classes (that is SpecificModelInput inherits from BaseModelInput and adds some fields and functionality, same with output)
What would be a better approach here?
As I stated in the comments:If you can really constrain the inputs and outputs to subtypes of a base-input and base-output, I don't see any problem with this model
If by violation you mean that on restricting the input options, the subclass can no longer b used everywhere the baseclass can, I'd say it is a case where you are taking the Liskov Substitution principle as dogma where it should be very good advice.
See, the Liskov..principle is a good guideline, and a bit simplistic, as it will give you a really good feeling of what inheritance should be.
But in real-world terms, restricting input parameters (and attribute types) is not a concern in all cases. Anyway, having a concrete example will serve us well: think of an abstract "Vehicle" class with a "board" method which allows you to board "Transportables" - and concrete "Transportables" and some os the allowable subclasses of "Transportable" are Persons, Dogs, Grocery Bag, Pianos, and Elephants. If your "Vehicle" class is a "Ship" - it can take all of those. If your vehicle class is a Car, some of those are out.
That seems to be your use case.
So, of course, you are violating the principle here. The wording in most places explaining it is that "if you substitute an instance of the subclass anywhere the superclass appears, the program should not break".
So, the most correct thing to do, is to modify the superclass contracts in a way that any input may or may not work, and allow it to raise a runtime exception (or return an object signalizing an error) even if the input is valid.
Everyone calling that method should handle the "did not work" state - with the example above it is easy to see that if I have a method in an unrelated class that calls vehicle.board, it should be responsible to see it is not trying to put an Elephant inside a car. If everywhere the method is called these checks are made, the principle holds!
If engineering this is overkill for whatever task you have, I'd say "...practicality beats purity" in this case, and simply set the annotations to silence the static type checker.
Of course, telling that to the static type checker is another thing - I think the use of Generics as pointed in the answer linked in the comments could do: How do I annotate the type of a parameter of an abstractmethod, when the parameter can have any type derived from a specific base type?
Related
I understand mixin as what looks like inheritance but what is more like composition.
(edit: I tend to think giving additional functionality/attributes by mixin rather than giving another is-a relationship.)
Mentally, I'm saying something like this when I use mixin: I'm giving you this mixin you are missing, rather than you are actually this mixin-type as well.(is-a)
And I read few times, you should prefer composition over inheritance.
We could just use straight compositions instead of mixins, what is mixin for?
If I have to guess, it's because my_instance.foo() is easier than my_instance.another_instance.foo()?
(You can use my_instance.foo() if mixin has foo(), you need my_instance.another_instance.foo() when you composite another_instance as an attribute of my_instance)
Are there any other reason?
Edit:
So even though I feel it's has-a, mixin is still is-a relationship. and benefit you get when you use is-a here is, cleaner interface. That' how I interpret delnan's answer.
(Since mixin doesn't give you is-a relationship, it gives you has-a)
Wrong, it does give you a is-a relationship. Consider class C(A, B). issubclass(C, A) is true and so is issubclass(C, B). The call C().method_of_B() calls the method of B if it isn't overridden somewhere in the MRO (which can happen with single inheritance too!), and similarly for methods of A. The attribute access C().attr_of_{a,b} gives you an attribute of A or B too (same caveat as with methods).
And I read few times, you should prefer composition over inheritance.
This rule of thumb exists because many people tend to use inheritance where it isn't appropriate, not because inheritance isn't a useful tool in any case. This applies to Python too. Common (good) reasons include:
A is-a relationship would be wrong, for example because it breaks the Liskov Substitution Principle. This reason is not affected by mixins, see above.
Composition is easier or less error-prone, for various reasons. This reason is valid when mixins are possible too, if anything it's amplified as it can be a bit tricky (though not impossible) to get the MRO and sequence of super() calls right.
If I have to guess, it's because my_instance.foo() is easier than my_instance.another_instance.foo()?
That's just a few additional characters. More important is that you have to re-type wrapper methods for every single method you want to re-export (which would be all of them if you're emulating an is-a relationship via composition).
More conceptually, this manual work seems pointless and error-prone if C really is-a A. Mixins/multiple inheritance support the case where an object is more than what can or should be represented by a single bass class. For example, a UTF-8 string may be both a ByteBuffer and a UnicodeStream. In general, we have interfaces for this (or not; in Python this is implicit), but mixins also allow adding related functionality in a composable and convenient manner.
I realize that in most cases, it's preferred in Python to just access attributes directly, since there's no real concept of encapsulation like there is in Java and the like. However, I'm wondering if there aren't any exceptions, particularly with abstract classes that have disparate implementations.
Let's say I'm writing a bunch of abstract classes (because I am) and that they represent things having to do with version control systems like repositories and revisions (because they do). Something like an SvnRevision and an HgRevision and a GitRevision are very closely semantically linked, and I want them to be able to do the same things (so that I can have code elsewhere that acts on any kind of Repository object, and is agnostic of the subclass), which is why I want them to inherit from an abstract class. However, their implementations vary considerably.
So far, the subclasses that have been implemented share a lot of attribute names, and in a lot of code outside of the classes themselves, direct attribute access is used. For example, every subclass of Revision has an author attribute, and a date attribute, and so on. However, the attributes aren't described anywhere in the abstract class. This seems to me like a very fragile design.
If someone wants to write another implementation of the Revision class, I feel like they should be able to do so just by looking at the abstract class. However, an implementation of the class that satisfies all of the abstract methods will almost certainly fail, because the author won't know that they need attributes called 'author' and 'date' and so on, so code that tries to access Revision.author will throw an exception. Probably not hard to find the source of the problem, but irritating nonetheless, and it just feels like an inelegant design.
My solution was to write accessor methods for the abstract classes (get_id, get_author, etc.). I thought this was actually a pretty clean solution, since it eliminates arbitrary restrictions on how attributes are named and stored, and just makes clear what data the object needs to be able to access. Any class that implements all of the methods of the abstract class will work... that feels right.
Anyways, the team I'm working with hates this solution (seemingly for the reason that accessors are unpythonic, which I can't really argue with). So... what's the alternative? Documentation? Or is the problem I'm imagining a non-issue?
Note: I've considered properties, but I don't think they're a cleaner solution.
Note: I've considered properties, but I don't think they're a cleaner solution.
But they are. By using properties, you'll have the class signature you want, while being able to use the property as an attribute itself.
def _get_id(self):
return self._id
def _set_id(self, newid):
self._id = newid
Is likely similar to what you have now. To placate your team, you'd just need to add the following:
id = property(_get_id, _set_id)
You could also use property as a decorator:
#property
def id(self):
return self._id
#id.setter
def id(self, newid):
self._id = newid
And to make it readonly, just leave out set_id/the id.setter bit.
You've missed the point. It isn't the lack of encapsulation that removes the need for accessors, it's the fact that, by changing from a direct attribute to a property, you can add an accessor at a later time without changing the published interface in any way.
In many other languages, if you expose an attribute as public and then later want to wrap some code round it on access or mutation then you have to change the interface and anyone using the code has at the very least to recompile and possibly to edit their code also. Python isn't like that: you can flip flop between attribute or property just as much as you want and no code that uses the class will break.
Only behind a property.
"they should be able to do so just by looking at the abstract class"
Don't know what this should be true. A "programmer's guide", a "how to extend" document, plus some training seems appropriate to me.
"the author won't know that they need attributes called 'author' and 'date' and so on".
In that case, the documentation isn't complete. Perhaps the abstract class needs a better docstring. Or a "programmer's guide", or a "how to extend" document.
Also, it doesn't seem very difficult to (1) document these attributes in the docstring and (2) provide default values in the __init__ method.
What's wrong with providing extra support for programmers?
It sounds like you have a social problem, not a technical one. Writing code to solve a social problem seems like a waste of time and money.
The discussion already ended a year ago, but this snippet seemed telltale, it's worth discussing:
However, an implementation of the
class that satisfies all of the
abstract methods will almost certainly
fail, because the author won't know
that they need attributes called
'author' and 'date' and so on, so code
that tries to access Revision.author
will throw an exception.
Uh, something is deeply wrong. (What S. Lott said, minus the personal comments).
If these are required members, aren't they referenced (if not required) in the constructor, and defined by docstring? or at very least, as required args of methods, and again documented?
How could users of the class not know what the required members are?
To be the devil's advocate, what if your constructor(s) requires you to supply all the members that will/may be required, what issue does that cause?
Also, are you checking the parameters when passed, and throwing informative exceptions?
(The ideological argument of accessor-vs-property is a sidebar. Properties are preferable but I don't think that's the issue with your class design.)
This question already has answers here:
How do I access the child classes of an object in django without knowing the name of the child class?
(8 answers)
Closed 7 years ago.
I have a model
BaseModel
and several subclasses of it
ChildModelA(BaseModel), ChildModelB(BaseModel), ...
using multi-table inheritance. In future I plan to have dozens of subclass models.
All subclasses have some implementation of method
do_something()
How can I call do_somthing from a BaseModel instance?
Almost identical problem (without solution) is posted here:
http://peterbraden.co.uk/article/django-inheritance
A simpler question: how I resolve BaseModel instnace to one of its subclasses instance without checking all possible subclasses?
If you want to avoid checking all possible subclasses, the only way I can think of would be to store the class name associated with the subclass in a field defined on the base class. Your base class might have a method like this:
def resolve(self):
module, cls_name = self.class_name.rsplit(".",1)
module = import_module(module)
cls = getattr(module, cls_name)
return cls.objects.get(pk=self.pk)
This answer does not make me happy and I too would love to see a better solution, as I will be facing a similar problem soon.
Will you ever be working with an instance of the base type or will you always be working with instances of the children? If the latter is the case then call the method, even if you have a reference to the base type since the object itself IS-A child type.
Since Python support duck typing this means that your method call will be bond appropriately since the child instance will truly have this method.
A pythonic programming style which
determines an object’s type by
inspection of its method or attribute
signature rather than by explicit
relationship to some type object (“If
it looks like a duck and quacks like a
duck, it must be a duck.”) By
emphasizing interfaces rather than
specific types, well-designed code
improves its flexibility by allowing
polymorphic substitution. Duck-typing
avoids tests using type() or
isinstance(). (Note, however, that
duck-typing can be complemented with
abstract base classes.) Instead, it
typically employs hasattr() tests or
EAFP programming.
Note that EAFP stands for Easier to Ask Forgiveness than Permission:
Easier to ask for forgiveness than permission. This common Python coding style assumes the existence of valid keys or attributes and catches exceptions if the assumption proves false. This clean and fast style is characterized by the presence of many try and except statements. The technique contrasts with the LBYL style common to many other languages such as C.
I agree with Andrew. On a couple of sites we have a class that supports a whole bunch of methods (but not fields (this was pre-ORM refactor)) that are common to most-but-not-all of our content classes. They make use of hasattr to sidestep situations where the method doesn't make sense.
This means most of our classes are defined as:
class Foo(models.Model, OurKitchenSinkClass):
Basically it's sort of a MixIn type of thing. Works great, easy to maintain.
I was wondering if I have a couple of models which both include fields like "meta_keywords" or "slug" which have to do with the web page the model instance will be displayed on, whether it would be advisable to break those page metadata elements out into their own class, say PageMeta, and have my other models subclass those via multiple inheritance?
General advice for a lightly-specified question:
Nontrivial multiple inheritance in Python requires Advanced Techniques to deal with the metaclass/metatype conflict. Look over this recipe from the ActiveState archives and see if it looks like the kind of stuff you like:
Extract from linked recipe:
The simplest case where a metatype
conflict happens is the following.
Consider a class A with metaclass M_A
and a class B with an independent
metaclass M_B; suppose we derive C
from A and B. The question is: what is
the metaclass of C ? Is it M_A or M_B
?
The correct answer (see the book
"Putting metaclasses to work" for a
thoughtful discussion) is M_C, where
M_C is a metaclass that inherits from
M_A and M_B.
However, Python is not that magic, and
it does not automatically create M_C.
Instead, it raises a TypeError,
warning the programmer of the possible
confusion.
Consequently, I recommend limiting your use of multiple inheritance in Python to the following cases:
You must, because your problem domain requires you to combine two separately-maintained single-inheritance libraries.
You have achieved such fluency with metatype and metaclass that you can write recipe 204197 or its equivalent as easily and confidently as you can write a print statement.
Edit:
Here's Guido van Rossum in An Introduction to Python:
It is clear that indiscriminate use of
multiple inheritance is a maintenance
nightmare, given the reliance in
Python on conventions to avoid
accidental name conflicts.
Here he is again in PEP 253, which describes the ideas which were incorporated into Python, but not the implementation:
Metatypes determine various policies
for types, such as what
happens when a type is called, how dynamic types are (whether a
type's dict can be modified after it is created), what the
method resolution order is, how instance attributes are looked
up, and so on.
I'll argue that left-to-right depth-first is not the best
solution when you want to get the most use from multiple
inheritance.
I'll argue that with multiple inheritance, the metatype of the
subtype must be a descendant of the metatypes of all base types.
This does not mean you shouldn't use multiple inheritance; I'm just warning you so you won't be suprised one day to find yourself slapping your forehead and exclaiming "D'oh!
The metatype of one of my subtypes isn't a descendant of the metatypes of all its base types! Don't you hate when that happens?"
I recently discovered metaclasses in python.
Basically a metaclass in python is a class that creates a class. There are many useful reasons why you would want to do this - any kind of class initialisation for example. Registering classes on factories, complex validation of attributes, altering how inheritance works, etc. All of this becomes not only possible but simple.
But in python, metaclasses are also plain classes. So, I started wondering if the abstraction could usefully go higher, and it seems to me that it can and that:
a metaclass corresponds to or implements a role in a pattern (as in GOF pattern languages).
a meta-metaclass is the pattern itself (if we allow it to create tuples of classes representing abstract roles, rather than just a single class)
a meta-meta-metaclass is a pattern factory, which corresponds to the GOF pattern groupings, e.g. Creational, Structural, Behavioural. A factory where you could describe a case of a certain type of problem and it would give you a set of classes that solved it.
a meta-meta-meta-metaclass (as far as I could go), is a pattern factory factory, a factory to which you could perhaps describe the type of your problem and it would give you a pattern factory to ask.
I have found some stuff about this online, but mostly not very useful. One problem is that different languages define metaclasses slightly differently.
Has anyone else used metaclasses like this in python/elsewhere, or seen this used in the wild, or thought about it? What are the analogues in other languages? E.g. in C++ how deep can the template recursion go?
I'd very much like to research it further.
This reminds me of the eternal quest some people seem to be on to make a "generic implementation of a pattern." Like a factory that can create any object (including another factory), or a general-purpose dependency injection framework that is far more complex to manage than simply writing code that actually does something.
I had to deal with people intent on abstraction to the point of navel-gazing when I was managing the Zend Framework project. I turned down a bunch of proposals to create components that didn't do anything, they were just magical implementations of GoF patterns, as though the pattern were a goal in itself, instead of a means to a goal.
There's a point of diminishing returns for abstraction. Some abstraction is great, but eventually you need to write code that does something useful.
Otherwise it's just turtles all the way down.
To answer your question: no.
Feel free to research it further.
Note, however, that you've conflated design patterns (which are just ideas) with code (which is an implementation.)
Good code often reflects a number of interlocking design patterns. There's no easy way for formalize this. The best you can do is a nice picture, well-written docstrings, and method names that reflect the various design patterns.
Also note that a meta-class is a class. That's a loop. There's no higher level of abstractions. At that point, it's just intent. The idea of meta-meta-class doesn't mean much -- it's a meta-class for meta-classes, which is silly but technically possible. It's all just a class, however.
Edit
"Are classes that create metaclasses really so silly? How does their utility suddenly run out?"
A class that creates a class is fine. That's pretty much it. The fact that the target class is a meta class or an abstract superclass or a concrete class doesn't matter. Metaclasses make classes. They might make other metaclasses, which is weird, but they're still just metaclasses making classes.
The utility "suddenly" runs out because there's no actual thing you need (or can even write) in a metaclass that makes another metaclass. It isn't that it "suddenly" becomes silly. It's that there's nothing useful there.
As I seed, feel free to research it. For example, actually write a metaclass that builds another metaclass. Have fun. There might be something useful there.
The point of OO is to write class definitions that model real-world entities. As such, a metaclass is sometimes handy to define cross-cutting aspects of several related classes. (It's a way to do some Aspect-Oriented Programming.) That's all a metaclass can really do; it's a place to hold a few functions, like __new__(), that aren't proper parts of the class itself.
During the History of Programming Languages conference in 2007, Simon Peyton Jones commented that Haskell allows meta programming using Type Classes, but that its really turtles all the way down. You can meta-meta-meta-meta etc program in Haskell, but that he's never heard of anyone using more than 3 levels of indirection.
Guy Steele pointed out that its the same thing in Lisp and Scheme. You can do meta-programming using backticks and evals (you can think of a backtick as a Python lambda, kinda), but he's never seen more than 3 backticks used.
Presumably they have seen more code than you or I ever has, so its only a slight exaggeration to say that no-one has ever gone beyond 3 levels of meta.
If you think about it, most people don't ever use meta-programming, and two levels is pretty hard to wrap your head around. I would guess that three is nearly impossible, and the that last guy to try four ended up in an asylum.
Since when I first understood metaclasses in Python, I kept wondering "what could be done with a meta-meta class?". This is at least 10 years ago - and now, just a couple months ago, it became clear for me that there is one mechanism in Python class creation that actually involves a "meta-meta" class. And therefore, it is possible to try to imagine some use for that.
To recap object instantiation in Python: Whenever one instantiates an object in Python by "calling" its class with the same syntax used for calling an ordinary function, the class's __new__ and __init__. What "orchestrates" the calling of these methods on the class is exactly the class'metaclass' __call__ method. Usually when one writes a metaclass in Python, either the __new__ or __init__ method of the metaclass is customized.
So, it turns out that by writing a "meta-meta" class one can customize its __call__ method and thus control which parameters are passed and to the metaclass's __new__ and __init__ methods, and if some other code is to be called before of after those. What turns out in the end is that metcalsses themselves are usually hardcoded and one needs just a few, if any, even in very large projects. So any customization that might be done at the "meta meta" call is usually done directly on the metaclass itself.
And them, there are those other less frequent uses for Python metaclasses - one can customize an __add__ method in a metaclass so that the classes they define are "addable", and create a derived class having the two added classes as superclasses. That mechanism is perfectly valid with metaclasses as well - therefore, so just we "have some actual code", follows an example of "meta-meta" class that allows one to compose "metaclasses" for a class just by adding them on class declaration:
class MM(type):
def __add__(cls, other):
metacls = cls.__class__
return metacls(cls.__name__ + other.__name__, (cls, other), {})
class M1(type, metaclass=MM):
def __new__(metacls, name, bases, namespace):
namespace["M1"] = "here"
print("At M1 creation")
return super().__new__(metacls, name, bases, namespace)
class M2(type, metaclass=MM):
def __new__(metacls, name, bases, namespace):
namespace["M2"] = "there"
print("At M2 creation")
return super().__new__(metacls, name, bases, namespace)
And we can see that working on the interactive console:
In [22]: class Base(metaclass = M1 + M2):
...: pass
...:
At M1 creation
At M2 creation
Note that as different metaclasses in Python are usually difficult to combine, this can actually be useful by allowing a user-made metaclass to be combined with a library's or stdlib one, without this one having to be explicitly declared as parent of the former:
In [23]: import abc
In [24]: class Combined(metaclass=M1 + abc.ABCMeta):
...: pass
...:
At M1 creation
The class system in Smalltalk is an interesting one to study. In Smalltalk, everything is an object and every object has a class. This doesn't imply that the hierarchy goes to infinity. If I remember correctly, it goes something like:
5 -> Integer -> Integer class -> Metaclass -> Metaclass class -> Metaclass -> ... (it loops)
Where '->' denotes "is an instance of".