Java абстрактно/дизайн на интерфейс в Python

Имам редица класове, които споделят едни и същи методи, само с различни реализации. В Java би имало смисъл всеки от тези класове да имплементира интерфейс или да разшири абстрактен клас. Python има ли нещо подобно на това или трябва да използвам алтернативен подход?


person Matt    schedule 18.11.2011    source източник
comment
Вижте stackoverflow.com /questions/372042/ Обърнете внимание на връзката към интерфейсния модул на Zope като имплементация на подобни на Java интерфейси.   -  person alex vasi    schedule 18.11.2011
comment
има ли точен отговор на това има ли интерфейси или не?   -  person Charlie Parker    schedule 19.06.2017


Отговори (4)


Зад интерфейсите в Python има известна история. Първоначалното отношение, което господстваше в продължение на много години, е, че нямате нужда от тях: Python работи на принципа EAFP (по-лесно да поискате прошка, отколкото разрешение). Тоест, вместо да посочите, че приемате, не знам, ICloseable обект, вие просто се опитвате да close обекта, когато е необходимо, и ако той повдигне изключение, той поражда изключение.

Така че при този манталитет вие просто ще напишете вашите класове отделно и ще ги използвате както искате. Ако едно от тях не отговаря на изискванията, вашата програма ще предизвика изключение; обратното, ако напишете друг клас с правилните методи, тогава той просто ще работи, без да е необходимо да уточнявате, че той изпълнява вашия конкретен интерфейс.

Това работи доста добре, но има определени случаи на използване на интерфейси, особено при по-големи софтуерни проекти. Окончателното решение в Python беше да се предостави модулът abc, който ви позволява да пишете < em>абстрактни базови класове т.е. класове, които не можете да създадете, освен ако не замените всичките им методи. Ваше е решението дали смятате, че използването им си струва.

PEP, въвеждащ ABCs, обяснява много по-добре, отколкото мога:

В областта на обектно-ориентираното програмиране моделите на използване за взаимодействие с обект могат да бъдат разделени на две основни категории, които са „извикване“ и „инспекция“.

Извикване означава взаимодействие с обект чрез извикване на неговите методи. Обикновено това се комбинира с полиморфизъм, така че извикването на даден метод може да изпълни различен код в зависимост от типа на обекта.

Инспекцията означава способността за външен код (извън методите на обекта) да изследва типа или свойствата на този обект и да взема решения как да третира този обект въз основа на тази информация.

И двата модела на използване обслужват една и съща обща цел, която е да могат да поддържат обработката на различни и потенциално нови обекти по еднакъв начин, но в същото време позволяват решенията за обработка да бъдат персонализирани за всеки различен тип обект.

В класическата OOP теория извикването е предпочитаният модел на използване, а инспекцията активно се обезсърчава, като се счита за реликва от по-ранен, процедурен стил на програмиране. На практика обаче този възглед е просто твърде догматичен и негъвкав и води до вид твърдост на дизайна, която е в голямо противоречие с динамичната природа на език като Python.

По-специално, често има нужда да се обработват обекти по начин, който не е бил предвиден от създателя на класа обект. Не винаги е най-доброто решение да се вграждат във всеки обект методи, които задоволяват нуждите на всеки възможен потребител на този обект. Освен това има много мощни философии за изпращане, които са в пряк контраст с класическото OOP изискване поведението да бъде стриктно капсулирано в рамките на обект, като примери са логика, управлявана от правило или съвпадение на шаблони.

От друга страна, една от критиките на инспекцията от класическите теоретици на ООП е липсата на формализъм и ad hoc характера на това, което се инспектира. В език като Python, в който почти всеки аспект на даден обект може да бъде отразен и директно достъпен от външен код, има много различни начини за тестване дали даден обект отговаря на конкретен протокол или не. Например, ако попитате „този обект променлив контейнер на последователност ли е?“, човек може да потърси базов клас „списък“ или може да потърси метод с име „_getitem_“. Но имайте предвид, че въпреки че тези тестове може да изглеждат очевидни, нито един от тях не е правилен, тъй като единият генерира фалшиви отрицателни, а другият фалшиви положителни резултати.

Общоприетото средство за защита е стандартизирането на тестовете и групирането им в официална подредба. Това се прави най-лесно чрез свързване с всеки клас на набор от стандартни тествани свойства, или чрез механизма за наследяване, или по някакъв друг начин. Всеки тест носи със себе си набор от обещания: съдържа обещание за общото поведение на класа и обещание какви други методи на класа ще бъдат налични.

Този PEP предлага конкретна стратегия за организиране на тези тестове, известни като абстрактни базови класове или ABC. ABC са просто класове на Python, които се добавят към дървото на наследяване на обект, за да сигнализират определени характеристики на този обект на външен инспектор. Тестовете се правят с помощта на isinstance() и наличието на определен ABC означава, че тестът е преминал.

