Python中的类(中)

上一篇介绍了Python中类相关的一些基本点,本文看看Python中类的继承和__slots__属性。

继承

在Python中,同时支持单继承与多继承,一般语法如下:

class SubClassName(ParentClass1 [, ParentClass2, ...]):
    class_suite

实现继承之后,子类将继承父类的属性,也可以使用内建函数insubclass()来判断一个类是不是另一个类的子孙类:

class Parent(object):
    ‘‘‘
    parent class
    ‘‘‘
    numList = []
    def numAdd(self, a, b):
        return a+b

class Child(Parent):
    pass

c = Child()
# subclass will inherit attributes from parent class
Child.numList.extend(range(10))
print Child.numList
print "2 + 5 =", c.numAdd(2, 5)

# built-in function issubclass()
print issubclass(Child, Parent)
print issubclass(Child, object)

# __bases__ can show all the parent classes
print Child.__bases__

# doc string will not be inherited
print Parent.__doc__
print Child.__doc__

代码的输出为,例子中唯一特别的地方是文档字符串。文档字符串对于类,函数/方法,以及模块来说是唯一的,也就是说__doc__属性是不能从父类中继承来的。

继承中的__init__

当在Python中出现继承的情况时,一定要注意初始化函数__init__的行为。

1. 如果子类没有定义自己的初始化函数,父类的初始化函数会被默认调用;但是如果要实例化子类的对象,则只能传入父类的初始化函数对应的参数,否则会出错。

class Parent(object):
    def __init__(self, data):
        self.data = data
        print "create an instance of:", self.__class__.__name__
        print "data attribute is:", self.data

class Child(Parent):
    pass

c = Child("init Child")
print
c = Child()   

代码的输出为:

2. 如果子类定义了自己的初始化函数,而没有显示调用父类的初始化函数,则父类的属性不会被初始化

class Parent(object):
    def __init__(self, data):
        self.data = data
        print "create an instance of:", self.__class__.__name__
        print "data attribute is:", self.data

class Child(Parent):
    def __init__(self):
        print "call __init__ from Child class"

c = Child()
print c.data

代码的输出为:

3. 如果子类定义了自己的初始化函数,显示调用父类,子类和父类的属性都会被初始化

class Parent(object):
    def __init__(self, data):
        self.data = data
        print "create an instance of:", self.__class__.__name__
        print "data attribute is:", self.data

class Child(Parent):
    def __init__(self):
        print "call __init__ from Child class"
        super(Child, self).__init__("data from Child")

c = Child()
print c.data

代码的输出为:

super

前面一个例子中,已经看到了通过super来调用父类__init__方法的例子,下面看看super的使用。

在子类中,一般会定义与父类相同的属性(数据属性,方法),从而来实现子类特有的行为。也就是说,子类会继承父类的所有的属性和方法,子类也可以覆盖父类同名的属性和方法。

class Parent(object):
    fooValue = "Hi, Parent foo value"
    def foo(self):
        print "This is foo from Parent"

class Child(Parent):
    fooValue = "Hi, Child foo value"
    def foo(self):
        print "This is foo from Child"

c = Child()
c.foo()
print Child.fooValue

在这段代码中,子类的属性"fooValue"和"foo"覆盖了父类的属性,所以子类有了自己的行为。

但是,有时候可能需要在子类中访问父类的一些属性:

class Parent(object):
    fooValue = "Hi, Parent foo value"
    def foo(self):
        print "This is foo from Parent"

class Child(Parent):
    fooValue = "Hi, Child foo value"
    def foo(self):
        print "This is foo from Child"
        print Parent.fooValue
        # use Parent class name and self as an argument
        Parent.foo(self)

c = Child()
c.foo()

这时候,可以通过父类名直接访问父类的属性,当调用父类的方法是,需要将"self"显示的传递进去的方式。

这种方式有一个不好的地方就是,需要经父类名硬编码到子类中,为了解决这个问题,可以使用Python中的super关键字:

class Parent(object):
    fooValue = "Hi, Parent foo value"
    def foo(self):
        print "This is foo from Parent"

class Child(Parent):
    fooValue = "Hi, Child foo value"
    def foo(self):
        print "This is foo from Child"
        # use super to access Parent attribute
        print super(Child, self).fooValue
        super(Child, self).foo()

c = Child()
c.foo()

对于"super(Child, self).foo()"可以理解为,首先找到Child的父类Parent,然后调用父类的foo方法,同时将Child的实例self传递给foo方法。

但是,如果当一个子类有多个父类的时候,super会如何工作呢?这是就需要看看MRO的概念了。

MRO

假设现在有一个如下的继承结构,首先通过类名显示调用的方式来调用父类的初始化函数:

