扩展类的继承与私有属性

简介: 扩展类的继承与私有属性


扩展类的继承




扩展类和动态类不同,扩展类只能继承单个基类,并且继承的基类必须也是扩展类(或者内置类,扩展类和内置类等价,都是静态类)。如果扩展类继承的基类是常规的动态类,或者继承了多个基类,那么编译时会报错。

cdef class Person:
    cdef public:
        str name
        int age
    def __init__(self, str name, int age):
        self.name = name
        self.age = age
    cpdef str get_info(self):
        return f"name: {self.name}, " \
               f"age: {self.age}, where: {self.where}"
cdef class CGirl(Person):
    cdef public str where
    def __init__(self,
                 str name,
                 int age,
                 str where):
        self.where = where
        super().__init__(name, age)
class PyGirl(Person):
    def __init__(self,
                 str name,
                 int age,
                 str where):
        self.where = where
        super().__init__(name, age)

文件名为 cython_test.pyx,下面来测试一下:

import pyximport
pyximport.install(language_level=3)
import cython_test
c_girl = cython_test.CGirl("古明地觉", 17, "东方地灵殿")
py_girl  = cython_test.PyGirl("古明地觉", 17, "东方地灵殿")
print(c_girl.get_info())
print(py_girl .get_info())
"""
name: 古明地觉, age: 17, where: 东方地灵殿
name: 古明地觉, age: 17, where: 东方地灵殿
"""
print(c_girl.name, c_girl.age, c_girl.where)
print(py_girl.name, py_girl.age, py_girl.where)
"""
古明地觉 17 东方地灵殿
古明地觉 17 东方地灵殿
"""

我们看一下扩展类 Person 里面的 get_info,返回值获取了 self.where,但 self 明明没有绑定 where 属性居然不会报错。原因很简单,我们是通过子类调用的 get_info,所以 self 是子类的 self,而子类的 self 是有 where 属性的。

另外我们看到,虽然扩展类不可继承动态类,但动态类可以继承扩展类。根据我们使用 Python 的经验也能得出结论,我们定义动态类的时候可以继承内置类,而内置类和扩展类是等价的。

因此在继承方面,扩展类和动态类的用法类似,但扩展类对继承有要求,就是它继承的必须也是扩展类,而且只能继承一个。


私有属性(方法)




继承的时候,子类是否可以访问父类的所有属性呢?我们说 cdef 定义的成员函数,无法被外部的 Python 代码访问,那么内部的 Python 类在继承的时候可不可以访问呢?以及私有属性(方法)的访问又是什么情况呢?

我们先来看看 Python 里面关于私有属性的例子。

class A:
    def __init__(self):
        self.__name = "xxx"
    def __foo(self):
        return self.__name
try:
    A().__name
except Exception as e:
    print(e)
"""
'A' object has no attribute '__name'
"""
try:
    A().__foo()
except Exception as e:
    print(e)
"""
'A' object has no attribute '__foo'
"""
print(A()._A__name)  # xxx
print(A()._A__foo())  # xxx

私有属性只能在当前类里面使用,一旦出去了就不能够再访问了。其实私有属性本质上只是解释器给你改了个名字,在原来的名字前面加上一个 _类名,所以 __name 和 __foo 其实相当于是 _A__name 和 _A__foo。

但是当我们在外部用实例去获取 __name 和 __foo 的时候,获取的就是 __name 和 __foo,而显然 A 里面没有这两个属性或方法,因此报错。解决的办法就是使用 _A__name 和 _A__foo,但是不建议这么做,因为这是私有变量,如果非要访问的话,那就不要定义成私有的。

但如果是在 A 这个类里面获取的话,那么解释器会自动为我们加上 _类名 这个前缀,所以是没问题的。比如我们在类里面获取 self.__name 的时候,实际上获取的也是 self._A__name,但是在类的外面就不会了。

