Python中方法的工作方式

方法是存储在类属性中的函数,你可以用下面这种方式声明和访问一个函数

    >>> class Pizza(object):
... def __init__(self, size):
... self.size = size
... def get_size(self):
... return self.size
...
>>> Pizza.get_size
<unbound method Pizza.get_size>

Python在这里说明了什么?Pizza类的属性get_size是unbound(未绑定的),这代表什么含义?我们调用一下就明白了:

    >>> Pizza.get_size()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unbound method get_size() must be called with Pizza instance as first argument (got nothing instead)

我们无法调用它(get_size),因为它没有绑定到Pizza的任何实例上,而且一个方法需要一个实例作为它的第一个参数(Python2中必须是类的实例,Python3没有这个强制要求),让我们试一下:

    >>> Pizza.get_size(Pizza(42))
42

我们使用一个实例作为这个方法的第一个参数来调用它,没有出现任何问题。但是如果我说这不是一个方便的调用方法的方式,你将会同意我的观点。我们每次调用方法都要涉及(这里我理解是引用)类

来看Python打算为我们做些什么,就是它从Pizza类中绑定所有的方法到这个类的任何实例上。意思就是Pizza实例化后get_size这个属性是一个绑定方法,方法的第一个参数会是实例对象自己

    >>> Pizza(42).get_size
<bound method Pizza.get_size of <__main__.Pizza object at 0x7f3138827910>>
>>> Pizza(42).get_size()
42

意料之中,我们不需要为get_size传任何参数,自从被绑定后,它的self参数会自动设置为Pizza实例,下面是一个更明显的例子:

    >>> m = Pizza(42).get_size
>>> m()
42

事实上是,你甚至不需要对Pizza引用,因为这个方法已经绑定到了这个对象

如果你想知道这个绑定方法绑定到了哪一个对象,这里有个快捷的方法:

    >>> m = Pizza(42).get_size
>>> m.__self__
<__main__.Pizza object at 0x7f3138827910>
>>> # You could guess, look at this:
...
>>> m == m.__self__.get_size
True

明显可以看出,我们仍然保持对我们对象的引用,而且如果需要我们可以找到它

在Python3中,类中的函数不再被认为是未绑定的方法(应该是作为函数存在),如果需要,会作为一个函数绑定到对象上,所以原理是一样的(和Python2),只是模型被简化了

    >>> class Pizza(object):
... def __init__(self, size):
... self.size = size
... def get_size(self):
... return self.size
...
>>> Pizza.get_size
<function Pizza.get_size at 0x7f307f984dd0>

静态方法

静态方法一种特殊方法,有时你想把代码归属到一个类中,但又不想和这个对象发生任何交互:

    class Pizza(object):
@staticmethod
def mix_ingredients(x, y):
return x + y def cook(self):
return self.mix_ingredients(self.cheese, self.vegetables)

上面这个例子,mix_ingredients完全可以写成一个非静态方法,但是这样会将self作为第一个参数传入。在这个例子里,装饰器@staticmethod 会实现几个功能:

Python不会为Pizza的实例对象实例化一个绑定方法,绑定方法也是对象,会产生开销,静态方法可以避免这类情况

        >>> Pizza().cook is Pizza().cook
False
>>> Pizza().mix_ingredients is Pizza.mix_ingredients
True
>>> Pizza().mix_ingredients is Pizza().mix_ingredients
True

简化了代码的可读性,看到@staticmethod我们就会知道这个方法不会依赖这个对象的状态(一国两制,高度自治)
允许在子类中重写mix_ingredients方法。如果我们在顶级模型中定义了mix_ingredients函数,继承自Pizza的类除了重写,否则无法改变mix_ingredients的功能

类方法

什么是类方法,类方法是方法不会被绑定到一个对象,而是被绑定到一个类中

     >>> class Pizza(object):
... radius = 42
... @classmethod
... def get_radius(cls):
... return cls.radius
...
>>>
>>> Pizza.get_radius
<bound method type.get_radius of <class '__main__.Pizza'>>
>>> Pizza().get_radius
<bound method type.get_radius of <class '__main__.Pizza'>>
>>> Pizza.get_radius == Pizza().get_radius
True
>>> Pizza.get_radius()
42

无论以何种方式访问这个方法,它都会被绑定到类中,它的第一个参数必须是类本身(记住类也是对象)

什么时候使用类方法,类方法在以下两种场合会有很好的效果:
    1、工厂方法,为类创建实例,例如某种程度的预处理。如果我们使用@staticmethod代替,我们必须要在代码中硬编码Pizza(写死Pizza),这样从Pizza继承的类就不能使用了

        class Pizza(object):
def __init__(self, ingredients):
self.ingredients = ingredients @classmethod
def from_fridge(cls, fridge):
return cls(fridge.get_cheese() + fridge.get_vegetables())

