Inspired by a great question (and bunch of great answers) from here.
Does the statement "Code against an interface, not an object" have any significance in Python?
I'm looking for answers like the ones in the Original Question but with Python snippets and thoughts.
"Code against an interface, not an object" doesn't make literal sense in Python because the language doesn't have an interface feature. The rough Python equivalent is "use duck typing." If you want to see if an object is a duck, in other words, you should check to see whether it has a
quack()
method, or better yet try toquack()
and provide appropriate error handling, not test to see if it is an instance ofDuck
.Common duck types in Python are files (well, really, file-like objects), mappings (
dict
-like objects), callables (function-like objects), sequences (list
-like objects), and iterables (things you can iterate over, which can be containers or generators).As an example, Python features that want a file will generally be happy to accept an object that implements the methods of
file
it needs; it needn't be derived from thefile
class. To use an object as standard out, for example, the main thing it is going to need is awrite()
method (and maybeflush()
andclose()
, which needn't actually do anything). Similarly, a callable is any object that has a__call__()
method; it needn't be derived from the function type (in fact, you can't derive from the function type).You should take a similar approach. Check for the methods and attributes you need for what you're going to do with an object. Better yet, document what you expect and assume that whoever is calling your code is not a total doofus. (If they give you an object you can't use, they will certainly figure that out quickly enough from the errors they get.) Test for specific types only when necessary. It is necessary at times, which is why Python gives you
type()
,isinstance()
, andissubclass()
, but be careful with them.Python's duck typing is equivalent to "code against an interface, not an object" in the sense that you're advised not to make your code too reliant on an object's type, but rather to see whether it has the interface you need. The difference is that in Python, "interface" just means an informal bundle of attributes and methods of an object that provide a certain behavior, rather than a language construct specifically named
interface
.You can formalize Python "interfaces" to some extent using the
abc
module, which allows you to declare that a given class is a subclass of a given "abstract base class" (interface) using any criteria you desire, such as "it has attributescolor
,tail_length
, andquack
, andquack
is callable." But this is still much less strict than static languages having an interface feature.The proper quote is "program against an interface, not an implementation". The principle holds the same way in Python that it did in Smalltalk, the language it in which it originated.
Yes. It has the same significance in Python as it has in the language that this quote originated from (Smalltalk), and in every other language.
To understand interfaces in Python you have to understand duck-typing. From the very Python glossary:
Python encourages coding for interfaces, only they are not enforced but by convention. Concepts like iterables, callables or the file interface are very pervasive in Python - as well the builtins that rely on interfaces like map, filter or reduce.
An interface means you expect certain methods to be present and standardised across objects; that is the point of an interface or abstract base class, or whatever implementation you wish to consider.
For example (Java), one might have an interface for symmetric encryption like so:
Then you can implement it:
It is then possible to declare an object like so:
What have we done here? Well, we've created this object
c
whose type must match that of the interface.c
can refer to anything that matches this interface, so the next stage would be:You can now call methods you expect a
cipher
to have.That's java. Now here's what you do in python:
When you want to use this, and you're not sure that the object you've been passed is, just try to use it:
Here, you're relying on c.encrypt's implementation to
raise
if it can't handle what it has been passed, if the method exists. Of course, ifc
is a string type and therefore not of the right type you require, it will also throw automatically, and you will catch (hopefully).In short, one form of programming is typed such that you have to obey the interface rules, the other is saying you don't even need to write them down, you simply trust that if it didn't error, it worked.
I hope that shows you the practical difference between the two.