python类:class创建、数据方法属性及访问控制详解


Posted in Python onJuly 25, 2016

在Python中,可以通过class关键字定义自己的类,然后通过自定义的类对象类创建实例对象。

python中创建类

创建一个Student的类,并且实现了这个类的初始化函数”__init__”:

class Student(object):
    count = 0
    books = []
    def __init__(self, name):
        self.name = name

接下来就通过上面的Student类来看看Python中类的相关内容。

类构造和初始化

”__init__”和”__new__”的联系和差别

下面先通过一段代码看看这两个方法的调用顺序:

class A(object):
  def __init__(self,*args, **kwargs):
    print "init %s" %self.__class__
  def __new__(cls,*args, **kwargs):
    print "new %s" %cls
    return object.__new__(cls, *args, **kwargs)
 
a = A()

从代码的输出可以看到,当通过类实例化一个对象的时候,”__new__”方法首先被调用,然后是”__init__”方法。

python类:class创建、数据方法属性及访问控制详解

一般来说,”__init__”和”__new__”函数都会有下面的形式:

def__init__(self,*args,**kwargs):
  # func_suite
 
def__new__(cls,*args,**kwargs):
  # func_suite
returnobj

对于”__new__”和”__init__”可以概括为:

•“__new__”方法在Python中是真正的构造方法(创建并返回实例),通过这个方法可以产生一个”cls”对应的实例对象,所以说”__new__”方法一定要有返回

•对于”__init__”方法,是一个初始化的方法,”self”代表由类产生出来的实例对象,”__init__”将对这个对象进行相应的初始化操作

__new__特性

“__new__”是在新式类中新出现的方法,它有以下行为特性:

•“__new__” 方法是在类实例化对象时第一个调用的方法,将返回实例对象

•“__new__” 方法始终都是类的静态方法(即第一个参数为cls),即使没有被加上静态方法装饰器

•第一个参数cls是当前正在实例化的类,如果要得到当前类的实例,应当在当前类中的 “__new__” 方法语句中调用当前类的父类的” __new__” 方法

对于上面的第三点,如果当前类是直接继承自 object,那当前类的 “__new__” 方法返回的对象应该为:

 

def __new__(cls, *args, **kwargs):
  # func_suite
return object.__new__(cls, *args, **kwargs)

重写__new__

如果(新式)类中没有重写”__new__”方法,Python默认是调用该类的直接父类的”__new__”方法来构造该类的实例,如果该类的父类也没有重写”__new__”,那么将一直按照同样的规则追溯至object的”__new__”方法,因为object是所有新式类的基类。

而如果新式类中重写了”__new__”方法,那么可以选择任意一个其他的新式类(必须是新式类,只有新式类有”__new__”,因为所有新式类都是从object派生)的”__new__”方法来创建实例,包括这个新式类的所有前代类和后代类,只要它们不会造成递归死循环。

看一段例子代码:

classFoo(object):
  def__new__(cls,*args,**kwargs):
    obj=object.__new__(cls,*args,**kwargs) 
    # 这里的object.__new__(cls, *args, **kwargs)  等价于
    # super(Foo, cls).__new__(cls, *args, **kwargs) 
    # object.__new__(Foo, *args, **kwargs)
    # Bar.__new__(cls, *args, **kwargs)
    # Student.__new__(cls, *args, **kwargs),即使Student跟Foo没有关系,也是允许的,因为Student是从object派生的新式类
 
    # 在任何新式类,不能调用自身的“__new__”来创建实例,因为这会造成死循环
    # 所以要避免return Foo.__new__(cls, *args, **kwargs)或return cls.__new__(cls, *args, **kwargs)
    print"Call __new__ for %s"%obj.__class__
    returnobj  
 
classBar(Foo):
  def__new__(cls,*args,**kwargs):
    obj=object.__new__(cls,*args,**kwargs) 
    print"Call __new__ for %s"%obj.__class__
    returnobj 
 
