Python 类小结

Python 类小结


类的定义

class Test:
    classattr1 = '类属性1'
    __classattr2 = '类属性2'

    def __init__(self):
        print '实例初始化'
        self.attr1 = '实例属性1'
        self.__attr2 = '实例属性2'

    def test1(self):
        print '实例方法1'
        self.attr3 = '实例属性3'

    def __test2(self):
        print '实例方法2'

    @classmethod
    def test3(cls):
        print '类方法1'

    @classmethod
    def __test4(cls):
        print '类方法2'

以上为一个简单的类的定义示例:

  • 类属性:类属性类似于 C++ 中的类 static 变量,是和类绑定的,所有该类的实例共享
  • 实例属性:实例属性是类实例独有的,其它类不能访问,需要有一个 self 参数
  • 类方法:类方法类似于 C++ 中的类 static 方法,是和类绑定的,需要使用 @classmethod 进行修饰,需要有一个 cls 参数
  • 实例方法:实例方法是类实例独有的,必须有一个参数 self,即该方法对应的类实例
  • __init__ 方法:__init__ 方法是类的特殊方法,在实例初始化时调用
  • 私有属性和方法:如果一个类中的属性或方法不想让外部进行访问,在属性或变量前添加双下划线

下面我们通过实验对类属性,实例属性,类方法和实例方法进行探究

类成员和实例成员

>>> dir(Test)
['_Test__classattr2', '_Test__test2', '_Test__test4', '__doc__', '__init__', '__module__', 'classattr1', 'test1', 'test3']
>>> print Test.__classattr2

Traceback (most recent call last):
  File "<pyshell#38>", line 1, in <module>
    print Test.__classattr2
AttributeError: class Test has no attribute '__classattr2'
>>> print Test._Test__classattr2
类属性2
>>>

首先,我们查看上面定义的 Test 内容,一个定义好的类,其内容包含了所有的类属性、类方法和实例方法,但是不会包含实例属性。

如果一个类包含了私有属性或私有方法,在类中实际上是在该属性和方法前添加了“_类名”,使用转化后的属性或方法名是可以对其进行访问的

>>> t1 = Test()
实例初始化
>>> dir(t1)
['_Test__attr2', '_Test__classattr2', '_Test__test2', '_Test__test4', '__doc__', '__init__', '__module__', 'attr1', 'classattr1', 'test1', 'test3']
>>> t1.test1()
实例方法1
>>> dir(t1)
['_Test__attr2', '_Test__classattr2', '_Test__test2', '_Test__test4', '__doc__', '__init__', '__module__', 'attr1', 'attr3', 'classattr1', 'test1', 'test3']
>>> dir(Test)
['_Test__classattr2', '_Test__test2', '_Test__test4', '__doc__', '__init__', '__module__', 'classattr1', 'test1', 'test3']
>>> 

然后,我们创建了一个 Test 的实例 t1,其调用了 __init__ 函数进行初始化,该函数中为实例绑定了两个实例属性 attr1 和 __attr2

对 t1 调用了 test1 方法,为 t1 绑定了一个实例属性 attr3

查看 Test 类的内容,实际没有发生改变

综上,实例属性是后绑定的,可以在需要时进行绑定,不需要预定义。一个实例绑定新的实例方法,并不会改变类中原有的内容

>>> t1.attr4 = '实例属性4'
>>> dir(t1)
['_Test__attr2', '_Test__classattr2', '_Test__test2', '_Test__test4', '__doc__', '__init__', '__module__', 'attr1', 'attr3', 'attr4', 'classattr1', 'test1', 'test3']
>>> t1._Test__attr5 = '实例属性5'
>>> dir(t1)
['_Test__attr2', '_Test__attr5', '_Test__classattr2', '_Test__test2', '_Test__test4', '__doc__', '__init__', '__module__', 'attr1', 'attr3', 'attr4', 'classattr1', 'test1', 'test3']

