扩展类的继承
扩展类和动态类不同,扩展类只能继承单个基类,并且继承的基类必须也是扩展类(或者内置类,扩展类和内置类等价,都是静态类)。如果扩展类继承的基类是常规的动态类,或者继承了多个基类,那么编译时会报错。
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
此时实现的私有,是真正意义上的私有。