python编程基础——类(高级)

继承

派生类定义的语法如下:

class DerivedClassName(BaseClassName):
	<statement-1>
	·
	·
	·
	<statement-N>

其中BaseClassName必须定义在包含派生类定义的作用域中。什么意思呢?就是说派生类必须能够在当前的作用域中找到其要继承的基类。如果基类不在当前作用域中,而是在另一个模块中,这时候就需要这样定义了。

class DerivedClassName(modname.BaseClassName):

派生类在定义过程中和基类是一样的。在构造类对象时,基类会被记住。此信息将被用来解析属性引用:如果请求的属性在类中没有找到,那么就去基类中找,如果基类也是派生其他类,则转入基类的基类中寻找,以此类推。
在派生类中可能会重载其基类的方法。因为方法在调用同一对象的其他方法时没有特殊权限,调用同一基类中定义的另一方法的基类方法最终可能会调用覆盖它的派生类的方法。这种覆盖只会看名称相同与否,相同则覆盖,与参数个数无关。

class A:
    def __init__(self,a,b):
        self.a=a
        self.b=b
    def add(self):
        print("A类中的方法")

class B(A):
    def add(self):
        print("B类中的方法")
b=B(1,2) #因为派生类中没有初始化函数,转到基类找到__init__()
b.add() #B中的add()覆盖了A中的add
# 输出结果
B类中的方法

如果我我们想在派生类中对基类的函数进行扩充,即只需要使用BaseClassName.methodname(self, arguments)即可。(这种方式与是不是派生没有关系)。使用super().methodname(argumenrs)也是可以的(这种方式仅限于调用父类的方法)。

class A:
    def add(self):
        print("A类中的方法")

class B(A):
    def add(self):
        A.add(self) # 或者super().add()
        print("B类中的方法")
a=B()
a.add()    
# 输出结果
A类中的方法
B类中的方法

实例与继承的判断

我们可以通过isinstance(a,A)来检查一a实例是不是A类型。

a=5
b=3.14
print(isinstance(a,int),isinstance(b,int))
#输出结果
True False

我们可以通过issubclass(B,A)来检测B是不是派生于A。

class A:
    pass
class B(A):
    pass
print(issubclass(B,A),issubclass(A,B))
#输出结果
True False


多重继承

Python也支持多种继承形式。具有多个基类的类定义如下所示:

class DerivedClassName(Base1,Base2,Base3...):
	<statement-1>
	.
	.
	.
	<statement-N>

对于多数情况来说,搜索操作是深度优先的,从左至右进行,当遇到相同的名称时,只会保留第一次遇到的。

class A:
    def app(self):
        print("a_app")
class B:
    add='B'
    def add(self):
        print("b_add")
    def app(self):
        print("b_app")
class Ain(A):
    def add(self):
        print("ain_add")
class Bin(B):
    def app(self):
        print("bin_app")
class C(Ain,Bin):
    pass
class D(Bin,Ain):
    pass
c=C()
d=D()
c.add(),c.app()
d.add(),d.app()

#输出结果
ain_add
a_app
b_add
bin_app

嗯哼,我想上面的代码看起来肯定有点迷,于是画了一张图,从下面的图中使用深度优先,我相信你可以很清楚的理解它。
在这里插入图片描述



私有化

在Python中私有化名称是比较简单的。就是在要私有化的名称前面加上“__”便可以了。(值得注意的是,如果名称前后都加“__”则不会私有化)

class A:
    def __add(self):
        print("A类中的方法")
a=A()
a.__add()   
#输出结果
AttributeError: 'A' object has no attribute '__add'

虽然在类外不能够再访问函数了,但在类内还是可以访问的。

class A:
    def __add(self):
        print("A类中的私有方法")
    def app(self):
        self.__add()
        print("常规方法")
a=A()
a.app()  
#输出结果
A类中的私有方法
常规方法


迭代器

对于for循环的容器对象来说,容器对象是可迭代的。事实上,for语句会调用容器对象中的iter()。该函数返回一个定义了__next__()方法的迭代器对象,该方法将逐一访问容器中的元素。当元素耗尽时,__next()__将会引发StopIteration异常来终止for循环。
了解迭代器机制后,给自定义类添加迭代器就比较容易了。定义一个__iter()__()方法来返回一个带有__next__()方法的对象。如果类已经定义了__next()__,则__iter__()可以简单地返回self。