最后,我们在类的外部对 Test 的实例绑定了一个普通的实例属性 attr4 和一个 __attr5,可见,实例属性不一定要在类的方法中去绑定,可以在类的外部进行绑定

实例方法的调用

>>> t1.test1()
实例方法1
>>> Test.test1(t1)
实例方法1
>>> Test.test1()

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

对实例方法的调用有两种,第一种使用“实例.方法名(其它参数)”进行调用,第二种使用“类.方法名(实例, 其它参数)”进行调用,第一种方法实际是第二种方法的简写

第一种如上例中的 t1.test1()

第二种如上例中的 Test.test1(t1)

实例属性访问

>>> t2 = Test()
实例初始化
>>> print t1.attr1
实例属性1
>>> print t2.attr1
实例属性1
>>> t2.attr1 = 't2.attr1'
>>> print t1.attr1
实例属性1
>>> print t2.attr1
t2.attr1
>>> print Test.attr1

Traceback (most recent call last):
  File "<pyshell#83>", line 1, in <module>
    print Test.attr1
AttributeError: class Test has no attribute 'attr1' 

前面已经探究过,类中是没有实例属性的,因此实例属性必须使用类实例去访问,而不能使用类去访问

由上例中可以看到,实例方法是和实例绑定的,当 t2 对 attr1 的值进行修改,t1 的 attr1 的值并未发生变化

类方法访问

>>> Test.test3()
类方法1
>>> t1.test3()
类方法1
>>> t2.test3()
类方法1
>>>

类方法可以使用“类名.类方法()”进行访问,也可以使用“实例名.类方法()进行访问”

类属性访问

>>> print t1.classattr1
类属性1
>>> print t2.classattr1
类属性1
>>> print Test.classattr1
类属性1
>>> t2.classattr1 = 'class attr1'
>>> print t1.classattr1
类属性1
>>> print t2.classattr1
class attr1
>>> print Test.classattr1
类属性1
>>> Test.classattr1 = 'new attr1'
>>> print t1.classattr1
new attr1
>>> print t2.classattr1
class attr1
>>> print Test.classattr1
new attr1
>>> 

类属性可以使用实例和类进行访问

类属性的修改必须使用“类名.类属性名”的方式去修改,上面 t2 修改的 classattr1 的值,实际上是对 t2 绑定了一个新的实例变量 classattr1,因此该修改并不会对 Test 和 test1 中的 classattr1 产生影响

我们使用 Test.classattr1 = ‘new attr1’ 对类属性进行修改后,可以看到 Test 和 t1 的classattr1 都产生了相应变化,但是由于 t2 的 classattr1 变成了实例属性,t2 的 classattr1 并未变化

类中访问实例属性和实例方法

在类中访问实例属性或实例方法必须使用 self 进行访问(这点和 C++ 区别比较大),类方法不能访问实例属性和实例方法。对于添加 self 的必要性,我们以一个实例来看

def test1():
    print "Outside test1"

class Test1:
    def __init__(self, a, b):
        self.a = "a"
        self.b = "b"

    def test1(self):
        print "Test1 test1"

    def test2(self):
        print "Test1 test2"

    def test(self):
        a = 'new a'
        print a
        print self.a
        self.test1()
        test1()
        self.test2()
        test2()

>>> t = Test1(1, 2)
>>> t.test()
new a
a
Test1 test1
Outside test1
Test1 test2

Traceback (most recent call last):
  File "<pyshell#6>", line 1, in <module>
    t.test()
  File "<pyshell#3>", line 19, in test
    test2()
NameError: global name 'test2' is not defined
>>> 

从上例我们可以看到不使用 self 访问 a,实际上 a 是一个临时变量,修改其值并不会影响 self.a 的值
同样,不使用 self 调用实例方法,实际上调用的是外部的方法,如果该方法不存在,执行就会出错

类的继承

单一继承

第一种定义方式

class A(object):
    def __init__(self, a1, a2):
        self.a1 = a1
        self.a2 = a2
        print "A.__init__()"

class B(A):
    def __init__(self, b):
        super(B, self).__init__(b, b)
        self.b = b
        print "B.__init__()"