classStudent(object):
  # Student没有“__new__”方法,那么会自动调用其父类的“__new__”方法来创建实例,即会自动调用 object.__new__(cls)
  pass
 
classCar(object):
  def__new__(cls,*args,**kwargs):
    # 可以选择用Bar来创建实例
    obj=object.__new__(Bar,*args,**kwargs) 
    print"Call __new__ for %s"%obj.__class__
    returnobj
 
foo=Foo()
bar=Bar()
car=Car()

代码的输出为:

python类:class创建、数据方法属性及访问控制详解

__init__的调用

“__new__”决定是否要使用该类的”__init__”方法,因为”__new__” 可以调用其他类的构造方法或者直接返回别的类创建的对象来作为本类的实例。

通常来说,新式类开始实例化时,”__new__”方法会返回cls(cls指代当前类)的实例,然后调用该类的”__init__”方法作为初始化方法,该方法接收这个实例(即self)作为自己的第一个参数,然后依次传入”__new__”方法中接收的位置参数和命名参数。

但是,如果”__new__”没有返回cls(即当前类)的实例,那么当前类的”__init__”方法是不会被调用的。

例子:

class A(object):
  def __init__(self, *args, **kwargs):
    print "Call __init__ from %s" %self.__class__
 
  def __new__(cls, *args, **kwargs):
    obj = object.__new__(cls, *args, **kwargs)
    print "Call __new__ for %s" %obj.__class__
    return obj  
 
class B(object):
  def __init__(self, *args, **kwargs):
    print "Call __init__ from %s" %self.__class__
 
  def __new__(cls, *args, **kwargs):
    obj = object.__new__(A, *args, **kwargs)
    print "Call __new__ for %s" %obj.__class__
    return obj   
 
b = B()
print type(b)

代码中,在B的”__new__”方法中,通过”obj = object.__new__(A, *args, **kwargs)”创建了一个A的实例,在这种情况下,B的”__init__”函数就不会被调用到。

python类:class创建、数据方法属性及访问控制详解

派生不可变类型

关于”__new__”方法还有一个重要的用途就是用来派生不可变类型。

例如,Python中float是不可变类型,如果想要从float中派生一个子类,就要实现”__new__”方法:

classRound2Float(float):
  def__new__(cls,num):
    num=round(num,2)
    #return super(Round2Float, cls).__new__(cls, num)
    returnfloat.__new__(Round2Float,num)
 
f=Round2Float(4.14159)
printf

代码中从float派生出了一个Round2Float类,该类的实例就是保留小数点后两位的浮点数。

通过内建函数dir(),或者访问类的字典属性__dict__,这两种方式都可以查看类有哪些属性。

数据属性

类数据属性和实例数据属性

在上面的Student类中,”count”"books”"name”和”age”都被称为类的数据属性,但是它们又分为类数据属性和实例数据属性。

类变量紧接在类名后面定义,相当于java和c++的static变量

实例变量在__init__里定义,相当于java和c++的普通变量

>>> class test:
         count = 0;类变量
        def __init__(self, c):
              self.count = c; 实例变量
             self.__class__.count = self.__class__.count + 1;

>>> a = test(3)
>>> a.count
3
>>> test.count
1

对于类数据属性和实例数据属性,可以总结为:

1.类数据属性属于类本身,可以通过类名进行访问/修改

2.类数据属性也可以被类的所有实例访问/修改

3.在类定义之后,可以通过类名动态添加类数据属性,新增的类属性也被类和所有实例共有

4.实例数据属性只能通过实例访问

5.在实例生成后,还可以动态添加实例数据属性,但是这些实例数据属性只属于该实例

特殊的类属性

对于所有的类,都有一组特殊的属性:

类属性 含义
__name__ 类的名字(字符串)
__doc__ 类的文档字符串
__bases__ 类的所有父类组成的元组
__dict__ 类的属性组成的字典
__module__ 类所属的模块
__class__ 类对象的类型

 Note:文档字符串对于类,函数/方法,以及模块来说是唯一的,也就是说__doc__属性是不能从父类中继承来的。

