Python'da soyut dersler yapmak mümkün müdür?


321

Python'da nasıl bir sınıf veya yöntem özeti oluşturabilirim?

Yeniden tanımlamayı denedim __new__():

class F:
    def __new__(cls):
        raise Exception("Unable to create an instance of abstract class %s" %cls)

ama şimdi Gböyle miras bir sınıf oluşturmak eğer F:

class G(F):
    pass

o zaman ben de Gsüper sınıfın __new__yöntemini çağırdığı için başlatamıyorum .

Soyut bir sınıf tanımlamanın daha iyi bir yolu var mı?


Evet, abc (soyut temel sınıflar) modülü ile python'da soyut sınıflar oluşturabilirsiniz. Bu site size yardımcı olacaktır: http://docs.python.org/2/library/abc.html
ORION

Yanıtlar:


554

abcSoyut sınıflar oluşturmak için modülü kullanın . abstractmethodBir yöntem özeti bildirmek için dekoratörü kullanın ve Python sürümünüze bağlı olarak üç yoldan birini kullanarak bir sınıf özeti bildirin.

Python 3.4 ve üstü sürümlerde devralınabilirsiniz ABC. Python'un önceki sürümlerinde, sınıfınızın meta sınıfını olarak belirtmeniz gerekir ABCMeta. Metaclass'ın belirtilmesi Python 3 ve Python 2'de farklı sözdizimine sahiptir. Üç olasılık aşağıda gösterilmiştir:

# Python 3.4+
from abc import ABC, abstractmethod
class Abstract(ABC):
    @abstractmethod
    def foo(self):
        pass
# Python 3.0+
from abc import ABCMeta, abstractmethod
class Abstract(metaclass=ABCMeta):
    @abstractmethod
    def foo(self):
        pass
# Python 2
from abc import ABCMeta, abstractmethod
class Abstract:
    __metaclass__ = ABCMeta

    @abstractmethod
    def foo(self):
        pass

Hangi yolu kullanırsanız kullanın, soyut yöntemlere sahip soyut bir sınıf başlatamazsınız, ancak bu yöntemlerin somut tanımlarını sağlayan bir alt sınıfı başlatabilirsiniz:

>>> Abstract()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class Abstract with abstract methods foo
>>> class StillAbstract(Abstract):
...     pass
... 
>>> StillAbstract()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class StillAbstract with abstract methods foo
>>> class Concrete(Abstract):
...     def foo(self):
...         print('Hello, World')
... 
>>> Concrete()
<__main__.Concrete object at 0x7fc935d28898>

9
@abstractmethod ne yapıyor? Ona neden ihtiyacın var? Sınıf zaten soyut olarak kurulmuşsa, derleyici / yorumlayıcı tüm yöntemlerin söz konusu soyut sınıftan olduğunu bilmemeli midir?
Charlie Parker

29
@CharlieParker - @abstractmethodo kadar dekore işlev yapar gerekir sınıf örneği oluşturulmak önce geçersiz kılınan olun. Dokümanlardan:A class that has a metaclass derived from ABCMeta cannot be instantiated unless all of its abstract methods and properties are overridden.
Sahte İsim

6
@CharlieParker - Temel olarak, bir sınıfı, alt sınıfın hemen somutlaştırmak için belirli bir yöntem paketini uygulaması gerektiği şekilde tanımlamanızı sağlar .
Sahte İsim

13
Kullanıcıların @abstractmethod yöntemleri olmadan Abstract () oluşturmasını engellemenin bir yolu var mı?
Joe

2
@ Yöntem @abstractmethodiçin de kullanabileceğiniz gibi görünüyor __init__, bkz. Stackoverflow.com/q/44800659/547270
scrutari

108

Bunu yapmanın eski okul ( PEP 3119 öncesi ) yolu raise NotImplementedError, soyut bir yöntem çağrıldığında soyut sınıftadır.

class Abstract(object):
    def foo(self):
        raise NotImplementedError('subclasses must override foo()!')

