扩展类的继承与私有属性
扩展类的继承
扩展类和动态类不同,扩展类只能继承单个基类,并且继承的基类必须也是扩展类(或者内置类,扩展类和内置类等价,都是静态类)。如果扩展类继承的基类是常规的动态类,或者继承了多个基类,那么编译时会报错。
cdef classPerson: 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 里面关于私有属性的例子。
classA: 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 = __nameprint(A.name)
print(A().name)
"""
古明地觉
古明地觉
"""
在 A 这个类里面,__name 实际上就是 _A__name。凡是以双下划线开头(不以双下划线结尾)的变量,解释器都会将它的名字给改掉,在原有的名字的前面加上 _类名,而这样的变量如果绑定在 self 上面,我们就称它为私有属性。
这里我们再将类的名字改成 B,那么会有什么结果呢?显然是报错,因为找不到 _B__name。
所以在类里面操作的私有属性,其实早已被解释器改了个名字,但是在类里面使用的时候是无感知的。然而一旦在类的外部使用就不行了,我们需要手动的加上 _类名,但是不建议在类的外部访问私有属性。
如果是继承的话,会有什么结果呢?
classA: def __init__(self):
self.__name = "xxx"
def __foo(self):
return self.__name
classB(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 classPerson:
cdefpublic:
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}"cdefclass
CGirl(Person): cpdeftest1(self):
return self.__name, self.__age, self.length
cpdeftest2(self):
return self.__get_info()
cpdeftest3(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 classPerson:
cdefpublic:
long __age
str __name
long length def__init__(self, name, age, length):
self.__age = age
self.__name = name
self.length = length
cdefstr __get_info(self):
return f"name: {self.__name}, " \
f"age: {self.__age}, length: {self.length}"
cdefstr 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)
deftest1(self):
return self.__name, self.__age, self.length
deftest2(self):
return self.__get_info()
deftest3(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 classPerson:
cdefpublic:
str where def __init__(self, where):
self.where = where
cdefclass CGirl(Person):
cdef:
strname
int age
intlength
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
此时实现的私有,是真正意义上的私有。