属性隐藏

从上面的介绍了解到,类数据属性属于类本身,被所有该类的实例共享;并且,通过实例可以去访问/修改类属性。

但是,在通过实例中访问类属性的时候一定要谨慎,因为可能出现属性”隐藏”的情况。

继续使用上面的Student类,来看看属性隐藏:

wilber = Student("Wilber", 28)
 
print "Student.count is wilber.count: ", Student.count is wilber.count
wilber.count = 1   
print "Student.count is wilber.count: ", Student.count is wilber.count
print Student.__dict__
print wilber.__dict__
del wilber.count
print "Student.count is wilber.count: ", Student.count is wilber.count
 
print
 
wilber.count += 3   
print "Student.count is wilber.count: ", Student.count is wilber.count
print Student.__dict__
print wilber.__dict__
 
del wilber.count

print
 
print "Student.books is wilber.books: ", Student.books is wilber.books
wilber.books = ["C#", "Python"]
print "Student.books is wilber.books: ", Student.books is wilber.books
print Student.__dict__
print wilber.__dict__
del wilber.books
print "Student.books is wilber.books: ", Student.books is wilber.books
 
print
 
wilber.books.append("CSS")
print "Student.books is wilber.books: ", Student.books is wilber.books
print Student.__dict__
print wilber.__dict__

代码的输出为:

python类:class创建、数据方法属性及访问控制详解

分析一下上面代码的输出:

•对于不可变类型的类属性Student.count,可以通过实例wilber进行访问,并且”Student.count is wilber.count”

•当通过实例赋值/修改count属性的时候,都将为实例wilber新建一个count实例属性,这时,”Student.count is not wilber.count”

•当通过”del wilber.count”语句删除实例的count属性后,再次成为”Student.count is wilber.count”

•同样对于可变类型的类属性Student.books,可以通过实例wilber进行访问,并且”Student. books is wilber. books”

•当通过实例赋值books属性的时候,都将为实例wilber新建一个books实例属性,这时,”Student. Books is not wilber. books”

•当通过”del wilber. books”语句删除实例的books属性后,再次成为”Student. books is wilber. books”

•当通过实例修改books属性的时候,将修改wilber.books指向的内存地址(即Student.books),此时,”Student. Books is wilber. books”

Note: 虽然通过实例可以访问类属性,但是,不建议这么做,最好还是通过类名来访问类属性,从而避免属性隐藏带来的不必要麻烦。

方法

在一个类中,可能出现三种方法,实例方法、静态方法和类方法,下面来看看三种方法的不同。

实例方法

实例方法的第一个参数必须是”self”,”self”类似于C++中的”this”。

实例方法只能通过类实例进行调用,这时候”self”就代表这个类实例本身。通过”self”可以直接访问实例的属性。

类方法

类方法以cls作为第一个参数,cls表示类本身,定义时使用@classmethod装饰器。通过cls可以访问类的相关属性。

class Student(object):
    '''
    this is a Student class
    '''
    count = 0
    books = []
    def __init__(self, name, age):
        self.name = name
        self.age = age
 
    @classmethod
    def printClassInfo(cls):
        print cls.__name__
        print dir(cls)
    pass
 
Student.printClassInfo()   
wilber = Student("Wilber", 28)
wilber.printClassInfo()

代码的输出为,从这段代码可以看到,类方法可以通过类名访问,也可以通过实例访问。

python类:class创建、数据方法属性及访问控制详解

静态方法

与实例方法和类方法不同,静态方法没有参数限制,既不需要实例参数,也不需要类参数,定义的时候使用@staticmethod装饰器。

同类方法一样,静态法可以通过类名访问,也可以通过实例访问。

class Student(object):
  '''
  this is a Student class
  '''
  count = 0
  books = []
  def __init__(self, name, age):
    self.name = name
    self.age = age
 
  @staticmethod
  def printClassAttr():
    print Student.count
    print Student.books
  pass
 