В допълнение, ABC определят минимален набор от методи, които установяват характерното поведение на типа. Кодът, който разграничава обекти въз основа на техния тип ABC, може да се довери, че тези методи винаги ще присъстват. Всеки от тези методи е придружен от обобщена абстрактна семантична дефиниция, която е описана в документацията за ABC. Тези стандартни семантични дефиниции не се прилагат, но са силно препоръчителни.

Както всички останали неща в Python, тези обещания са в естеството на джентълменско споразумение, което в този случай означава, че макар езикът да изпълнява някои от обещанията, дадени в ABC, зависи от изпълнителя на конкретния клас да гарантира, че останалите се пазят.

person Katriel    schedule 18.11.2011

Не съм толкова запознат с Python, но рискувам да предположа, че не е така.

Причината, поради която интерфейсите съществуват в Java е, че те определят договор. Нещо, което имплементира java.util.List, например, е гарантирано, че има add() метод, за да съответства на общото поведение, както е дефинирано в интерфейса. Бихте могли да пуснете всяка (здрава) реализация на List, без да знаете конкретния му клас, да извикате последователност от методи, дефинирани в интерфейса, и да получите същото общо поведение.

Освен това, както разработчикът, така и компилаторът могат да знаят, че такъв метод съществува и може да бъде извикан на въпросния обект, дори и да не знаят точния му клас. Това е форма на полиморфизъм, която е необходима със статичното типизиране, за да позволи различни класове за изпълнение, но въпреки това да знае, че всички те са законни.

Това всъщност няма смисъл в Python, защото не е статично въведен. Не е необходимо да декларирате класа на обект, нито да убеждавате компилатора, че методите, които извиквате, определено съществуват. „Интерфейсите“ в света на патешко писане са толкова прости, колкото извикването на метода и доверието, че обектът може да обработи това съобщение по подходящ начин.

Забележка – редакциите от по-осведомени специалисти по Python са добре дошли.

person Andrzej Doyle    schedule 18.11.2011
comment
Или защото в Java няма множествено наследяване. - person alex vasi; 18.11.2011

Може би можете да използвате нещо подобно. Това ще действа като абстрактен клас. Следователно всеки подклас е принуден да имплементира func1()

class Abstract:

    def func1(self):
        raise NotImplementedError("The method not implemented")
person M S    schedule 18.11.2011

Написах библиотека в 3.5+, която позволява писане на интерфейси в Python.

Същността е да напишете декоратор на клас с помощта на inspect.

import inspect


def implements(interface_cls):
    def _decorator(cls):
        verify_methods(interface_cls, cls)
        verify_properties(interface_cls, cls)
        verify_attributes(interface_cls, cls)
        return cls

    return _decorator


def verify_methods(interface_cls, cls):
    methods_predicate = lambda m: inspect.isfunction(m) or inspect.ismethod(m)
    for name, method in inspect.getmembers(interface_cls, methods_predicate):
        signature = inspect.signature(method)
        cls_method = getattr(cls, name, None)
        cls_signature = inspect.signature(cls_method) if cls_method else None
        if cls_signature != signature:
            raise NotImplementedError(
                "'{}' must implement method '{}({})' defined in interface '{}'"
                .format(cls.__name__, name, signature, interface_cls.__name__)
            )


def verify_properties(interface_cls, cls):
    prop_attrs = dict(fget='getter', fset='setter', fdel='deleter')
    for name, prop in inspect.getmembers(interface_cls, inspect.isdatadescriptor):
        cls_prop = getattr(cls, name, None)
        for attr in prop_attrs:
            # instanceof doesn't work for class function comparison
            if type(getattr(prop, attr, None)) != type(getattr(cls_prop, attr, None)):
                raise NotImplementedError(
                    "'{}' must implement a {} for property '{}' defined in interface '{}'"  # flake8: noqa
                    .format(cls.__name__, prop_attrs[attr], name, interface_cls.__name__)
                )


def verify_attributes(interface_cls, cls):
    interface_attributes = get_attributes(interface_cls)
    cls_attributes = get_attributes(cls)
    for missing_attr in (interface_attributes - cls_attributes):
        raise NotImplementedError(
            "'{}' must have class attribute '{}' defined in interface '{}'"
            .format(cls.__name__, missing_attr, interface_cls.__name__)
        )


def get_attributes(cls):
    boring = dir(type('dummy', (object,), {}))
    return set(item[0] for item in inspect.getmembers(cls)
               if item[0] not in boring and not callable(item[1]))

След това можете да пишете класове по този начин:

class Quackable:
    def quack(self) -> bool:
        pass


@implements(Quackable)
class MallardDuck:    
    def quack(self) -> bool:
        pass

По-долу обаче ще ви даде грешка:

@implements(Quackable)
class RubberDuck:    
    def quack(self) -> str:
        pass

NotImplementedError: 'RubberdDuck' must implement method 'quack((self) -> bool)' defined in interface 'Quackable'
person Kamil Sindi    schedule 28.05.2017