class A:
    def __init__(self,name):
        self.name=name
        self.index=len(name)
    def __iter__(self):
        return self
    def __next__(self):
        if self.index==0:
            raise StopIteration
        self.index = self.index - 1
        return self.name[self.index]
a=A(['asd','q',1,2])
print(iter(a))
for i in a:
    print(i)
# 输出结果
2
1
q
asd


生成器

生成器是一个用于创建迭代器的简单而强大的工具,它的写法类似函数,但返回数据时使用的不是return而是yield语句。每次生成器调用next()时,他会从上次离开位置执行(它会记录上次执行语句时的所有值)

我们可以看到return返回的仅仅是一个值,而yield返回的是一个可生成器对象,是所有的执行结果,对于这一点我们可以通过list来查看。

def ret():
    for i in range(5,1,-1):
        return i
def yie():
    for i in range(5,1,-1):
        yield i
print(ret())
print(list(yie()),yie())
#输出结果
5
[5, 4, 3, 2] <generator object yie at 0x0000027FF69230C0>

生成器的操作是可以用迭代器来完成的,不同的是生成器在写法上要比迭代器更加简单清晰,因为它会自动创建__iter__()__next()__方法。


生成器表达式

某些简单的生成器可以写成简单的表达式代码;

>>> a=[i*i for i in range(10)]
>>> a
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

>>> a=[1,2,4]
>>> b=[2,2,5]
>>> c=[x*y for x,y in zip(a,b)]
>>> c
[2, 4, 20]

>>> c=[x*y for x in a for y in b]
>>> c
[2, 2, 5, 4, 4, 10, 8, 8, 20]

>>> a = 'golf'
>>> list(a[i] for i in range(len(a)-1, -1, -1))
['f', 'l', 'o', 'g']

注意:迭代器和生成器都是执行的结果,不像列表一次性直接加载出来



杂项

dict

一个字典或其他类型的映射对象,用于存储对象的(可写)属性。

class A:
    "自定义类A"
    st='hello'
    def add(self,name):
        self.name=name
a=A()
print(A.__dict__)  
print(a.__dict__) 
# 输出结果
{'__module__': '__main__', 
 '__doc__': '自定义类A',
 'st': 'hello',
 'add': <function A.add at 0x007C37C8>, 
 '__dict__': <attribute '__dict__' of 'A' objects>, 
 '__weakref__': <attribute '__weakref__' of 'A' objects>}
{}

不难看出,__dict__以字典的形式返回当前类中的存在的熟悉名称(key)以及对应的值(value)。你可能会奇怪为什么实例化变量a的结果是空的。这是因为实例化的过程仅返回该类的一个新实例,所以实例化的属性在没有调用前是不会生产的(详细参见类(基础)的实例化),此时仅__init__函数会在实例化过程中被调用。因此有属性值age,但类A则不会有该属性,这是因为age是在实例化中产生的,是实例化变量的熟悉,与类A无关。

	...
	def __init__(self):
		self.age=5
	...
{'__module__': '__main__', '__doc__': '自定义类A', 
 'st': 'hello', '__init__': <function A.__init__ at 0x007C37C8>, 
 'add': <function A.add at 0x007C3810>, 
 '__dict__': <attribute '__dict__' of 'A' objects>, 
 '__weakref__': <attribute '__weakref__' of
'A' objects>}
{'age': 5}

当我们在实例化调用st时,因为在实例化中没有找到,因此转向类中寻找。因此a.st指向了类属性st,因此我们通过赋值来建立实例属性。只有通过赋值才能在实例中新建一个同名属性。

...
print(a.st)
print(a.__dict__)
a.st='hello world'
print(a.st)
print(a.__dict__) 
# 输出结果
hello
{'age': 5}
hello world
{'age': 5, 'st': 'hello world'}

因此,对于类和实例来说,实例化优先访问自己的属性,后访问类属性,在类中定义的属性可以被实例化调用,反之则不行。


