Python——详解__slots__,property和私有方法
本文始发于个人公众号:TechFlow,原创不易,求个关注
今天是Python专题的第11篇文章,我们来聊聊面向对象的一些进阶使用。
__slots__
如果你看过github当中一些大牛的代码,你会发现很多大牛经常在类的顶部加上__slots__关键字。如果你足够好奇,你可能会试着把这个关键字去掉再运行试试,你会发现去掉了之后什么也没有发生,一切依然运行得很好。
那么这个__slots__关键字究竟是做什么的呢?
它主要有两个功能,我们先来说第一个功能,就是限制用户的使用。
我们都知道Python是一门非常灵活的动态语言,很多在其他语言看起来完全不能容忍的事情在Python当中是可行的,这也是Python的设计理念,为了灵活和代码方便牺牲了效率。比如我们来看一个很简单的例子,由于Python是动态语言,所以类的成员甚至可以在类创建好了之后动态创建。这在静态语言当中是绝对不行的,我们只能调用类当中已有的属性,是不能或者很难添加新属性的。
比如这段代码:
class Exp:
def __init__(self):
self.a = None
self.b = None
if __name__ == "__main__":
exp = Exp()
exp.c = 3
print(exp.c)
我们定义了一个类叫做Exp,我们为它创建了a和b两个成员。但是我们在使用的时候,对c成员进行了赋值。要知道Exp类当中是没有成员c的,但是程序并不会报错,我们这么运行了之后它会将c添加进这个实例当中。
从一方面来看,这当然非常灵活,但是另一方面,这也留下了隐患。如果用户随意添加属性,可能会导致未知的问题,尤其在复杂的系统当中。所以有些时候为了严谨,我们会不希望用户做这种动态的修改。__slots__正是用来做这个的。
我们把这个关键字加上,再来运行结果就不一样了:
class Exp:
__slots__ = ['a', 'b']
def __init__(self):
self.a = None
self.b = None
if __name__ == "__main__":
exp = Exp()
exp.c = 3
print(exp.c)
如果你运行这段代码的话,你会得到一个报错,提示你Exp这个对象当中并没有c这个成员,也就是说我们只能运用__slots__这个关键字当中定义的成员,对于没有定义的成员不能随意创建,这样就限制了用户的使用。
虽然现在大部分人使用这个关键字都是报着这个目的,但是很遗憾的是,Python创建者的初衷其实并不是这个。这就谈到了__slots__关键字的第二个作用,就是节省内存。
如果了解过Python底层的实现原理,你会发现在Python当中为每一个实例都创建了一个字典,就是大名鼎鼎的__dict__字典。正是因为背后有一个字典,所以我们才可以创造出原本不存在的成员,也才支持这样动态的效果。我们可以人工地调用这个字典输出其中的内容,我们在加上__slots__关键字之前,输出的结果是这样的:
{'a': None, 'b': None}
但是加上了这个关键字之后,会得到一个报错,会告诉你Exp这个对象当中没有__dict__这个成员。原因很简单,因为使用dict来维护实例,会消耗大量的内存,额外存储了许多数据,而使用__slots__之后,Python内部将不再为实例创建一个字典来维护,而是会使用一个固定大小的数组,这样就节省了大量的空间。这个节省可不是一点半点,一般可以节省一半以上。也就是说牺牲了一定的灵活性,保证了性能。这一点也是__slots__这个关键字设计的初衷,但是现在很多人都用错了地方。
property
这个关键字在的文章当中曾经提到过,不过很不好意思的是,由于之前写文章的时候对它的了解还很有限,导致一些阐述存在一些谬误,所以这里再提一下这个关键字的运用作为弥补。
property可以帮我们绑定类当中一些属性的赋值和获取,也就是get和set。我们来看个例子:
class Exp:
def __init__(self, param):
self.param = param
@property
def param(self):
return self._param
@param.setter
def param(self, value):
self._param = value
这里的property注解会在我们调用.param的时候被执行,而param.setter会在我们为param这个属性赋值的时候被执行。所以你可能会奇怪,为什么我们在__init__方法当中初始化的时候用的是self.param = param而不是self._param = param,这是因为我们在执行前者的时候,Python一样会调用@param.setter这个注解,所以我们没有必要写成后者的形式。当然你也可以这么写,不过两者是完全等价的。
作为一个前Java程序员为类当中所有变量加上get和set方法几乎成了政治正确,所以我特别喜欢为类当中所有的属性加上property。但是这是不对的,加上property是非常耗时的,所以如非必要不要这么做,我们直接调用来进行赋值就好了,如果有必要,我们可以手动写上get和set方法。那么问题来了,既然不是为了规范,那么我们又为什么要用到property呢?
答案很简单,为了校验变量类型。
由于Python是动态语言,并且是隐式类型的,所以我们拿到变量的时候并不知道它究竟是什么类型,也不知道用户为给它赋值成什么类型。所以在一些情况下我们可能会希望做好限制,告诉用户只能将这个变量赋值成这个类型,否则就会报错。通过使用property,我们可以很方便地做到这点。
class Exp:
def __init__(self, param):
self.param = param
@property
def param(self):
return self._param
@param.setter
def param(self, value):
if not isinstance(value, str):
raise TypeError('Want a string')
self._param = value
除此之外,property还有一个用法是代替函数。举个例子:
class Exp:
def __init__(self, param):
self.param = param
@property
def param(self):
return self._param
@param.setter
def param(self, value):
if not isinstance(value, str):
raise TypeError('Want a string')
self._param = value
@property
def hello(self):
return 'hello ' + self.param
这样我们就可以通过.hello来代替调用一个函数,这样做其实是一种动态计算。hello的结果并没有被存储起来,之后当我们调用的时候才会执行,在一些场景下这样做会非常方便。
命名规范
最后我们来看下Python对象当中的命名规范,在之前的文章当中我们曾经说过,在Python当中没有对public和private的字段做区分,所有的字段都是public的,也就是说用户可以拿到类当中所有的字段和方法。为了规范,程序员们约定俗成,决定所有加了下划线的方法和变量都看成是private的,即使我们能调用,但是一般情况下我们也不这么干。
所以我们通常会写两个方法,一个是公开的接口,一个是内部的实现。我们调用的时候只调用公开的接口,公开的接口再去调用内部的实现。这在Python当中已经成了惯例,因为我们在调用内部方法的时候,往往还会传入一些内部的参数。
我们来看个简单的例子:
class ExpA:
def __init__(self):
pass
def public_func(self):
self._private_func()
def _private_func(self):
print('private ExpA')
if __name__ == "__main__":
exp = ExpA()
exp.public_func()
除了_之外我们经常还会看到一些两个下划线的变量和方法,那么它们之间又有什么区别呢?
为了回答这个问题,我们来看下面这个例子:
class ExpA:
def __init__(self):
pass
def public_func(self):
self.__private_func()
def __private_func(self):
print('private ExpA')
class ExpB(ExpA):
def __init__(self):
pass
def public_func(self):
self.__private_func()
def __private_func(self):
print('private ExpB')
if __name__ == "__main__":
exp = ExpB()
exp.public_func()
exp._ExpB__private_func()
exp._ExpA__private_func()
请问最后会输出什么?
我们试一下就知道,第一行输出的是private ExpB,这个没有问题。但是后面两个是什么?
后面两个就是__private_func,只不过系统自动将它重新命名了。重新命名的原因也很简单,因为Python禁止加了两个下划线的方法被子类覆盖。所以这两者的区别就在这里,它们都被认为是private的方法和属性,但是一个下划线允许子类覆盖,而两个下划线不行。所以如果我们在开发的时候希望我们某一个方法不会被子类覆盖,那么我们就需要加上两个下划线。
最后,我们来看一个小问题。在C++当中当我们的变量名和系统的关键字冲突的时候,我们往往会在变量前面加上一个_来作为区分。但是由于Python当中下划线被赋予了含义,所以我们不能这么干,那么当变量冲突的时候应该怎么办呢?答案也很简单,我们可以把下划线加在后面,比如lambda_。
总结
回顾一下今天的内容,主要是__slots__, property和下划线在类当中的使用。这三者都是Python面向对象当中经常用到的知识,了解它们不但可以让我们写出更规范的代码,也有助于帮助我们理解其他大牛的源码,因此是非常必要的。
今天的文章就是这些,如果觉得有所收获,请顺手点个关注或者转发吧,你们的举手之劳对我来说很重要。
Python——详解__slots__,property和私有方法的更多相关文章
- [转载]python 详解re模块
原文地址:python 详解re模块作者:Rocky 正则表达式的元字符有. ^ $ * ? { [ ] | ( ) .表示任意字符 []用来匹配一个指定的字符类别,所谓的字符类别就是你想匹配的一个字 ...
- ubuntu apache2配置详解(含虚拟主机配置方法)
ubuntu apache2配置详解(含虚拟主机配置方法) 在Windows下,Apache的配置文件通常只有一个,就是httpd.conf.但我在Ubuntu Linux上用apt-get inst ...
- 详解Java中的clone方法
详解Java中的clone方法 参考:http://blog.csdn.net/zhangjg_blog/article/details/18369201/ 所谓的复制对象,首先要分配一个和源对象同样 ...
- 分区工具parted的详解及常用分区使用方法【转】
来源:http://blog.51cto.com/zhangmingqian/1068779 分区工具parted的详解及常用分区使用方法 一. parted的用途及说明 概括使用说明 ...
- 33 Python 详解命令解析 - argparse--更加详细--转载
https://blog.csdn.net/lis_12/article/details/54618868 Python 详解命令行解析 - argparse Python 详解命令行解析 - arg ...
- Ubuntu下安装JDK图文教程详解 jdk-java6-30 .bin 的处理方法
Ubuntu下安装JDK图文教程详解 jdk-java6-30 .bin 的处理方法: https://blog.csdn.net/mingjie1212/article/details/485250 ...
- (转)linux命令详解之useradd命令使用方法
linux命令详解之useradd命令使用方法 原文:http://blog.csdn.net/u011537073/article/details/51987121 Linux 系统是一个多用户多任 ...
- 个人用户永久免费,可自动升级版Excel插件,使用VSTO开发,Excel催化剂安装过程详解及安装失败解决方法
因Excel催化剂用了VSTO的开发技术,并且为了最好的用户体验,用了Clickonce的布署方式(无需人工干预自动更新,让用户使用如浏览器访问网站一般,永远是最新的内容和功能).对安装过程有一定的难 ...
- 第7.27节 Python案例详解: @property装饰器定义属性访问方法getter、setter、deleter
上节详细介绍了利用@property装饰器定义属性的语法,本节通过具体案例来进一步说明. 一. 案例说明 本节的案例是定义Rectangle(长方形)类,为了说明问题,除构造函数外,其他方法都只 ...
随机推荐
- 安装RationalRose的问题解决
列出大问题:在这一步无法进行下一步,直接就只能退出. 翻译过来的意思是:IBM安装程序被完全下载之前就终止了,大概是这个意思. 然后我就直接进了IBM的官网看了一下产品支持,上面解释说是组件clear ...
- 动态规划-01背包-Tallest Billboard
2020-02-07 17:46:32 问题描述: 问题求解: 解法一:BF 看问题规模看似可以直接暴力解决. 如果直接去解肯定是会超时的,因为每次将原空间划分成A区域,B区域和剩余区域的时间复杂度为 ...
- Building Applications with Force.com and VisualForce(Dev401)(十七):Data Management: Data management Tools
ev401-018:Data Management: Data management ToolsModule Objectives1.List objects exposed in the impor ...
- PyTorch ImageNet 基于预训练六大常用图片分类模型的实战
微调 Torchvision 模型 在本教程中,我们将深入探讨如何对 torchvision 模型进行微调和特征提取,所有这些模型都已经预先在1000类的Imagenet数据集上训练完成.本教程将深入 ...
- mac 根目录下新建文件夹并赋予权限
在根目录中,你会发现你无法创建文件夹,即使使用命令也无法创建目录: 1.修改auto_master 编译 /etc/auto_master 文件,注释掉或者移除以 /home 开头的那一行,保存. 终 ...
- [Asp.Net Core] 关于 Blazor Server Side 的一些杂项, 感想
在2016年, 本人就开始了一个内部项目, 其特点就是用C#构建DOM树, 然后把DOM同步到浏览器中显示. 并且在一些小工程中使用. 3年下来, 效果很不错, 但因为是使用C#来构建控件树, 在没有 ...
- 20175314 《Java程序设计》第十周学习总结
20175314 <Java程序设计>第十周学习总结 教材学习内容总结 进程与线程:一个进程的进行期间可以产生多个线程. Java内置对多线程的支持,计算机只能执行线程中的一个,Java虚 ...
- Html,css构建一个对话框,练习201911281028
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/stri ...
- 12.1 flask基础之简单实用
一.Flask介绍(轻量级的框架,非常快速的就能把程序搭建起来) Flask是一个基于Python开发并且依赖jinja2模板和Werkzeug WSGI服务的一个微型框架,对于Werkzeug本质是 ...
- [转发]对ThreadPoolExecutor初识
知识点提前预知: Java.util.concurrent.ThreadPoolExecutor类是ExecutorSerivce接口的具体实现.ThreadPoolExecutor使用线程池中的一个 ...