11

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?

goteguru
  • 443
  • 3
  • 14
  • 1
    Reminds me of this https://stackoverflow.com/a/50381071/ - but my custom solution may not play nice with your linter. The last example - but with type annotation seems best to me. Btw. "attribute" may be a better keyword than "object property". – krassowski Jun 11 '19 at 07:10
  • term "attribute" added to clarify. Your solution does what I need I think, will see if the linter likes it too. Duck typing is funky, but somewhere I should be able to describe what makes a duck. – goteguru Jun 11 '19 at 10:45
  • I am somewhat confused by this mixing typing and abc lingo. Are you looking for a *static type check* assertion or an *abc runtime* assertion? And your requirement to have some abstract/protocol ``MyType`` which guarantees that each implementation *class* has a ``myprop`` *attribute*? – MisterMiyagi Oct 13 '21 at 15:37
  • @MisterMiyagi ABC is definitely about typing. There is no mixing here. I'd like to define a type safe interface (or more preferably a contract). I'd like to statically enforce some rules about the object parameters like "must have a specific property". I'm *definitely* not interested in runtime assertations, that could be done trivially. Certainly it's not possible in pure python, but might be possible with the linter. – goteguru Oct 13 '21 at 17:44
  • Right, let's focus on what exactly you need then. By "property" do you mean specifically a ``property`` or just generally any attribute? Do you actually *want* a "class property" or was that just part of your non-working attempt? Do you need the type checker to verify for every implementation ``class MyType(MyInterface)``, ``MyType`` satisfies ``MyInterface``, or do you just want to be able to define a ``class MyType`` that can be safely used wherever a ``MyInterface`` is expected? – MisterMiyagi Oct 13 '21 at 17:53

1 Answers1

2

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

Peter Trcka
  • 1,279
  • 1
  • 16
  • 21
  • I would really appreciate feedback, what was wrong? – Peter Trcka Oct 07 '21 at 18:47
  • Can you perhaps clarify how the individual parts are related? The old ``abstractproperty`` doesn't imply a setter. A setter doesn't affect how a ``property`` is looked up on its *class*. Assigning ``myprop = 10`` doesn't create a "static property" – no matter whether the baseclass has a property of that name – it's just a class attribute. – MisterMiyagi Oct 07 '21 at 19:49
  • @MisterMiyagi I have read the question more precise and yes you are correct, again ;). I have edited my answer. – Peter Trcka Oct 07 '21 at 20:22
  • Probably this is the best we have in 2021 using python 3.7+, (backported to 3.6). Unfortunately it's not really an answer (I fear there is no true solution). Dataclass won't provide a contract and actually not really different to example #3 in the OP (actually less than that). – goteguru Oct 13 '21 at 14:13
  • @goteguru I thing this is very much possible. The question is just not (yet) clear on what "this" actually is. – MisterMiyagi Oct 13 '21 at 15:38