另外再补充一下,我们说私有属性只是解释器给改了个名字,但不光是私有属性,只要类里面是以双下划线开头(不以双下划线结尾)的变量,名字都会被解释器给改掉,举个例子:

_A__name = "古明地觉"
class A:
    name = __name
    def __init__(self):
        self.name = __name
print(A.name)
print(A().name)
"""
古明地觉
古明地觉
"""

在 A 这个类里面,__name 实际上就是 _A__name。凡是以双下划线开头(不以双下划线结尾)的变量,解释器都会将它的名字给改掉,在原有的名字的前面加上 _类名,而这样的变量如果绑定在 self 上面,我们就称它为私有属性。

这里我们再将类的名字改成 B,那么会有什么结果呢?显然是报错,因为找不到 _B__name。

所以在类里面操作的私有属性,其实早已被解释器改了个名字,但是在类里面使用的时候是无感知的。然而一旦在类的外部使用就不行了,我们需要手动的加上 _类名,但是不建议在类的外部访问私有属性。

如果是继承的话,会有什么结果呢?

class A:
    def __init__(self):
        self.__name = "xxx"
    def __foo(self):
        return self.__name
class B(A):
    def test(self):
        try:
            self.__name
        except Exception as e:
            print(e)
        try:
            self.__foo()
        except Exception as e:
            print(e)
B().test()
"""
'B' object has no attribute '_B__name'
'B' object has no attribute '_B__foo'
"""

通过报错信息我们可以得知原因,B 也是一个类,那么在 B 里面获取私有属性,同样会加上 _类名 这个前缀。但是这个类名显然是 B 的类名,不是 A 的类名,因此找不到 _B__name 和 _B__foo,当然我们强制通过 _A__name 和 _A__foo 也是可以访问的,只是不建议这么做。

因此动态类里面不存在绝对的私有,只不过是解释器内部偷梁换柱将私有属性换了个名字罢了,但我们可以认为它是私有的,因为在类的外面按照原本的逻辑没有办法访问了。同理继承的子类,也没有办法使用父类的私有属性。

以上就是 Python 的私有,但在 Cython 里面是否也是这样呢?

cdef class Person:
    cdef public:
        long __age
        str __name
        long length
    def __init__(self, name, age, length):
        self.__age = age
        self.__name = name
        self.length = length
    cdef str __get_info(self):
        return f"name: {self.__name}, " \
               f"age: {self.__age}, length: {self.length}"
    cdef str get_info(self):
        return f"name: {self.__name}, " \
               f"age: {self.__age}, length: {self.length}"
cdef class CGirl(Person):
    cpdef test1(self):
        return self.__name, self.__age, self.length
    cpdef test2(self):
        return self.__get_info()
    cpdef test3(self):
        return self.get_info()

静态类 CGirl 继承静态类 Person,那么 CGirl 对象能否使用 Person 里面的私有属性或方法呢?

import pyximport
pyximport.install(language_level=3)
import cython_test
c_g = cython_test.CGirl("古明地觉", 17, 156)
print(c_g.__name, c_g.__age, c_g.length)
"""
古明地觉 17 156
"""
print(c_g.test1())  
print(c_g.test2())  
print(c_g.test3())  
"""
('古明地觉', 17, 156)
name: 古明地觉, age: 17, length: 156
name: 古明地觉, age: 17, length: 156
"""

我们看到没有任何问题,对于扩展类而言,子类可以使用父类中 cdef 定义的方法。除此之外,私有属性(方法)也是可以使用的,就仿佛这些属性定义在自身内部一样。

其实根本原因就在于,对于扩展类而言,里面的所有属性名称、方法名称都是所见即所得。比如我们设置了 self.__name,那么它的属性名就叫做 __name,不会在属性名的前面加上 _类名,获取的时候也是一样。所以对于扩展类而言,属性(方法)名称是否以双下划线开头根本无关紧要。

然后我们再来看看动态类继承扩展类之后会有什么表现呢?