class A(object):
    def __init__(self):
        print "   ->Enter A"
        print "   <-Leave A" 

class B(A):
    def __init__(self):
        print "  -->Enter B"
        A.__init__(self)
        print "  <--Leave B"

class C(A):
    def __init__(self):
        print " --->Enter C"
        A.__init__(self)
        print " <---Leave C"

class D(B, C):
    def __init__(self):
        print "---->Enter D"
        B.__init__(self)
        C.__init__(self)
        print "<----Leave D"

d = D()

从输出中可以看到,类A的初始化函数被调用了两次,这不是我们想要的结果:

下面,我们通过super方式来调用父类的初始化函数:

class A(object):
    def __init__(self):
        print "   ->Enter A"
        print "   <-Leave A" 

class B(A):
    def __init__(self):
        print "  -->Enter B"
        super(B, self).__init__()
        print "  <--Leave B"

class C(A):
    def __init__(self):
        print " --->Enter C"
        super(C, self).__init__()
        print " <---Leave C"

class D(B, C):
    def __init__(self):
        print "---->Enter D"
        super(D, self).__init__()
        print "<----Leave D"

d = D()

通过输出可以看到,当使用super后,A的初始化函数只能调用了一次:

为什么super会有这种效果?下面就开始看看Python中的方法解析顺序MRO(Method Resolution Order)。

Python的类有一个__mro__属性,这个属性中就保存着方法解析顺序。结合上面的例子来看看类D的__mro__:

>>> print "MRO:", [x.__name__ for x in D.__mro__]
MRO: [‘D‘, ‘B‘, ‘C‘, ‘A‘, ‘object‘]
>>>

看到这里,对于上面使用super例子的输出就应该比较清楚了。

  • Python的多继承类是通过MRO的方式来保证各个父类的函数被逐一调用,而且保证每个父类函数只调用一次(如果每个类都使用super)
  • 混用super类和非绑定的函数是一个危险行为,这可能导致应该调用的父类函数没有调用或者一个父类函数被调用多次

__slots__

从前面的介绍可以看到,当我们通过一个类创建了实例之后,仍然可以给实例添加属性,但是这些属性只属于这个实例。

有些时候,我们可以需要限制类实例对象的属性,这时就要用到类中的__slots__属性了。__slots__属性对于一个tuple,只有这个tuple中出现的属性可以被类实例使用。

class Student(object):
    __slots__ = ("name", "age")
    def __init__(self, name, age):
        self.name = name
        self.age = age

s = Student("Wilber", 28)
print "%s is %d years old" %(s.name, s.age)
s.score = 96

在这个例子中,当场是给Student的实例s添加一个score属性的时候,就会遇到下面的异常:

子类没有__slots__属性

使用__slots__要注意,__slots__定义的属性仅对当前类的实例起作用,对继承的子类实例是不起作用的:

class Person(object):
    __slots__ = ("name", "age")
    pass

class Student(Person):
    pass

s = Student()
s.name, s.age = "Wilber", 28
s.score = 100

print "%s is %d years old, score is %d" %(s.name, s.age, s.score)

从代码的输出可以看到,子类Student的实例并不受父类中__slots__属性的限制:

子类拥有__slots__属性

但是,如果子类本身也有__slots__属性,子类的属性就是自身的__slots__加上父类的__slots__。

class Person(object):
    __slots__ = ("name", "age")
    pass

class Student(Person):
    __slots__ = ("score", )
    pass

s = Student()
s.name, s.age = "Wilber", 28
s.score = 100

print "%s is %d years old, score is %d" %(s.name, s.age, s.score)
print s.__slots__

s.city = "Shanghai"

代码的输出为:

所以说,对于__slots__属性:

  • 如果父类包含对__slots__的定义,子类不包含对__slots__的定义,解释器忽略__slots__的作用
  • 如果父类包含对__slots__的定义,子类包含对__slots__的定义,并且无论元组的的元素个数,解释器都会按照父类的__slots__和子类的__slots__的并集来检查

总结

本文介绍了Python中的继承,当使用多继承的时候,可以使用super关键字去访问父类中被子类覆盖的方法;对于方法的调用,需要参照MRO。

另外介绍了Python类的__slots__属性,通过这个属性可以限制类实例的可用属性。

时间: 2024-08-29 10:19:15

Python中的类(中)的相关文章

C#中Thread类中Join方法的理解(转载)