>>> b =B(1)
A.__init__()
B.__init__()

注意:在 Python 2.7 中 class A 必须要指定继承 object,否则在执行 super(B, self).init(b, b) 时会报错

第二种定义方式

class A(object):
    def __init__(self, a1, a2):
        self.a1 = a1
        self.a2 = a2
        print "A.__init__()"

class C(A):
    def __init__(self, c):
        A.__init__(self, c, c)
        self.c = c
        print "C.__init__()"

>>> c = C(10)
A.__init__()
C.__init__()

在 Python 2.7 中使用第二种定义方式,class A 后面可以不指定继承 object

上述两种方法中,在子类的 __init__() 中调用父类的 __init__(),是为了让子类对父类中的一些变量进行初始化,该步不是必须的

单一继承中的多态

class A(object):
    def __init__(self):
        print "A.__init__()"

    def say(self):
        print "class A say"

    def eat(self):
        print "class A eat"

    def sleep(self):
        print "class A sleep"


class B(A):
    def __init__(self):
        super(B, self).__init__()
        print "B.__init__()"

    def say(self):
        print "class B say"

    def sleep(self, time):
        print "class B sleep", str(time)


class C(A):
    def __init__(self):
        A.__init__(self)
        print "C.__init__()"

    def say(self):
        print "class C say"

    def sleep(self, time):
        print "class C sleep", str(time)

>>> a = A()
A.__init__()
>>> b = B()
A.__init__()
B.__init__()
>>> c = C()
A.__init__()
C.__init__()
>>> a.say()
class A say
>>> b.say()
class B say
>>> c.say()
class C say
>>> a.eat()
class A eat
>>> b.eat()
class A eat
>>> c.eat()
class A eat
>>> a.sleep()
class A sleep
>>> b.sleep()

Traceback (most recent call last):
  File "<pyshell#16>", line 1, in <module>
    b.sleep()
TypeError: sleep() takes exactly 2 arguments (1 given)
>>> c.sleep()

Traceback (most recent call last):
  File "<pyshell#17>", line 1, in <module>
    c.sleep()
TypeError: sleep() takes exactly 2 arguments (1 given)
>>> b.sleep(20)
class B sleep 20
>>> c.sleep(10)
class C sleep 10
>>> 

总结上面的例子:

  • 当子类未对父类的方法进行重写,调用方法调用的是父类的方法,如 eat()
  • 当子类对父类的方法进行重写后,调用方法实际调用的是子类的方法,如 say()
  • 当子类对父类的方法进行重写,并且参数发生了改变,调用时必须为子类方法重写后的参数个数,否则程序出错,如 sleep。这点与 C++ 中不太一样,C++ 中同名函数,如果参数个数不一样,编译后得到的函数名是不一样的,Python 函数名和参数并无相关性,不能对同一个函数名绑定不同个数参数
类的两个同名函数测试
class Test(object):
    def __init__(self):
        pass

    def test1(self, a):
        print "test1", str(a)

    def test1(self):
        print "test1"

>>> t = Test()
>>> t.test1()
test1
>>> t.test1(10)

Traceback (most recent call last):
  File "<pyshell#38>", line 1, in <module>
    t.test1(10)
TypeError: test1() takes exactly 1 argument (2 given)
>>> 

如上例所示,当在同一类中定义两个相同名字的函数时,后定义的一个会把前面一个覆盖掉。前面的 B 和 C 中的 sleep 实际就是把原来从 A 中继承过来的 sleep 覆盖掉了,所以不能无参数调用

多重继承

两个父类拥有相同的初始化参数个数
class A(object):
    def __init__(self):
        print "A.__init__()"


class B(object):
    def __init__(self):
        print "B.__init__()"


class C(A, B):
    def __init__(self):
        super(C, self).__init__()
        super(C, self).__init__()
        print "C.__init__()"


class D(A, B):
    def __init__(self):
        A.__init__(self)
        B.__init__(self)
        print "D.__init__()"