class Derived(Abstract):
    def foo(self):
        print 'Hooray!'

>>> d = Derived()
>>> d.foo()
Hooray!
>>> a = Abstract()
>>> a.foo()
Traceback (most recent call last): [...]

Bu abcmodülün kullanımı ile aynı hoş özelliklere sahip değildir . Yine de soyut temel sınıfın kendisini başlatabilirsiniz ve çalışma zamanında soyut yöntemi çağırıncaya kadar hata bulamazsınız.

Ancak küçük bir dizi basit sınıfla, belki de sadece birkaç soyut yöntemle uğraşıyorsanız, bu yaklaşım abcdokümantasyondan geçmeye çalışmaktan biraz daha kolaydır .


1
Bu yaklaşımın sadeliğini ve etkinliğini takdir edin.
mohit6up

Haha, bunu OMSCS kursumun her yerinde gördüm ve ne olduğu hakkında hiçbir fikrim yoktu :)
mLstudent33

18

ABC modülü ile uğraşmak zorunda kalmadan çok kolay bir yol.

Gelen __init__özet bir sınıfı olmak istiyorum bu sınıfın yönteminin, sen kendini "türü" kontrol edebilirsiniz. Benlik türü temel sınıfsa, arayan kişi temel sınıfı somutlaştırmaya çalışır, bu nedenle bir istisna oluşturun. İşte basit bir örnek:

class Base():
    def __init__(self):
        if type(self) is Base:
            raise Exception('Base is an abstract class and cannot be instantiated directly')
        # Any initialization code
        print('In the __init__  method of the Base class')

class Sub(Base):
    def __init__(self):
        print('In the __init__ method of the Sub class before calling __init__ of the Base class')
        super().__init__()
        print('In the __init__ method of the Sub class after calling __init__ of the Base class')

subObj = Sub()
baseObj = Base()

Çalıştırıldığında şunları üretir:

In the __init__ method of the Sub class before calling __init__ of the Base class
In the __init__  method of the Base class
In the __init__ method of the Sub class after calling __init__ of the Base class
Traceback (most recent call last):
  File "/Users/irvkalb/Desktop/Demo files/Abstract.py", line 16, in <module>
    baseObj = Base()
  File "/Users/irvkalb/Desktop/Demo files/Abstract.py", line 4, in __init__
    raise Exception('Base is an abstract class and cannot be instantiated directly')
Exception: Base is an abstract class and cannot be instantiated directly

Bu, bir temel sınıftan devralınan bir alt sınıfı başlatabileceğinizi, ancak temel sınıfı doğrudan başlatamadığınızı gösterir.


11

Önceki cevapların çoğu doğruydu ama işte Python 3.7 için cevap ve örnek . Evet, soyut bir sınıf ve yöntem oluşturabilirsiniz. Hatırlatma olarak, bazen bir sınıf mantıksal olarak bir sınıfa ait bir yöntem tanımlamalıdır, ancak bu sınıf, yöntemin nasıl uygulanacağını belirleyemez. Örneğin, aşağıdaki Ebeveynler ve Bebekler sınıflarında ikisi de yer ama uygulama her biri için farklı olacak çünkü bebekler ve ebeveynler farklı türde yiyecekler yiyorlar ve yemek sayıları farklı. Yani, yemek yöntemi alt sınıfları AbstractClass.eat geçersiz kılar.

from abc import ABC, abstractmethod

class AbstractClass(ABC):

    def __init__(self, value):
        self.value = value
        super().__init__()

    @abstractmethod
    def eat(self):
        pass

class Parents(AbstractClass):
    def eat(self):
        return "eat solid food "+ str(self.value) + " times each day"

class Babies(AbstractClass):
    def eat(self):
        return "Milk only "+ str(self.value) + " times or more each day"

food = 3    
mom = Parents(food)
print("moms ----------")
print(mom.eat())

infant = Babies(food)
print("infants ----------")
print(infant.eat())