指在一线程里面调用另一线程join方法时,表示将本线程阻塞直至另一线程终止时再执行      比如 Java代码   using System; namespace TestThreadJoin { class Program { static void Main() { System.Threading.Thread x = new System.Threading.Thread(new System.Threading.ThreadStart(f1)); x.Start(); Console

Java中主类中定义方法加static和不加static的区别

Java中主类中定义方法加static和不加static的区别(前者可以省略类名直接在主方法调用,后者必须先实例化后用实例调用) 知识点:1.Getter and Setter 的应用 2.局部变量与成员变量(也可叫做全局变量) 3.Static关键字的用法 a.成员变量被static修饰后的所有类的共享属性 b.方法被static修饰之后,在本类内调用的类名省略问题;以及不用Static,即使在本类内也必须先实例化 4.This关键字的用法 this:是当前类的对象引用.简单的记,它就代表当前

python第三十六天-----类中的特殊成员方法

__doc__ 查看尖的描述信息 __module__表示当前操作的对象所在的模块 __class__表示当前操作的对象所属的类 __init__构造方法 通过类创建对象自动执行 __del__析构方法,当前对象在内存中被释放自动斩妖执行 __call__对象后面加括号触发执行 __dict__查看类或对象中的成员 __str__如果一个类中定义了此方法,那么打印此类对象时,输出此方法的返回值 __getitem__当类中定义了一个字典的属性成员,可以获取 __setitem__设置修改类中字典

python中ctypes类中的平台兼容性问题

python中的ctypes中定义的类型和C中的类型一一对应,应用在二进制文件解析中十分方便,但是使用过程中一定要注意平台兼容性的问题,例如c_ulong类型在linux和windows环境中代表的字符数是不一样的,为了保证平台兼容性,可以使用c_int64来代替. windows中, linux中, 原文地址:https://www.cnblogs.com/rucnevermore/p/9071470.html

python中的类中属性元素加self.和不加self.的区别

在类中,self只能在函数中使用,表示的是实例属性,就是每个实例可以设置不值,而不相互影响. 如果在类级别使用没有self的属性,是类属性,一般作为全局变量来用的.事实上:就是一个是类属性 一个是对象属性 类和实例都可以访问到属性 当你想让这个变量成为这个类的子变量时 class lc(): def __init__(self): self.a = 5 smalllc = lc() print smalllc. 如果不加self的话 , 那么就不能 smalllc.a 这样访问了

回调函数中调用类中的非静态成员变量或非静态成员函数

有关这方面的问题,首先说一点: 回调函数必须是静态成员函数或者全局函数来实现回调函数,大概原因是普通的C++成员函数都隐含了一个函数参数,即this指针,C++通过传递this指针给成员函数从而实现函数可以访问类的特定对象的数据成员.由于this指针的原因,使得一个普通成员函数作为回调函数时就会因为隐含的this指针问题使得函数参数个数不匹配,从而导致回调函数编译失败. 基于上面的理论,如何在类中封装回调函数呢? 回调函数只能是全局函数或者静态成员函数,但是由于全局函数会破坏封装性,所以只能用静

struct框架中实体类中属性的类型错误问题

在struct框架中,我们会写出实体类然后再sqlmap中进行映射,通常我们都知道 java中date类型有java.sql.date 和 java.util.date 经过细心发现,将对象生成为json对象时,json类会将date类型转换成java.util.date 如果在实体类中写成java.sql.date,就会抛出异常 下面我就来传个图 这里仅仅只需要把实体中date类型导入的包改成util的便不会出错 有人说util的包更常用,其实sql包中的直接输入就和我们平时写的时间的差不多,

CI框架中一个类中调用另一个类中已经加载对象测试

controller.php 1 <?php 2 class CI_Controller { 3 4 private static $instance; 5 6 public function __construct() 7 { 8 self::$instance =& $this; 9 10 foreach (is_loaded() as $var => $class) 11 { 12 $this->$var =& load_class($class); 13 $obj

关于在C#中对类中的隐藏基类方法和重写方法的理解

最近在学习C#,在C#中的类看到重写和隐藏基类的方法这些概念.才开始感觉自己不是很理解这些概念.也区分不开这些概念.通过自己的查找资料和练习后.慢慢的理解了类中的隐藏和重写这个概念.在C#中只有在基类定义了一些虚方法才能在派生类中重写基类中的虚方法.但是如果在派生类中使用隐藏方法,就不用在基类中定义虚方法.虚方法和重写实现的功能感觉是差不多的.都是在派生类中改变了基类中的方法,但是两者还是有质的区别,概念的性质也是不一样的.   重写是指:将基类中的方法替换掉,也就是抹掉基类中的原有方法,在派生

java中String类中的replace方法

package stringTest; public class StringDemo2 { public static void main(String[] args) { String s = "acvbb"; String s1 = new String("acvbb"); String s2 = s.replace("a", "X"); String s3 = s1.replace("a", &qu