cdef class Person:
    cdef public:
        long __age
        str __name
        long length
    def __init__(self, name, age, length):
        self.__age = age
        self.__name = name
        self.length = length
    cdef str __get_info(self):
        return f"name: {self.__name}, " \
               f"age: {self.__age}, length: {self.length}"
    cdef str get_info(self):
        return f"name: {self.__name}, " \
               f"age: {self.__age}, length: {self.length}"
class PyGirl(Person):
    def __init__(self, name, age,
                 length, where):
        self.__where = where
        super().__init__(name, age, length)
    def test1(self):
        return self.__name, self.__age, self.length
    def test2(self):
        return self.__get_info()
    def test3(self):
        return self.get_info()

我们来测试一下:

import pyximport
pyximport.install(language_level=3)
import cython_test
py_g = cython_test.PyGirl("古明地觉", 17, 
                          156, "东方地灵殿")
# 首先 __name、__age、length 这三个属性
# 都是在 Person 里面设置的,Person 是一个静态类
# 而我们说静态类里面没有那么多花里胡哨的
# 不会在以双下划线开头的成员变量前面加上 "_类名"
# 所以直接获取是没有问题的
print(py_g.__name)  # 古明地觉
print(py_g.__age)  # 17
print(py_g.length)  # 156
# 但是 __where 不一样,它不是在静态类中设置的
# 所以它会加上 "_类名"
try:
    py_g.__where
except AttributeError as e:
    print(e) 
"""
'PyGirl' object has no attribute '__where'
"""    
print(py_g._PyGirl__where)  # 东方地灵殿
try:
    py_g.test1()
except AttributeError as e:
    print(e)
"""
'PyGirl' object has no attribute '_PyGirl__name'
"""
# 我们看到调用 test1 的时候报错了
# 原因就在于对于动态类而言,在类里面调用以双下划线开头的属性
# 会自动加上 "_类名",所以此时反而不正确了
try:
    py_g.test2()
except AttributeError as e:
    print(e) 
"""
'PyGirl' object has no attribute '_PyGirl__get_info'
"""    
# 对于调用方法也是如此
# 因为解释器 "自作聪明" 的加上了 "_类名",导致方法名错了
try:
    py_g.test3()
except AttributeError as e:
    print(e)
"""
'PyGirl' object has no attribute 'get_info'
"""    
# 无法调用 cdef 定义的方法

因此结论很清晰了,静态类非常单纯,里面的属性(方法)名称所见即所得,双下划线开头的属性(方法)对于静态类而言并没有什么特殊含义,动态类之所以不能调用是因为"多此一举"地在前面加上了 "_类名",导致方法名指定错了。

然后是 cdef 定义的方法,即使是在 Cython 中,动态类也是不可以调用的。因为我们说 cdef 定义的是 C 一级的方法,它既不是 Python 的方法、也不像 cpdef 定义的时候自带 Python 包装器,因此它无法被子类继承,因此也就没有跨语言的边界。

如果将 cdef 改成 def 或者 cpdef,那么动态类就可以调用了。


真正的私有




双下划线开头、非双下划线结尾的属性,在动态类里面叫私有属性,但在静态类里面则没有任何特殊的含义。因为是否私有不是通过名称前面是否有双下划线决定的,而是通过是否在类里面使用 cdef public 或者 cdef readonly 进行了声明所决定的。并且通过这种方式定义的私有,是真正意义上的私有,如果不想让外界访问,那么外界是无论如何都访问不到的。

cdef class Person:
    cdef public:
        str where
    def __init__(self, where):
        self.where = where
cdef class CGirl(Person):
    cdef:
        str name
        int age
        int length
    def __init__(self, name, age, length, where):
        self.name = name
        self.age = age
        self.length = length
        super(CGirl, self).__init__(where)

注意:对于 CGirl 而言,我们不需要声明 where,因为 self.where 的绑定是在 Person 中发生的,只要在 Person 中声明即可。由于 CGirl 继承 Person,如果 CGirl 中也声明了 where 那么反而会报错。