ÇIKTI:

moms ----------
eat solid food 3 times each day
infants ----------
Milk only 3 times or more each day

Belki import abcişe yaramaz.
Benyamin Jafari

Ayrıca init işlevinde @abstractmethod yazabilir miyiz ?
değişken

+1 Neden @abstractmethod def eat (self)? Eğer bu sınıf soyutsa ve dolayısıyla somutlaştırılmak istenmiyorsa, neden yemek yemekten geçiyorsunuz? Onsuz iyi çalışıyor
VMMF

7

Bu python 3'te çalışacak

from abc import ABCMeta, abstractmethod

class Abstract(metaclass=ABCMeta):

    @abstractmethod
    def foo(self):
        pass

Abstract()
>>> TypeError: Can not instantiate abstract class Abstract with abstract methods foo

4

Diğer cevaplarda açıklandığı gibi, evet Python'daki soyut sınıfları abcmodülü kullanarak kullanabilirsiniz . Ben soyut kullanarak gerçek bir örnek vermek Aşağıda @classmethod, @propertyve @abstractmethod(3.6+ Python kullanarak). Benim için kolayca kopyalayıp yapıştırabileceğim örneklerle başlamak genellikle daha kolaydır; Umarım bu cevap başkaları için de yararlıdır.

İlk olarak şu temel sınıfı oluşturalım Base:

from abc import ABC, abstractmethod

class Base(ABC):

    @classmethod
    @abstractmethod
    def from_dict(cls, d):
        pass

    @property
    @abstractmethod
    def prop1(self):
        pass

    @property
    @abstractmethod
    def prop2(self):
        pass

    @prop2.setter
    @abstractmethod
    def prop2(self, val):
        pass

    @abstractmethod
    def do_stuff(self):
        pass

Bizim Basesınıf her zaman olacaktır from_dict classmethod, bir property prop1(salt okunur olan) ve bir property prop2(aynı zamanda kümesi olabilir) adında bir işlevi yanı sıra do_stuff. Şimdi ne tür bir sınıf inşa edildiyse, Basetüm bunları yöntemler / özellikler için uygulamak zorunda kalacaktır. Bir yöntemin soyut olması için iki dekoratörün gerekli olduğunu classmethodve soyut olduğunu lütfen unutmayın property.

Şimdi böyle bir sınıf oluşturabiliriz A:

class A(Base):
    def __init__(self, name, val1, val2):
        self.name = name
        self.__val1 = val1
        self._val2 = val2

    @classmethod
    def from_dict(cls, d):
        name = d['name']
        val1 = d['val1']
        val2 = d['val2']

        return cls(name, val1, val2)

    @property
    def prop1(self):
        return self.__val1

    @property
    def prop2(self):
        return self._val2

    @prop2.setter
    def prop2(self, value):
        self._val2 = value

    def do_stuff(self):
        print('juhu!')

    def i_am_not_abstract(self):
        print('I can be customized')

Gerekli tüm yöntemler / özellikler uygulanır ve elbette - parçası olmayan ek işlevler de ekleyebiliriz Base(burada:) i_am_not_abstract.

Şimdi yapabiliriz:

a1 = A('dummy', 10, 'stuff')
a2 = A.from_dict({'name': 'from_d', 'val1': 20, 'val2': 'stuff'})

a1.prop1
# prints 10

a1.prop2
# prints 'stuff'

İstediğiniz gibi ayarlayamayız prop1:

a.prop1 = 100

dönecek

AttributeError: özellik ayarlanamıyor

Ayrıca from_dictyöntemimiz iyi çalışıyor:

a2.prop1
# prints 20

Şimdi Bböyle bir ikinci sınıf tanımlamışsak :

class B(Base):
    def __init__(self, name):
        self.name = name

    @property
    def prop1(self):
        return self.name

ve böyle bir nesneyi somutlaştırmaya çalıştı:

b = B('iwillfail')

bir hata alacağız