>>> d = D()
A.__init__()
B.__init__()
D.__init__()

>>> c = C()
A.__init__()
A.__init__()
C.__init__()
>>> 

从上面的例子看出,多重继承使用 super 的方式调用父类的初始化函数,会出现问题,因为解释器不知道该调用 A 的初始化函数还是 B 的初始化函数,在本例中,就出现了调用两次 A 的初始化的情况
因此,对于多重继承,建议使用第二种初始化方式,指定父类的名称

两个父类拥有不同的初始化参数个数
class A(object):
    def __init__(self, a, b):
        print "A.__init__()"


class B(object):
    def __init__(self, a):
        print "B.__init__()"


class C(A, B):
    def __init__(self, a):
        super(C, self).__init__(a, a)
        super(C, self).__init__(a)
        print "C.__init__()"


class D(A, B):
    def __init__(self, a):
        A.__init__(self, a, a)
        B.__init__(self, a)
        print "D.__init__()"

>>> c = C(10)
A.__init__()

Traceback (most recent call last):
  File "<pyshell#9>", line 1, in <module>
    c = C(10)
  File "<pyshell#5>", line 4, in __init__
    super(C, self).__init__(a)
TypeError: __init__() takes exactly 3 arguments (2 given)
>>> 
>>> d = D(10)
A.__init__()
B.__init__()
D.__init__()

从上例可以看出,第一种类继承方法,当两个父类初始化参数不同时,会直接报错,因此建议使用第二种方法,第一种方法只适用于单一继承

多重继承中的多态

class A(object):
    def __init__(self):
        print "A.__init__()"

    def say(self):
        print "Class A say"

    def eat(self):
        print "Class A eat"

    def sleep(self):
        print "Class A sleep"

    def run(self):
        print "Class A run"


class B(object):
    def __init__(self):
        print "B.__init__()"

    def say(self):
        print "Class B say"

    def eat(self, a):
        print "Class B eat", str(a)

    def sleep(self, a):
        print "Class B sleep", str(a)

    def run(self, a):
        print "Class B run", str(a)


class C(A, B):
    def __init__(self, a):
        A.__init__(self)
        B.__init__(self)
        print "C.__init__()"

    def say(self):
        print "Class C say"

    def eat(self, a, b):
        print "Class C eat", str((a, b))

    def sleep(self, a):
        print "Class C sleep", str(a)

>>> c = C(1)
A.__init__()
B.__init__()
C.__init__()
>>> c.say()
Class C say
>>> c.eat()

Traceback (most recent call last):
  File "<pyshell#7>", line 1, in <module>
    c.eat()
TypeError: eat() takes exactly 3 arguments (1 given)
>>> c.eat(1)

Traceback (most recent call last):
  File "<pyshell#8>", line 1, in <module>
    c.eat(1)
TypeError: eat() takes exactly 3 arguments (2 given)
>>> c.eat(1, 2)
Class C eat (1, 2)
>>> c.sleep()

Traceback (most recent call last):
  File "<pyshell#10>", line 1, in <module>
    c.sleep()
TypeError: sleep() takes exactly 2 arguments (1 given)
>>> c.sleep(1)
Class C sleep 1
>>> c.run()
Class A run
>>> c.run(1)

Traceback (most recent call last):
  File "<pyshell#13>", line 1, in <module>
    c.run(1)
TypeError: run() takes exactly 1 argument (2 given)
>>> 

和单一继承相同,当子类重写了一个函数,参数相同不相同都是调用子类重写的函数,因此参数个数必须符合子类重写的函数参数
如果子类未对父类的函数进行重写,如果父类有相同函数名的函数,只会继承其中一个,具体继承哪一个不确定,初步测试下来,应该与父类类名排序顺序有关

总结

类的继承尽量使用“父类类名.__init__()”的方式进行初始化,避免使用 super 的方式
子类重写父类的函数后,如果参数个数发生变化,必须以子类参数个数为准
多重继承时,尽量避免两个父类拥有同名,参数不同的函数

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值