Search code examples
pythonpython-3.xinterfaceabstract-classcontract

How to write interface (contract) for object property (attribute) existence in python?


I'd like to have a contract on a parameter of some function to enforce the parameter object must have specific property. I understand python isn't a strictly typed language, but having contracts and interfaces is very useful sometimes. Python now has type hints, which is great, so we can do this:

def myfunc(myparam: MyType) -> SomeType:
    myparam.myprop # this should exist

But how can I say MyType must have a specific object property (myprop) without inserting assertions and throwing exceptions in run-time? I could define abstract classes with abc metaclasses, which can be used as interfaces.

from abc import ABC, abstractmethod
class MyInterface(ABC):
     @property
     @abstractmethod
     def myprop(self) -> int: pass

now somewhere in the code I could define MyType as:

class MyType(MyInterface):
    myprop = 8

It's working, but myprop is a class property and not an object property (attribute). Of course I could do this:

class MyType(MyInterface):
    myprop = 0
    def __init__(self):
        self.myprop = 8

Fine, but I had to define an (unnecessary) class ("static") property and effectively hide it with an object property. Not very clean. Moreover now I have a default value for myprop which is not what I want. But if I do this:

class MyType(MyInterface):
    myprop = None  # wrong type here
    def __init__(self):
        self.myprop = 8

it's wrong because, myprop must be int and can not be None, which is correctly caught by the linter. There shall be an object property without a class property.

The goal would be that a static checker like mypy could catch implementation errors where a class doesn't obey the defined interface or contract which requires the parameter instance must have some property.

What is the pythonic (or not so pythonic) way to achieve this?


Solution

  • You don't need to create new interface or implement ABC. You can use @dataclass. I am using default code checker in pycharm (warnings are in comments).

    from dataclasses import dataclass
    
    
    @dataclass
    class MyType:
        my_prop: int
    
    
    @dataclass
    class SomeType:
        my_prop_out: int
    
    
    def my_func_ok(my_param: MyType) -> SomeType:
        some_type = SomeType(my_prop_out=my_param.my_prop)
        return some_type
    
    
    def my_func_bad(my_param: MyType) -> SomeType:
        return my_param              # this is not returning SomeType
    
    
    my_type = MyType()               # this is expecting to set my_prop
    my_type = MyType(my_prop="sss")  # this is expecting to set my_prop with int not str
    my_func_ok(my_param=100)         # this is expecting MyType object
    
    my_func_ok(my_param=MyType(my_prop=10))  # this is correct, no errors
    

    I am adding picture of pycharm code checker warnings:


    enter image description here