solts

slots 允许我们显式地声明数据成员(例如特征属性)并禁止创建 dictweakref (除非是在 slots 中显式地声明或是在父类中可用。)

相比使用 dict 此方式可以显著地节省空间。 属性查找速度也可得到显著的提升。

我们通过 slots 为类P显示定义了两个属性ench。与 dict 不同的是,即便实例化后,也会返回一个和类一样的元组对象。

#显示定义了两个数据属性
class P:
    __slots__=("en","ch")
print(P.__slots__)
p=P()
print(p.__slots__)
#运行结果
('en', 'ch')
('en', 'ch')

那么 __sorts__ 有什么用呢?
  • 限制实例创建与类同名的属性
class P1:
    __slots__=("en","ch")
    en='asd'
    def __init__(self):
        self.en='asd_init'
p=P1()
#运行结果
ValueError: 'en' in __slots__ conflicts with class variable
  • 接管实例属性
class P1:
    __slots__=("en","ch")
    def __init__(self):
        self.en='asd_init'
p=P1()
print(p.en)
P1.en='asd'
print(p.en)
del P1.en
print(p.en)
#运行结果
asd_init
asd
AttributeError: 'P1' object has no attribute 'en'
  • 限制实例修改属性
class P1:
    __slots__=("en","ch")
p=P1()
P1.en="asd"
print(p.en)
P1.en="qwe"
print(p.en)
p.en="asd"
print(p.en)
#运行结果
asd
qwe
AttributeError: 'P1' object attribute 'en' is read-only

通过 __slots__ 可以让类管控实例,本质上是起到了优化内存的效果。(因为类和实例公用一个存储空间,且该存储空间由类管理)

setattr

object.__setattr__(self, name, value)
此方法在一个属性被尝试赋值时被调用。这个调用会取代正常机制(即将值保存到实例字典)。 name 为属性名称, value 为要赋给属性的值。

class P:
    def __setattr__(self,name,value):
        value+='__调用setattr函数写入dict'
        self.__dict__[name]=value
p=P()
p.n="asd"
print(p.n)
#输出结果
asd__调用setattr函数写入dict


getattr

object.__getattr__(self, name)
当访问属性失败时被调用,返回属性值或时引发一个AttributeError异常。注意如果属性是通过正常机制找到的,getattr()就不会被调用

class P:
    def __getattr__(self,name):
        return "调用的属性不存在"
p=P()
print(p.n)
#运行结果
调用的属性不存在


getattribute

object.__getattribute__(self, name)
此方法会无条件地被调用以实现对类实例属性的访问。如果类还定义了 getattr(),则后者不会被调用,除非 getattribute() 显式地调用它或是引发了 AttributeError。

class P:
    def __getattr__(self,name):
        return "调用的属性不存在"
    def __getattribute__(self,name):
        return object.__getattribute__(self,name)
p=P()

#调用未创建的属性
print(p.n)
#运行结果
函数getattribute
调用的属性不存在

#先创建属性再尝试调用它
p.n='asd'
print(p.n)
#运行结果
函数getattribute
asd

Q: 可以使用return self.__dict__[name]代替return object.__getattribute__(self,name)吗?

A: 不可以,因为如果用这样的方式就是访问 self.__dict__,只要是访问类的某个属性,就要调用__getattribute__,会导致循环下去。

class P:
    def __getattribute__(self,name):
        return self.__dict__[name]
p=P()
p.n='asd'
print(p.n)
#输出结果
···
  return self.__dict__[name]
 [Previous line repeated 996 more times]
RecursionError: maximum recursion depth exceeded

Q: 为什么要用 object.__getattribute__(self,name)呢?
A: object类有__getattribute__属性,因此所有的类默认就有__getattribute__属性(所有类都继承自object),object的__getattribute__起什么用呢?它做的就是查找自定义类的属性,如果属性存在则返回属性的值,如果不存在则抛出AttributeError。

发布了19 篇原创文章 · 获赞 19 · 访问量 3611

猜你喜欢

转载自blog.csdn.net/qq_36733722/article/details/102499247
今日推荐