Python中使用static、class、abstract方法
2019独角兽企业重金招聘Python工程师标准>>>
方法在Python中是如何工作的
方法就是一个函数,它作为一个类属性而存在,你可以用如下方式来声明、访问一个函数:
Python
1 2 3 4 5 6 7 8 |
>>> 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在告诉你,属性_get_size是类Pizza的一个未绑定方法。这是什么意思呢?很快我们就会知道答案:
Python
1 2 3 4 |
>>> 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) |
我们不能这么调用,因为它还没有绑定到Pizza类的任何实例上,它需要一个实例作为第一个参数传递进去(Python2必须是该类的实例,Python3中可以是任何东西),尝试一下:
Python
1 2 |
>>> Pizza.get_size(Pizza(42)) 42 |
太棒了,现在用一个实例作为它的的第一个参数来调用,整个世界都清静了,如果我说这种调用方式还不是最方便的,你也会这么认为的;没错,现在每次调用这个方法的时候我们都不得不引用这个类,如果不知道哪个类是我们的对象,长期看来这种方式是行不通的。
那么Python为我们做了什么呢,它绑定了所有来自类_Pizza的方法以及该类的任何一个实例的方法。也就意味着现在属性get_size是Pizza的一个实例对象的绑定方法,这个方法的第一个参数就是该实例本身。
Python
1 2 3 4 |
>>> Pizza(42).get_size <bound method Pizza.get_size of <__main__.Pizza object at 0x7f3138827910>> >>> Pizza(42).get_size() 42 |
和我们预期的一样,现在不再需要提供任何参数给_get_size,因为它已经是绑定的,它的self参数会自动地设置给Pizza实例,下面代码是最好的证明:
Python
1 2 3 |
>>> m = Pizza(42).get_size >>> m() 42 |
更有甚者,你都没必要使用持有Pizza对象的引用了,因为该方法已经绑定到了这个对象,所以这个方法对它自己来说是已经足够了。
也许,如果你想知道这个绑定的方法是绑定在哪个对象上,下面这种手段就能得知:
Python
1 2 3 4 5 6 7 |
>>> 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保持一致,但是模块更简洁:
Python
1 2 3 4 5 6 7 8 |
>>> 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> |
静态方法
静态方法是一类特殊的方法,有时你可能需要写一个属于这个类的方法,但是这些代码完全不会使用到实例对象本身,例如:
Python
1 2 3 4 5 6 7 |
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对象实例初始化一个绑定方法,绑定方法同样是对象,但是创建他们需要成本,而静态方法就可以避免这些。
Python
1 2 3 4 5 6 |
>>> 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作为模块的顶层函数,那么继承自Pizza的子类就没法改变pizza的mix_ingredients了如果不覆盖cook的话。
类方法
话虽如此,什么是类方法呢?类方法不是绑定到对象上,而是绑定在类上的方法。
Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
>>> 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 is Pizza().get_radius True >>> Pizza.get_radius() 42 |
无论你用哪种方式访问这个方法,它总是绑定到了这个类身上,它的第一个参数是这个类本身(记住:类也是对象)。
什么时候使用这种方法呢?类方法通常在以下两种场景是非常有用的:
- 工厂方法:它用于创建类的实例,例如一些预处理。如果使用@staticmethod代替,那我们不得不硬编码Pizza类名在函数中,这使得任何继承Pizza的类都不能使用我们这个工厂方法给它自己用。
Python
1 2 3 4 5 6 7 |
class Pizza(object): def __init__(self, ingredients): self.ingredients = ingredients @classmethod def from_fridge(cls, fridge): return cls(fridge.get_cheese() + fridge.get_vegetables()) |
- 调用静态类:如果你把一个静态方法拆分成多个静态方法,除非你使用类方法,否则你还是得硬编码类名。使用这种方式声明方法,Pizza类名明永远都不会在被直接引用,继承和方法覆盖都可以完美的工作。
Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
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) |
抽象方法
抽象方法是定义在基类中的一种方法,它没有提供任何实现,类似于Java中接口(Interface)里面的方法。
在Python中实现抽象方法最简单地方式是:
Python
1 2 3 |
class Pizza(object): def get_radius(self): raise NotImplementedError |
任何继承自_Pizza的类必须覆盖实现方法get_radius,否则会抛出异常。
这种抽象方法的实现有它的弊端,如果你写一个类继承Pizza,但是忘记实现get_radius,异常只有在你真正使用的时候才会抛出来。
Python
1 2 3 4 5 6 7 |
>>> 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模块,对象被初始化之后就可以抛出异常:
Python
1 2 3 4 5 6 7 8 |
import abc class BasePizza(object): __metaclass__ = abc.ABCMeta @abc.abstractmethod def get_radius(self): """Method that should do something.""" |
使用abc后,当你尝试初始化BasePizza或者任何子类的时候立马就会得到一个TypeError,而无需等到真正调用get_radius的时候才发现异常。
Python
1 2 3 4 |
>>> BasePizza() Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: Can't instantiate abstract class BasePizza with abstract methods get_radius |
混合静态方法、类方法、抽象方法
当你开始构建类和继承结构时,混合使用这些装饰器的时候到了,所以这里列出了一些技巧。
记住,声明一个抽象的方法,不会固定方法的原型,这就意味着虽然你必须实现它,但是我可以用任何参数列表来实现:
Python
1 2 3 4 5 6 7 8 9 10 11 12 13 |
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对象所定义的接口需求。同样我们也可以用一个类方法或静态方法来实现:
Python
1 2 3 4 5 6 7 8 9 10 11 12 13 |
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_ingredients方法并不需要知道返回结果是什么,结果是实现细节,不是契约条件。
因此,你不能强制抽象方法的实现是一个常规方法、或者是类方法还是静态方法,也没什么可争论的。从Python3开始(在Python2中不能如你期待的运行,见issue5867),在abstractmethod方法上面使用@staticmethod和@classmethod装饰器成为可能。
Python
1 2 3 4 5 6 7 8 9 10 11 12 |
import abc class BasePizza(object): __metaclass__ = abc.ABCMeta ingredient = ['cheese'] @classmethod @abc.abstractmethod def get_ingredients(cls): """Returns the ingredient list.""" return cls.ingredients |
别误会了,如果你认为它会强制子类作为一个类方法来实现get_ingredients那你就错了,它仅仅表示你实现的get_ingredients在BasePizza中是一个类方法。
可以在抽象方法中做代码的实现?没错,Python与Java接口中的方法相反,你可以在抽象方法编写实现代码通过super()来调用它。(译注:在Java8中,接口也提供的默认方法,允许在接口中写方法的实现)
Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
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() |
这个例子中,你构建的每个pizza都通过继承BasePizza的方式,你不得不覆盖get_ingredients方法,但是能够使用默认机制通过super()来获取ingredient列表。
转载于:https://my.oschina.net/u/3055388/blog/893601
Python中使用static、class、abstract方法相关推荐
- python文件处理seek()方法的参数是,在Python中操作文件之seek()方法的使用教程
在Python中操作文件之seek()方法的使用教程 seek()方法在偏移设定该文件的当前位置.参数是可选的,默认为0,这意味着绝对的文件定位,它的值如果是1,这意味着寻求相对于当前位置,2表示相对 ...
- Python中的静态类变量和方法
问: 如何在 Python 中创建静态类变量或方法? 答1: huntsbot.com聚合了超过10+全球外包任务平台的外包需求,寻找外包任务与机会变的简单与高效. 在类定义内但不在方法内声明的变量是 ...
- 在Python中连接字符串的首选方法是什么?
本文翻译自:Which is the preferred way to concatenate a string in Python? Since Python's string can't be c ...
- 关于python中requests模块导入问题-python中requests模块的使用方法
本文实例讲述了python中requests模块的使用方法.分享给大家供大家参考.具体分析如下: 在HTTP相关处理中使用python是不必要的麻烦,这包括urllib2模块以巨大的复杂性代价获取综合 ...
- python弹出警告框_selenium+webdriver+python 中警告框的处理方法
在自动化测试过程中,经常会遇到弹出警告框的情况,如图所示: 在 WebDriver 中处理 JavaScript 所生成的 alert.confirm 以及 prompt 是很简单的.具体做法是使用 ...
- 在Python中操作文件之truncate()方法的使用教程
在Python中操作文件之truncate()方法的使用教程 这篇文章主要介绍了在Python中操作文件之truncate()方法的使用教程,是Python入门学习中的基础知识,需要的朋友可以参考下 ...
- python中seek函数的用法_在Python中操作文件之seek()方法的使用教程
seek()方法在偏移设定该文件的当前位置.参数是可选的,默认为0,这意味着绝对的文件定位,它的值如果是1,这意味着寻求相对于当前位置,2表示相对于文件的末尾. 没有返回值.需要注意的是,如果该文件被 ...
- python中验证码连通域分割的方法详解
python中验证码连通域分割的方法详解 这篇文章主要给大家介绍了关于python中验证码连通域分割的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用python具有一定的参考学习价值,需 ...
- Python中常用的内置方法
Python中常用的内置方法: 比较大小.求和 如下所示: 代码块: #枚举:返回索引值和对应的value值for i,v in enumerate('westos'):print(i,v)#zips ...
最新文章
- 网站收录工具(php导航自动收录源码)_网站如何快速收录,网站不收录怎么办?...
- opencv resize_利用OpenCV 识别两张相似的图片
- MS SQL Server查询优化方法
- 线程堆栈大小 pthread_attr_setstacksize 的使用
- 学python推荐书籍-零基础学python推荐几本python学习的书籍
- oracle 加查询锁,oracle 锁查询 select加锁方法
- JavaScript中四种不同的属性检测方式比较
- USACO - 3.1.6 - Stamps
- 基础练习 数列排序 c语言
- java 初始化 静态变量,关于spring:Java初始化静态变量的时间顺序
- Exception in thread main java.lang.NoClassDefFoundError
- 李海自学java_JAVA程序设计 实验一
- python中while。。。。else的用法
- logback日志pattern_Springboot整合log4j2日志全解
- div+css+js实现深圳交互地图
- Terraform操作阿里云实例
- 自动驾驶芯片之——FPGA和ASIC介绍
- 【点云压缩】Lossless Coding of Point Cloud Geometry using a Deep Generative Model
- okgroup考试题:2019
- python猜单词游戏实验原理_猜单词游戏课程设计报告
热门文章
- vb.net 同时给多个属性赋值_一个float值赋值给double,结果是什么?进来重温一下类型提升吧...
- 4怎么打开项目_基于车位引导系统的捷顺室内导航项目怎么调试?
- 1235813找规律第100个数_每日一课:奥数知识点 —— 找简单数列的规律
- 21天 Jenkins打卡-Day1 环境准备
- mysql 分区 mycat 分片_Mysql系列六:(Mycat分片路由原理、Mycat常用分片规则及对应源码介绍)...
- java程序片的元素符号_UML 中的 基本画图元素及符号
- 蓝桥杯比赛常考算法_蓝桥杯比赛要求
- linux光驱安装内核,Linux的内核管理--之光盘恢复grub的方法
- 计算机分屏解决方案,整套解决方案:使窗口布局更高效,为Windows平台编译分屏工具...
- android日历信息获取错误,android – 从日历中获取事件