Python : Class

  Python 和 JavaScript一样即是面向过程语言,也是面向对象语言,动态语言。大多数面向对象语言里,Class是必不可少的。面向对象有三大特性:封装, 继承,多态。在Python中Class到底是怎样的呢?

1、Class组成

  先来看一个示例:

class Person(object):
    id=''
    name = ''
    age = 3
    
    # 等同于Java中的<init>,即构造器
    def __init__(self, id, name, age):
        print("init a Person instance")
        self.id = id
        self.name = name
        self.age = age

    def show(this):
        print(this)
    #   print(this.toString())

    #def toString(self):        
    #    return "id:{}, name:{}, age:{}".format(self.id, self.name, self.age)
    
    # 等同于Java中的toString
    def __str__(self):
    #   return self.toString()
        return "id:{}, name:{}, age:{}".format(self.id, self.name, self.age)
    

    # 等同于Java中的finalize方法,del 实例时调用
    def __del__(self):
        print("finally a Person instance")
        self.id = None
        self.name = None
        self.age = None
        self = None
    

 


  下面是以对比Java的方式,来说明Python中的类: 

  1)Class中,包括属性、方法,它们都是public的。在Python的Class中,是不存在private,protected等修饰符的。

  2)__init__是构造函数,调用构造器时,会自动调用__init__。它相当于Java中的<init>。在创建一个Python对象时,不需要像Java那样使用new。

  3)__del__是析构函数,当del instance时,会自动调用__del__。它相当于Java中的finalize

  4)需要获取对象的字符串表示时,会调用__str__。它就相当于Java中的toString。

  5)类的方法的第一个参数,都是self,相当于Java中的this。其实Java中的实例方法的第一个参数也是this,只是被隐藏了而已,如果你了解JVM 运行时的话,或者使用过 javassist或者asm等字节码工具的话,会知道的。上面的示例中的p1.show()运行时,可以理解为执行的是Person.show(p1)。 并不是说必须得写成self,你也可以写完this或者其他任何的满足变量命名的形式。但通常大家都约定俗称的写为self,保持编码风格的统一,有利于方便他人理解代码。

  6)__init__,__del__,__str__不是必须的。

  7)所有属性都要有初始值

 

2、Class getter,setter

  Java,JavaScript ES6都支持setter,getter。Python中也是支持的。

class Person(object):
    id=''
    name = ''
    age = 3
    
    # 等同于Java中的<init>,即构造器
    def __init__(self, id, name, age):
        print("init a Person instance")
        self.id = id
        self.name = name
        self.age = age

    def show(this):
        print(this)
    #    print(this.toString())

   #def toString(self):        
   #   return "id:{}, name:{}, age:{}".format(self.id, self.name, self.age)
    
    # 等同于Java中的toString
    def __str__(self):
   #   return self.toString()
        return "id:{}, name:{}, age:{}".format(self.id, self.name, self.age)
    

    # 等同于Java中的finalize方法,del 实例时调用
    def __del__(self):
        print("finally a Person instance")
        self.id = None
        self.name = None
        self.age = None
        self = None

    def __get__(self, name):
        print("invoke in __get__")
        print(self.__dict__)
        return 1111

    def __getattr__(self, name):
        print("invoke in __getattr__")
        return 1111

    def __getattribute__(self, name):
        print("invoke in __getattribute__")
        print(object.__getattribute__(self, name))
        print("after invoke in __getattribute__")
        return object.__getattribute__(self, name)

  对于旧式类,访问属性的顺序是: 

  1)直接访问属性

  2)访问__getattr__

  对于新式类,访问属性的顺序是:

  1)__getattribute

  2)直接访问属性

  3)__getattr__

  切记,如果写了__getattribute__,最后一句话必须是object.__getattribute(self,name) 否则就会出现:’XxxType’ object is not callable 。它的存在,更像是作为拦截器使用。

3、Class继承

  C++是多继承的,Java是单继承的,Python借鉴了C++的多继承方式。

  在继承结构下,访问属性,方法时,与Java中一样的,先自己的,自己没有时,才去从父类找。

  由于支持多继承,所以当一个属性、方法,在多个父类中都存在时,会访问到哪个呢?

  要解答这个问题,就得先知道搜索属性、方法的顺序。采用的搜索算法是深度优先搜索算法。

  也就是一个class A(S1,S2,S3):pass; 要调用一个属性时,会先从A里找,找不到再从S1,如果还找不到再从S2,依次类推。

 

  那么在多继承情况下,如果A,S1,S2,S3都重写了__getattr__方法,那会有什么区别呢?查找顺序大体不变的:

  1)  A的直接属性,找不到然后是A.__getattr__

  2)  S1的直接属性,找不到然后是S1.__getattr__

  3)  S2的直接属性,找不到然后是S2.__getattr__

  4)  S3的直接属性,找不到然后是S3.__getattr__

 

  对于构造函数__init__,在构造实例时,不会像Java那样,先去调用父类的__init__。