import pyximport
pyximport.install(language_level=3)
import cython_test
c_g = cython_test.CGirl("古明地觉", 16, 
                        157, "东方地灵殿")
# where 是使用 cdef public 声明的,所以不是私有的
# name、age、length 是使用 cdef 声明的,所以是私有的
print(c_g.where)  # 东方地灵殿
print(hasattr(c_g, "where"))  # True
print(hasattr(c_g, "name"))  # False
print(hasattr(c_g, "age"))  # False
print(hasattr(c_g, "length"))  # False

此时实现的私有,是真正意义上的私有。

相关文章
用原型链的方式写一个类和子类
用原型链的方式写一个类和子类
37 0
|
3月前
|
C++ Python
扩展类的成员函数
扩展类的成员函数
27 0
|
7月前
|
Python
类的继承
类的继承
36 1
|
7月前
|
存储 编译器 程序员
【C++】类和对象①(什么是面向对象 | 类的定义 | 类的访问限定符及封装 | 类的作用域和实例化 | 类对象的存储方式 | this指针)
【C++】类和对象①(什么是面向对象 | 类的定义 | 类的访问限定符及封装 | 类的作用域和实例化 | 类对象的存储方式 | this指针)
|
7月前
|
存储 C语言 C++
【c++】类和对象 - 类的访问限定符及封装/作用域和实例化
【c++】类和对象 - 类的访问限定符及封装/作用域和实例化
【c++】类和对象 - 类的访问限定符及封装/作用域和实例化
|
安全 编译器 数据安全/隐私保护
C++——类的继承
C++——类的继承
C++——类的继承
|
安全 程序员 C++
【C++要笑着学】继承 | 子类默认成员函数 | 单继承与多继承 | 钻石继承 | 虚拟继承 | 继承和组合(一)
本系列 C++ 教学博客的基础知识已经告一段落了,下面的章节我会先把面向对象三大特性讲完,然后穿插一些数据结构的教学以方便我们继续讲解 STL 的 map 和 set。对于面向对象三大特性 —— 封装、继承、多态,我们已经在之前讲解过封装了,本章将开始讲解继承,详细探讨多继承引发的钻石继承问题,并用虚继承解决钻石继承问题。阅读本章需要掌握访问限定符以及默认成员函数的知识,如果阅读过程中感到有些许生疏建议先去复习一下。
130 0
【C++要笑着学】继承 | 子类默认成员函数 | 单继承与多继承 | 钻石继承 | 虚拟继承 | 继承和组合(一)
|
Java 编译器 C++
【C++要笑着学】继承 | 子类默认成员函数 | 单继承与多继承 | 钻石继承 | 虚拟继承 | 继承和组合(二)
本系列 C++ 教学博客的基础知识已经告一段落了,下面的章节我会先把面向对象三大特性讲完,然后穿插一些数据结构的教学以方便我们继续讲解 STL 的 map 和 set。对于面向对象三大特性 —— 封装、继承、多态,我们已经在之前讲解过封装了,本章将开始讲解继承,详细探讨多继承引发的钻石继承问题,并用虚继承解决钻石继承问题。阅读本章需要掌握访问限定符以及默认成员函数的知识,如果阅读过程中感到有些许生疏建议先去复习一下。
173 0
【C++要笑着学】继承 | 子类默认成员函数 | 单继承与多继承 | 钻石继承 | 虚拟继承 | 继承和组合(二)
|
存储 编译器 程序员
【C++要笑着学】类和对象 | 初识封装 | 访问限定符 | 类的作用域和实例化 | 类对象模型 | this指针(二)
本章将正式开始学习C++中的面向对象,本篇博客涵盖讲解 访问限定符、封装的基础知识、类的作用域和实例化、探究类对象的存储和对于this指针由浅入深地讲解。
175 0
【C++要笑着学】类和对象 | 初识封装 | 访问限定符 | 类的作用域和实例化 | 类对象模型 | this指针(二)