2、使用静态方法调用静态方法,如果你需要将一个静态方法拆分为多个,可以使用类方法来避免硬编码类名。使用这种方法来声明我们的方法Pizza的名字永远不会被直接引用,而且继承和重写方法都很方便

        class Pizza(object):
def __init__(self, radius, height):
self.radius = radius
self.height = height @staticmethod
def compute_area(radius):
return math.pi * (radius ** 2) @classmethod
def compute_volume(cls, height, radius):
return height * cls.compute_area(radius) def get_volume(self):
return self.compute_volume(self.height, self.radius)

抽象方法

抽象方法是定义在基类中的,可以是不提供任何功能代码的方法
在Python中简单的写抽象方法的方式是:

    class Pizza(object):
def get_radius(self):
raise NotImplementedError

继承自Pizza的类都必须要实现并重写get_redius,否则就会报错

这种方式的抽象方法有一个问题,如果你忘记实现了get_radius,只有在你调用这个方法的时候才会报错

    >>> Pizza()
<__main__.Pizza object at 0x7fb747353d90>
>>> Pizza().get_radius()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in get_radius
NotImplementedError

使用python的abc模块可以是这个异常被更早的触发

    import abc

    class BasePizza(object):
__metaclass__ = abc.ABCMeta @abc.abstractmethod
def get_radius(self):
"""Method that should do something."""

使用abc和它的特殊类,如果你尝试实例化BasePizza或者继承它,都会得到TypeError错误

    >>> BasePizza()
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: Can't instantiate abstract class BasePizza with abstract methods get_radius

备注:我使用Python3.6实现的代码  

    In [8]: import abc
...:
...: class BasePizza(abc.ABC):
...:
...: @abc.abstractmethod
...: def get_radius(self):
...: """:return"""
...: In [9]: BasePizza()
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-9-70b53ea21e68> in <module>()
----> 1 BasePizza() TypeError: Can't instantiate abstract class BasePizza with abstract methods get_radius

混合静态,类和抽象方法

当需要创建类和继承时,如果你需要混合这些方法装饰器,这里有一些小窍门建议给你
记住要将方法声明为抽象,不要冻结这个方法的原型。意思是它(声明的方法)必须要执行,但是它在执行的时候,参数不会有任何限制

        import abc

        class BasePizza(object):
__metaclass__ = abc.ABCMeta @abc.abstractmethod
def get_ingredients(self):
"""Returns the ingredient list.""" class Calzone(BasePizza):
def get_ingredients(self, with_egg=False):
egg = Egg() if with_egg else None
return self.ingredients + egg

这样是有效的,因为Calzone实现了我们为BasePizza定义的接口要求,这意味着我们也可以将它实现为一个类或者静态方法,例如:

        import abc

        class BasePizza(object):
__metaclass__ = abc.ABCMeta @abc.abstractmethod
def get_ingredients(self):
"""Returns the ingredient list.""" class DietPizza(BasePizza):
@staticmethod
def get_ingredients():
return None

这也是正确的,它实现了抽要BasePizza的要求,事实上是get_ingredioents方法不需要知道对象返回的结果,
因此,你不需要强制抽象方法实现成为常规方法、类或者静态方法。在python3中,可以将@staticmethod和@classmethod装饰器放在@abstractmethod上面

        import abc

        class BasePizza(object):
__metaclass__ = abc.ABCMeta ingredient = ['cheese'] @classmethod
@abc.abstractmethod
def get_ingredients(cls):
"""Returns the ingredient list."""
return cls.ingredients

和Java的接口相反,你可以在抽象方法中实现代码并通过super()调用它

        import abc

        class BasePizza(object):
__metaclass__ = abc.ABCMeta default_ingredients = ['cheese'] @classmethod
@abc.abstractmethod
def get_ingredients(cls):
"""Returns the ingredient list."""
return cls.default_ingredients class DietPizza(BasePizza):
def get_ingredients(self):
return ['egg'] + super(DietPizza, self).get_ingredients()

在上面的例子中,继承BasePizza来创建的每个Pizza都必须重写get_ingredients 方法,但是可以使用super()来获取default_ingredients

本文翻译自:https://julien.danjou.info/guide-python-static-class-abstract-methods/