Student.printClassAttr()  
wilber = Student("Wilber", 28)
wilber.printClassAttr()

 

 

这三种方法的主要区别在于参数,实例方法被绑定到一个实例,只能通过实例进行调用;但是对于静态方法和类方法,可以通过类名和实例两种方式进行调用。

访问控制

Python中没有访问控制的关键字,例如private、protected等等。

但是,在Python编码中,有一些约定来进行访问控制。

“_”和” __”的使用 更多的是一种规范/约定,不没有真正达到限制的目的:

“_”:以单下划线开头的表示的是protected类型的变量,即只能允许其本身与子类进行访问;同时表示弱内部变量标示,如,当使用”from moduleNmae import *”时,不会将以一个下划线开头的对象引入。
“__”:双下划线的表示的是私有类型的变量。只能是允许这个类本身进行访问了,连子类也不可以,这类属性在运行时属性名会加上单下划线和类名。

单下划线”_”

在Python中,通过单下划线”_”来实现模块级别的私有化,一般约定以单下划线”_”开头的变量、函数为模块私有的,也就是说”from moduleName import *”将不会引入以单下划线”_”开头的变量、函数。

现在有一个模块lib.py,内容用如下,模块中一个变量名和一个函数名分别以”_”开头:

numA = 10
_numA = 100
 
def printNum():
  print "numA is:", numA
  print "_numA is:", _numA
 
def _printNum():
  print "numA is:", numA
print "_numA is:", _numA

当通过下面代码引入lib.py这个模块后,所有的以”_”开头的变量和函数都没有被引入,如果访问将会抛出异常:

from lib import *
print numA
printNum()
 
print _numA
#print _printNum()

python类:class创建、数据方法属性及访问控制详解

双下划线”__”

对于Python中的类属性,可以通过双下划线”__”来实现一定程度的私有化,因为双下划线开头的属性在运行时会被”混淆”(mangling)。

在Student类中,加入了一个”__address”属性:

 

class Student(object):
  def __init__(self, name, age):
    self.name = name
    self.age = age
    self.__address = "Shanghai"
 
  pass
 
wilber = Student("Wilber", 28)
print wilber.__address

 当通过实例wilber访问这个属性的时候,就会得到一个异常,提示属性”__address”不存在。

python类:class创建、数据方法属性及访问控制详解

其实,通过内建函数dir()就可以看到其中的一些原由,”__address”属性在运行时,属性名被改为了”_Student__address”(属性名前增加了单下划线和类名)