TypeError: do_stuff, from_dict, prop2 soyut yöntemleriyle soyut B sınıfı somutlaştırılamaz

tanımlanan her şeyi listeleme Basebiz uygulamak vermedi hangi B.


2

Ayrıca bu çalışır ve basit:

class A_abstract(object):

    def __init__(self):
        # quite simple, old-school way.
        if self.__class__.__name__ == "A_abstract": 
            raise NotImplementedError("You can't instantiate this abstract class. Derive it, please.")

class B(A_abstract):

        pass

b = B()

# here an exception is raised:
a = A_abstract()

2

Ayrıca __new__ yöntemini kendi yararınıza kullanabilirsiniz. Bir şey unuttun. __New__ yöntemi her zaman yeni nesneyi döndürür, böylece üst sınıfının yeni yöntemini döndürmeniz gerekir. Aşağıdaki gibi yapın.

class F:
    def __new__(cls):
        if cls is F:
            raise TypeError("Cannot create an instance of abstract class '{}'".format(cls.__name__))
        return super().__new__(cls)

Yeni yöntemi kullanırken, None anahtar sözcüğünü değil, nesneyi döndürmeniz gerekir. Tüm kaçırdığınız bu.


2

selfSoyut bir sınıfa geçtikleri için kabul edilen yanıtı ve diğerlerini tuhaf buluyorum . Soyut bir sınıf somutlaştırılmamıştır, bu yüzden a self.

Bu yüzden deneyin, işe yarıyor.

from abc import ABCMeta, abstractmethod


class Abstract(metaclass=ABCMeta):
    @staticmethod
    @abstractmethod
    def foo():
        """An abstract method. No need to write pass"""


class Derived(Abstract):
    def foo(self):
        print('Hooray!')


FOO = Derived()
FOO.foo()

1
Abc belgeleri bile kendi örnekler bağlantısında kendini kullanır
igor Smirnov

2
 from abc import ABCMeta, abstractmethod

 #Abstract class and abstract method declaration
 class Jungle(metaclass=ABCMeta):
     #constructor with default values
     def __init__(self, name="Unknown"):
     self.visitorName = name

     def welcomeMessage(self):
         print("Hello %s , Welcome to the Jungle" % self.visitorName)

     # abstract method is compulsory to defined in child-class
     @abstractmethod
     def scarySound(self):
         pass

Nice @Shivam Bharadwaj, aynı şekilde yaptım
Muhammad Irfan

-3

Kod snippet'inizde __new__, aynı şekilde alt sınıftaki yöntem için bir uygulama sağlayarak da çözebilirsiniz :

def G(F):
    def __new__(cls):
        # do something here

Ama bu bir hack ve ne yaptığınızı bilmediğiniz sürece size karşı tavsiyede bulunuyorum. Hemen hemen tüm durumlarda abc, benden önce başkalarının önerdiği modülü kullanmanızı öneririm .

Yeni (taban) sınıf oluşturmak zaman Ayrıca, alt sınıfı yapmak objectbu gibi: class MyBaseClass(object):. Artık bu kadar önemli olup olmadığını bilmiyorum, ancak kodunuzda stil tutarlılığını korumaya yardımcı oluyor


-4

@ TimGilbert'in eski okul cevabına hızlı bir eklenti ... soyut temel sınıfınızın init () yönteminin bir istisna atmasını sağlayabilirsiniz ve bu, somutlaştırılmasını engelleyecektir, değil mi?

>>> class Abstract(object):
...     def __init__(self):
...         raise NotImplementedError("You can't instantiate this class!")
...
>>> a = Abstract()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in __init__
NotImplementedError: You can't instantiate this class! 

6
Bu, alt sınıfların , ortak temel sınıflarında mantıksal olarak bulunması gereken herhangi bir ortak başlangıç kodundan faydalanmasını önleyecektir .
Arpit Singh

Yeterince adil. Eski okul için çok fazla.
Dave Wade-Stein
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.