Muh*_*uri 192 python abstract-class abc
因为我习惯于在Python中使用旧的鸭子打字方式,所以我无法理解对ABC(抽象基类)的需求.的帮助下是如何使用它们好.
我试图在PEP中阅读理由,但它超越了我的脑海.如果我正在寻找一个可变序列容器,我会检查__setitem__
,或者更有可能尝试使用它(EAFP).我没有遇到数字模块的实际用途,它确实使用了ABC,但这是我必须理解的最接近的数字模块.
有人能解释一下我的理由吗?
Ben*_*son 209
@ Oddthinking的答案并没有错,但我认为它错过了Python在鸭子打字世界中拥有ABC 的真实,实用的原因.
抽象方法很简洁,但在我看来,它们并没有真正填充任何未被鸭子打字所涵盖的用例.抽象基类真正的强大之处在于他们允许您自定义的行为方式isinstance
和issubclass
.(__subclasshook__
基本上是Python __instancecheck__
和__subclasscheck__
钩子之上的友好API .)调整内置构造以处理自定义类型是Python的哲学的一部分.
Python的源代码堪称典范.以下是collections.Container
标准库中的定义(在撰写本文时):
class Container(metaclass=ABCMeta):
__slots__ = ()
@abstractmethod
def __contains__(self, x):
return False
@classmethod
def __subclasshook__(cls, C):
if cls is Container:
if any("__contains__" in B.__dict__ for B in C.__mro__):
return True
return NotImplemented
Run Code Online (Sandbox Code Playgroud)
这个定义__subclasshook__
说任何具有__contains__
属性的类都被认为是Container的子类,即使它没有直接对它进行子类化.所以我可以这样写:
class ContainAllTheThings(object):
def __contains__(self, item):
return True
>>> issubclass(ContainAllTheThings, collections.Container)
True
>>> isinstance(ContainAllTheThings(), collections.Container)
True
Run Code Online (Sandbox Code Playgroud)
换句话说,如果您实现了正确的接口,那么您就是一个子类!ABCs提供了一种在Python中定义接口的正式方法,同时坚持鸭子打字的精神.此外,这是以尊重开放原则的方式运作的.
Python的对象模型看起来很像一个更"传统"的OO系统(我的意思是Java*) - 我们得到了你的类,你的对象,你的方法 - 但是当你划伤表面时你会发现更丰富的东西更灵活.同样,Python的抽象基类概念对于Java开发人员来说可能是可识别的,但实际上它们的目的非常不同.
我有时会发现自己编写的多态函数可以作用于单个项目或项目集合,而且我发现isinstance(x, collections.Iterable)
它比hasattr(x, '__iter__')
同等try...except
块更具可读性.(如果您不了解Python,那三个中的哪一个会使代码的意图最清晰?)
也就是说,我发现我很少需要编写自己的ABC,而且我通常会发现需要通过重构来实现.如果我看到一个多态函数进行了大量的属性检查,或者许多函数进行了相同的属性检查,那气味就表明存在等待提取的ABC.
*没有讨论Java是否是一个"传统的"面向对象系统......
附录:即使一个抽象基类可以重写的行为isinstance
,并issubclass
,它仍然没有进入MRO虚拟子类.这对客户来说是一个潜在的陷阱:并非每个对象isinstance(x, MyABC) == True
都定义了方法MyABC
.
class MyABC(metaclass=abc.ABCMeta):
def abc_method(self):
pass
@classmethod
def __subclasshook__(cls, C):
return True
class C(object):
pass
# typical client code
c = C()
if isinstance(c, MyABC): # will be true
c.abc_method() # raises AttributeError
Run Code Online (Sandbox Code Playgroud)
不幸的是,这些"只是不做那个"陷阱(其中Python相对较少!):避免使用a __subclasshook__
和非抽象方法定义ABCs .此外,您应该使您的定义__subclasshook__
与ABC定义的抽象方法集合一致.
Odd*_*ing 144
ABC在客户端和实现的类之间提供更高级别的语义契约.
课程与其来电者之间存在合同.该班承诺做某些事情并具有某些特性.
合同有不同的级别.
在非常低的级别,合同可能包括方法的名称或其参数的数量.
在静态类型语言中,该合同实际上将由编译器强制执行.在Python中,您可以使用EAFP或内省来确认未知对象是否符合此预期合同.
但合同中也有更高层次的语义承诺.
例如,如果存在__str__()
方法,则期望返回该对象的字符串表示.它可以删除对象的所有内容,提交事务并从打印机中吐出一个空白页......但是对于它应该做什么有共同的理解,如Python手册中所述.
这是一个特殊情况,其中语义合同在手册中描述.该print()
方法应该怎么做?它应该将对象写入打印机还是屏幕线或其他东西?这取决于 - 您需要阅读评论以了解完整的合同.一个简单地检查print()
方法是否存在的客户端代码已经确认了合同的一部分 - 可以进行方法调用,但不能对调用的更高级别语义达成一致.
定义抽象基类(ABC)是一种在类实现者和调用者之间产生契约的方法.它不仅仅是方法名称列表,而是对这些方法应该做什么的共同理解.如果您继承了此ABC,则承诺遵循注释中描述的所有规则,包括print()
方法的语义.
Python的duck-typing在灵活性方面比静态类型有许多优点,但它并没有解决所有问题.ABCs提供了一种自由形式的Python与静态类型语言的束缚和规范之间的中间解决方案.
cer*_*ros 99
ABC的一个方便的特性是,如果你没有实现所有必要的方法(和属性),你会在实例化时遇到错误,而不是AttributeError
在你真正尝试使用缺少的方法时可能更晚.
from abc import ABCMeta, abstractmethod
# python2
class Base(object):
__metaclass__ = ABCMeta
@abstractmethod
def foo(self):
pass
@abstractmethod
def bar(self):
pass
# python3
class Base(object, metaclass=ABCMeta):
@abstractmethod
def foo(self):
pass
@abstractmethod
def bar(self):
pass
class Concrete(Base):
def foo(self):
pass
# We forget to declare `bar`
c = Concrete()
# TypeError: "Can't instantiate abstract class Concrete with abstract methods bar"
Run Code Online (Sandbox Code Playgroud)
示例来自https://dbader.org/blog/abstract-base-classes-in-python
编辑:包含python3语法,谢谢@PandasRocks
抽象方法确保您在父类中调用的方法必须出现在子类中.下面是noraml调用和使用抽象的方式.用python3编写的程序
正常的通话方式
class Parent:
def methodone(self):
raise NotImplemented()
def methodtwo(self):
raise NotImplementedError()
class Son(Parent):
def methodone(self):
return 'methodone() is called'
c = Son()
c.methodone()
Run Code Online (Sandbox Code Playgroud)
'methodone()被称为'
c.methodtwo()
Run Code Online (Sandbox Code Playgroud)
NotImplementedError
用抽象方法
from abc import ABCMeta, abstractmethod
class Parent(metaclass=ABCMeta):
@abstractmethod
def methodone(self):
raise NotImplementedError()
@abstractmethod
def methodtwo(self):
raise NotImplementedError()
class Son(Parent):
def methodone(self):
return 'methodone() is called'
c = Son()
Run Code Online (Sandbox Code Playgroud)
TypeError:无法使用抽象方法methodtwo实例化抽象类Son.
由于在子类中没有调用methodtwo,我们得到了错误.正确实施如下
from abc import ABCMeta, abstractmethod
class Parent(metaclass=ABCMeta):
@abstractmethod
def methodone(self):
raise NotImplementedError()
@abstractmethod
def methodtwo(self):
raise NotImplementedError()
class Son(Parent):
def methodone(self):
return 'methodone() is called'
def methodtwo(self):
return 'methodtwo() is called'
c = Son()
c.methodone()
Run Code Online (Sandbox Code Playgroud)
'methodone()被称为'
归档时间: |
|
查看次数: |
64750 次 |
最近记录: |