>>> wilber = Student("Wilber", 28)
>>> dir(wilber)
['_Student__address', '__class__', '__delattr__', '__dict__', '__doc__', '__form
at__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__r
educe__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '
__subclasshook__', '__weakref__', 'age', 'name']
>>>

以说,即使是双下划线,也没有实现属性的私有化,因为通过下面的方式还是可以直接访问”__address”属性:

>>> wilber = Student("Wilber", 28)
>>> print wilber._Student__address
Shanghai
>>>

双下划线的另一个重要的目地

避免子类对父类同名属性的冲突。

看下面一个例子:

class A(object):
  def __init__(self):
    self.__private()
    self.public()
 
  def __private(self):
    print 'A.__private()'
 
  def public(self):
    print 'A.public()'
 
class B(A):
  def __private(self):
    print 'B.__private()'
 
  def public(self):
    print 'B.public()'
 
b = B()

当实例化B的时候,由于没有定义__init__函数,将调用父类的__init__,但是由于双下划线的”混淆”效果,”self.__private()”将变成 “self._A__private()”。

看到这里,就清楚为什么会有如下输出了:

A.__private()

B.public()

以上这篇python类:class创建、数据方法属性及访问控制详解就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持三水点靠木。

Python 相关文章推荐
python使用循环实现批量创建文件夹示例
Mar 25 Python
python多线程操作实例
Nov 21 Python
Python中几种操作字符串的方法的介绍
Apr 09 Python
Python按行读取文件的简单实现方法
Jun 22 Python
使用Python来开发微信功能
Jun 13 Python
PySide和PyQt加载ui文件的两种方法
Feb 27 Python
Python利用lxml模块爬取豆瓣读书排行榜的方法与分析
Apr 15 Python
django框架实现模板中获取request 的各种信息示例
Jul 01 Python
Python3之字节串bytes与字节数组bytearray的使用详解
Aug 27 Python
python文件读写代码实例
Oct 21 Python
python GUI库图形界面开发之PyQt5信号与槽基础使用方法与实例
Mar 06 Python
Python 求向量的余弦值操作
Mar 04 Python
python实现汉诺塔方法汇总
Jul 25 #Python
python魔法方法-属性访问控制详解
Jul 25 #Python
python魔法方法-属性转换和类的表示详解
Jul 22 #Python
wxpython中自定义事件的实现与使用方法分析
Jul 21 #Python
wxpython中Textctrl回车事件无效的解决方法
Jul 21 #Python
Python实现Sqlite将字段当做索引进行查询的方法
Jul 21 #Python
python装饰器初探(推荐)
Jul 21 #Python
You might like
关于我转生变成史莱姆这档事:第二季PV上线,萌王2021年回归
2020/05/06 日漫
php 多关键字 高亮显示实现代码
2012/04/23 PHP
php cURL和Rolling cURL并发方式比较
2013/10/30 PHP
php通过rmdir删除目录的简单用法
2015/03/18 PHP
Codeigniter发送邮件的方法
2015/03/19 PHP
PHP自定义多进制的方法
2016/11/03 PHP
getElementById在任意一款浏览器中都可以用吗的疑问回复
2007/05/13 Javascript
模拟一个类似百度google的模糊搜索下拉列表
2014/04/15 Javascript
javascript连续赋值问题
2015/07/08 Javascript
js当前页面登录注册框,固定div,底层阴影的实例代码
2016/10/04 Javascript
功能强大的jquery.validate表单验证插件
2016/11/07 Javascript
微信小程序 后台https域名绑定和免费的https证书申请详解
2016/11/10 Javascript
微信小程序网络封装(简单高效)
2018/08/06 Javascript
vue中Element-ui 输入银行账号每四位加一个空格的实现代码
2018/09/14 Javascript
微信小程序五子棋游戏的悔棋实现方法【附demo源码下载】
2019/02/20 Javascript
Nodejs核心模块之net和http的使用详解
2019/04/02 NodeJs
vue实现随机验证码功能的实例代码
2019/04/30 Javascript
微信小程序swiper使用网络图片不显示问题解决
2019/12/13 Javascript
vue 出现data-v-xxx的原因及解决
2020/08/04 Javascript
js实现删除json中指定的元素
2020/09/22 Javascript
python数据库操作常用功能使用详解(创建表/插入数据/获取数据)
2013/12/06 Python
python版本的读写锁操作方法
2016/04/25 Python
Python字符编码与函数的基本使用方法
2017/09/30 Python
python模块导入的细节详解
2018/12/10 Python
Python编程在flask中模拟进行Restful的CRUD操作
2018/12/28 Python
Python占用的内存优化教程
2019/07/28 Python
python调用有道智云API实现文件批量翻译
2020/10/10 Python
CSS中越界问题的经典解决方案【推荐】
2016/04/19 HTML / CSS
3D空间设计学生找工作的自我评价
2013/10/28 职场文书
商务英语本科生的自我评价分享
2013/11/15 职场文书
环境工程专业个人求职信
2013/12/05 职场文书
消防安全责任书范本
2014/04/15 职场文书
解除劳动合同证明书
2014/09/26 职场文书
入党积极分子自我批评思想汇报
2014/10/10 职场文书
2014年村党支部工作总结
2014/12/04 职场文书
年度考核个人总结
2015/03/06 职场文书