class Student(Person):
    def __init__(self, id, name, age,email):
        print("invoke in Student __init__")
        super(Student, self).__init__(id, name, age)
        self.email = email

 

  Java中有super(),Python中是否有呢?能否利用super呢?如果不能自动调用父类的构造器,那么在重写__init__又得给所有的属性都分配一下,这个好麻烦的,该怎么办呢?只能以编程的方式自己调用了。

   super(类,instance),这个函数的作用是找到指定的类的下一个父类的指定方法,将该方法在指定的实例上调用。

     例如上面的例子中,继承关系是这样的:Student > Person > object。self是Student对象,super(Student, self).__init__(id, name, age)该语句的意思就是:找到Student的下一个父类(即Person)的__init__方法,然后在self上调用该__init__方法。

 

  需要注意的是:

  1super函数的两个参数,不能有误。需要满足两个条件,第二个instance应该是第一个参数所代表的类的实例(或者是其子类的实例)。

  2)Super只能在新式类中使用。

 

 

4、运算符重写

  在.Net和Scala编程语言中,都是支持运算符重写的。如果没有接触过这些东西,可能会费解的。其实只要把运算符看着是方法就可以了。从这个角度来理解的话,Java也是支持运算符重写的,只不过呢,该特性并没有暴露给用户罢了。例如字符串拼接 + ,它其实是调用的StringBuilder.append方法,遍历集合的foreach,它其实调用的是iterator。既然提到了运算符重写,那么Python中也必然是支持的,并且它把这个特性暴露出来了。

 

  Python String 就重写了几个运算符:

+ 字符串拼接
* copy多份
== 比较字符串内容
> 字符串比较
< 字符串比较

  那么怎样实现运算符重写呢? 

  上面已经说明,将运算符看作是一个方法。那么重写运算符,就是就是重写方法了。

  下面列出了常见的运算符重载:

重载方法

说明

调用

__init__

构造器

对象建立,X=Class()

__del__

析构方法

对象回收,或者del X

__add__

运算符 +

X+Y, X+=Y

__iadd__

增强的 +

X+Y, X+=Y

__radd__

左侧+

Noninstance + Y

__or__

运算符 | (位 OR)

X|Y, X|=Y

__repr__,__str__

打印,转换

print(X), repr(X),str(X)

__getattr__

点号运算符

获取属性

__setattr__

赋值运算符

设置属性

__call__

函数调用

Y()

__len__

长度

len(X)

__cmp__,

__lt__,

__eq__

比较

X < Y

X==Y

__getitem__

索引运算符

X[name]

__setitem__

索引赋值

X[Y]=Z

__iter__

迭代

循环,迭代等

 

  使用__getitem__可以使得对象具备索引的方式来访问?

  测试如下:对上面的Student如下:

 

class Student(Person):
    def __init__(self, id, name, age,email):
        print("invoke in Student __init__")
        super(Student, self).__init__(id, name, age)
        self.email = email


    def __getitem__(self, name):
        return self.__dict__[name] + "_suffix"

  执行测试: 

  

import model
Student = model.Student
s = Student('0001', 'fjn', 20, 'fs1194361820@163.com')
s.show()
print(s["email"])

  发现结果是:fs1194361820@163.com_suffix 

 

5、模拟私有属性

  通过上面的学习,了解到Python的属性、方法都是public, 这点和JavaScript太 一样了。写Java时间久了,私有属性的好处,怎么可以浪费了呢。那么如何模拟出私有的?

有两种方式可以模拟的,都与JavaScript里的理念是类似的。

  方式一:使用对象时,直接加属性。

  方式二:利用对象的__dict__

 

6、Static Method

  在java中,会将实例方法与类方法区分,具体做法是在 class 方法上加上static修饰符。在python中是没有static的,那么如何实现static方法。

  前面也讲了,实例方法声明时,第一个参数是self。在python中,实例方法本质就是调用类方法的。  

p1=Person(‘a’,’b’,23)
Person.show(p1)

 

在方法前加上注解:@staticmethod

 

posted @ 2018-03-25 18:44  乐享程序员  阅读(1345)  评论(1编辑  收藏  举报