关于如何在Python中使用静态、类或抽象方法的权威指南的更多相关文章

  1. 如何在Python中从零开始实现随机森林

    欢迎大家前往云+社区,获取更多腾讯海量技术实践干货哦~ 决策树可能会受到高度变异的影响,使得结果对所使用的特定测试数据而言变得脆弱. 根据您的测试数据样本构建多个模型(称为套袋)可以减少这种差异,但是 ...

  2. Python中如何获取类属性的列表

    这篇文章主要给大家介绍了在Python中如何获取类属性的列表,文中通过示例代码介绍的很详细,相信对大家的学习或者工作具有一定的参考借鉴价值,有需要的朋友可以参考借鉴,下面来一起看看吧. 前言 最近工作 ...

  3. 面试官问我:如何在 Python 中解析和修改 XML

    摘要:我们经常需要解析用不同语言编写的数据.Python提供了许多库来解析或拆分用其他语言编写的数据.在此 Python XML 解析器教程中,您将学习如何使用 Python 解析 XML. 本文分享 ...

  4. Python中的元类(metaclass)

    推荐+收藏:深刻理解Python中的元类(metaclass) 做一些笔记学习学习: 在大多数编程语言中,类就是用来描述如何生成一个对象的代码段,在Python中类也是一个对象,这个(类)对象自身拥有 ...

  5. [转]深刻理解Python中的元类(metaclass)以及元类实现单例模式

    使用元类 深刻理解Python中的元类(metaclass)以及元类实现单例模式 在看一些框架源代码的过程中碰到很多元类的实例,看起来很吃力很晦涩:在看python cookbook中关于元类创建单例 ...

  6. Python中的元类(译)

    add by zhj: 这是大stackoverflow上一位小白提出的问题,好吧,我承认我也是小白,元类这块我也是好多次想搞明白, 但终究因为太难懂而败下阵来.看了这篇文章明白了许多,再加下啄木鸟社 ...

  7. python中的嵌套类

    python中的嵌套类 在.NET和JAVA语言中看到过嵌套类的实现,作为外部类一个局部工具还是很有用的,今天在python也看到了很不错支持一下.动态语言中很好的嵌套类的实现,应该说嵌套类解决设计问 ...

  8. 如何在Python中快速画图——使用Jupyter notebook的魔法函数(magic function)matplotlib inline

    如何在Python中快速画图--使用Jupyter notebook的魔法函数(magic function)matplotlib inline 先展示一段相关的代码: #we test the ac ...

  9. 如何在Python中使用Linux epoll

    如何在Python中使用Linux epoll 内容 介绍 阻塞套接字编程示例 异步套接字和Linux epoll的好处 epoll的异步套接字编程示例 性能考量 源代码 介绍 从2.6版开始,Pyt ...

随机推荐

  1. 《Linux内核分析》课程第四周学习总结

    姓名:何伟钦 学号:20135223 ( *原创作品转载请注明出处*) ( 学习课程:<Linux内核分析>MOOC课程http://mooc.study.163.com/course/U ...

  2. VS系列软件中debug和release编译环境有什么区别

    当编译和执行一个工程时,可以在Debug和Release两种配置下执行. Debug模式用于调试程序,这是个受保护的运行环境,它将告诉你程序是否有泄露,在运行时也能对特定函数的结果进行检查.然而它生成 ...

  3. 实训四(cocos2dx sharesdk集成-1)

    随着项目的进行,好像陷入的技术优先的迷局,对那些可以实现的功能,代码的编写上越来越漫不经心,反而对没有掌握的技术求之若渴,不免显得有些好高骛远.在没有掌握之前,思考.学习.交流也是一种快乐与享受. 言 ...

  4. 重温httpsession①

    Session—HTTPSession 服务器创建的,Javaweb提供的 与HTTP协议无关是服务器端对象,保存在服务器端.用来会话跟踪. Cookie与服务器创建,与HTTP协议相关,保存在客户端 ...

  5. spring-boot随笔

    配置了spring-boot-starter-web的依赖后,会自动添加tomcat和spring mvc的依赖,那么spring boot 会对tomcat和spring mvc进行自动配置 < ...

  6. DHCP中续代理

    什么是DHCP中继代理? 答:DCHP中继代理(即DHCP Relay  Agent )用于转发来自于另一个没有DHCP服务器子网段中的客户端的DHCP请求,即当一台DCHP客户机发起请求后,此时DH ...

  7. Tomcat下bootstrap启动分析

    "C:\Program Files\Java\jdk1.7.0\bin\javaw.exe" -agentlib:jdwp=transport=dt_socket,suspend= ...

  8. 2013长春网赛1005 hdu 4763 Theme Section(kmp应用)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4763 题意:给出一个字符串,问能不能在该串的前中后部找到相同的子串,输出最长的字串的长度. 分析:km ...

  9. bower安装和使用

    bower的安装 1,首先在我的系统 安装 nodejs.因为我的系统是windows,还需要安装msysgit,注意图二中的选项   msysgit   Git setup 2,之后就可以用npm包 ...

  10. NESTED内部事务异常会回滚 外部事务不会回滚 ;内部事务没有异常,外部事务有异常 则整体事务都回滚

    NESTED内部事务异常会回滚 外部事务不会回滚 :内部事务没有异常,外部事务有异常 则整体事务都回滚