类的封装继承多态

举报
子都爱学习 发表于 2021/11/14 19:37:03 2021/11/14
【摘要】 成员修饰符python的类中只有私有成员和公有成员两种,不像c++中的类有公有成员(public),私有成员(private)和保护成员(protected).并且python中没有关键字去修饰成员,默认python中所有的成员都是公有成员,但是私有成员是以两个下划线开头的名字标示私有成员,私有成员不允许直接访问,只能通过内部方法去访问,私有成员也不允许被继承。我们定义一个类,类外获取私有属...

成员修饰符
python的类中只有私有成员和公有成员两种,不像c++中的类有公有成员(public),私有成员(private)和保护成员(protected).并且python中没有关键字去修饰成员,默认python中所有的成员都是公有成员,但是私有成员是以两个下划线开头的名字标示私有成员,私有成员不允许直接访问,只能通过内部方法去访问,私有成员也不允许被继承。
我们定义一个类,类外获取私有属性时会报错 :AttributeError错误


class Person:
    def __init__(self, name):
        self.__name = name

tom = Person('tom')
tom.__name
# AttributeError: 'Person' object has no attribute '__name'

打印下下tom实例的字典,发现__name变成了  _类名+私有方法

tom.__dict__
{'_Person__name': 'tom'}
tom._Person__name
# tom

总结: 类的公有成员定义:self.变量名;受保护的成员:_变量名,只是一个区分标识,和公有成员没有区别,当我们需要默认遵守protected的规则;私有属性:__变量名,不能在类外访问(实际上只是改名成了_类名+私有方法,我们可以通过这个访问,但是建议不要)

下面的例子告诉我们如何访问受保护的成员和私有成员

class Person1:
    def __init__(self, name):
        self.__name = name

    @property
    def name(self):
        return self.__name

    @name.setter
    def name(self,newname):
        self.__name = newname

    @name.deleter
    def name(self):
        del self.__name

也等同于

class Person2:
    def __init__(self, name):
        self.__name = name

    def get_name(self):
        return self.__name

    def set_name(self, name):
        self.__name = name

    def del_name(self):
        del self.__name

    name = property(get_name,set_name,del_name)

# 两种访问方式
tom = Person2('tom')
tom.name
tom.get_name()

继承

class Animal(object):
# python 默认是新式类,继承object, object是类型,根基类
# 等价于 class Animal:
    def __init__(self, name):
        self.__name = name

    @property
    def name(self):
        return self.__name

    def shout(self):
        print('my name is {}'.format(self.name))

class mao():pass
class Dog(Animal, mao): pass

dog = Dog('wangwang')
print(dog.shout, dog.__dict__, Animal.__dict__)
print(Animal.__bases__)   # 类的基类元祖
print(Animal.__mro__)     # 显示类的查找顺序的元祖
print(Dog.mro())          # 显示类的查找顺序的列表
print(Animal.__subclasses__()) # 显示子类的列表

# <bound method Animal.shout of <__main__.Dog object at 0x0000021DBCAD7FD0>> {'_Animal__name': 'wangwang'} {'__module__': '__main__', '__init__': <function Animal.__init__ at 0x0000021DBCAEC280>, 'name': <property object at 0x0000021DBC8269D0>, 'shout': <function Animal.shout at 0x0000021DBCAEC310>, '__dict__': <attribute '__dict__' of 'Animal' objects>, '__weakref__': <attribute '__weakref__' of 'Animal' objects>, '__doc__': None}
# (<class 'object'>,)
# (<class '__main__.Animal'>, <class 'object'>)
# [<class '__main__.Dog'>, <class '__main__.Animal'>, <class '__main__.mao'>, <class 'object'>]
# [<class '__main__.Dog'>]

父类 也称基类和超类  Animal
子类 也称派生类   Dog
继承可以让子类从父类获取特征(属性和方法)
特征查找顺序 实例的__dict__ ->  类__dict__  ->  父类__dict__(有继承)[父类的查找顺序根据__mro__返回的元祖]  ->  返回atrrbuilderror

覆盖

# 完全覆盖
class Animal:
    def shout(self):
        print("Animal shout")

class Dog(Animal):
    def shout(self):
        print('Dog shout')

# 增强覆盖 override
class Cat(Animal):
    def shout(self):
        self.__class__.__base__.shout(self)      # 通过过去类名来找父类名 调父类的方法

        Animal.shout(self)                       # 已知父类名来调父类方法
        super().shout()                          # super()找到离最近父类的shout方法
        super(Cat, self).shout()                 # super() 等价于 super() -> same as super(__class__, <first argument>)
        super(Cat, self).shout()                 # super(type, obj) -> bound super object; requires isinstance(obj, type)

        print('Cat shot')                        # 方法增强

Dog().shout()
# Dog shout

Cat().shout()
# Animal shout
# Animal shout
# Animal shout
# Animal shout
# Animal shout
# Cat shot

继承与初始化

class Animal:
    def __init__(self, a):
        self.a = a

    def show(self):
        pass


class Cat(Animal):
    def __init__(self, a, b, c):
        # Animal.__init__(self,a)
        super().__init__(a)
        self.b = b
        self.c = c

    def show(self):
        print(self.b, self.c)
        print(self.a)
        super().show()
        print(self.__class__.__base__.__dict__)

cat = Cat(1, 2, 3)
cat.show()
print(cat.__dict__)
print(isinstance(cat, Animal))
# 2 3
# 1
# {'__module__': '__main__', '__init__': <function Animal.__init__ at 0x000001CCFA01C280>, 'show': <function Animal.show at 0x000001CCFA01C1F0>, '__dict__': <attribute '__dict__' of 'Animal' objects>, '__weakref__': <attribute '__weakref__' of 'Animal' objects>, '__doc__': None}
# {'a': 1, 'b': 2, 'c': 3}
# True

总结:
如果在子类中覆盖了父类的__init__方法,那么在子类的__init__中,应该显式的调用父类的__init__方法
尽量早调用,调用方式: super().__init__()

单继承:继承列表只有一个类
多继承:继承列表有多个类, mro算法
抽象基类,在其他面对对象语言中,不可实例化,但python可以,但是默认遵守习惯不初始化

class Animal:
    def shout(self):
        raise NotImplementedError("我是(抽象基类)父类,方法在子类实现")

 OCP原则: 多用继承,少修改。对扩展开放,对修改封闭
继承用途: 在子类实现基类的增强,实现多态
多态前提:继承,覆盖

Mixin类

class Document:                  # 第三方库,不允许修改
    def __init__(self, content):
        self.content = content

class Word(Document): pass       # 第三方库,不允许修改
class Pdf(Document): pass        # 第三方库,不允许修改


class PrintableMixin:
    def print(self):
        print(self.content, "Mixin")

class PrintableWorld(PrintableMixin, Word): pass

print(PrintableWorld.__bases__)    # 打印父类
print(PrintableWorld.mro())        # 打印类查找顺序
# (<class '__main__.PrintableMixin'>, <class '__main__.Word'>)
# [<class '__main__.PrintableWorld'>, <class '__main__.PrintableMixin'>, <class '__main__.Word'>, <class '__main__.Document'>, <class 'object'>]

Mixin类体现的是一种组合的设计模式:多组合,多继承
使用Mixin类应该放在继承列表的第一位,做到尽量覆盖和增加
Mixin类设计原则:
Mixin类不应该显示的出现__init__初始化方法
Mixin类仅实现单一功能,通常不能独立工作
Mixin类是类,也可以继承,其祖先类应该也是Mixin类

Mixin类和装饰器都可以实现对类的增强
如果还需要继承,则需要使用Mixin类

特殊成员
1.__init__

__init__方法可以简单的理解为类的构造方法(实际并不是构造方法,只是在类生成对象之后就会被执行),之前已经在上一篇博客中说明过了。

2.__del__

__del__方法是类中的析构方法,当对象消亡的时候(被解释器的垃圾回收的时候会执行这个方法)这个方法默认是不需要写的,不写的时候,默认是不做任何操作的。因为你不知道对象是在什么时候被垃圾回收掉,所以,除非你确实要在这里面做某些操作,不然不要自定义这个方法。

3.__call__

__call__方法在类的对象被执行的时候(obj()或者 类()())会执行。

4.__int__

__int__方法,在对象被int()包裹的时候会被执行,例如int(obj)如果obj对象没有、__int__方法,那么就会报错。在这个方法中返回的值被传递到int类型中进行转换。

5.__str__

__str__方法和int方法一样,当对象被str(obj)包裹的时候,如果对象中没有这个方法将会报错,如果有这个方法,str()将接收这个方法返回的值在转换成字符串。

6.__add__

__add__方法在两个对象相加的时候,调用第一个对象的__add__方法,将第二个对象传递进来,至于怎么处理以及返回值,那是程序员自定义的,就如下面的例子:

class abc:
    def __init__(self,age):
        self.age=age
    def __add__(self,obj):
        return self.age+obj.age
a1=abc(18)
a2=abc(20)
print(a1+a2)
#执行结果:38


7.__dict__

__dict__方法在类里面有,在对象里面也有,这个方法是以字典的形式列出类或对象中的所有成员。就像下面的例子:

class abc:
    def __init__(self,age):
        self.age=age
    def __add__(self,obj):
        return self.age+obj.age
a1=abc(18)
print(abc.__dict__)
print(a1.__dict__)
#执行结果:
{'__add__': <function abc.__add__ at 0x0000020666C9E2F0>, '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'abc' objects>, '__init__': <function abc.__init__ at 0x0000020666C9E268>, '__doc__': None, '__dict__': <attribute '__dict__' of 'abc' objects>}
{'age': 18}


8.__getitem__ __setitem__ __delitem__

__getitem__方法匹配 对象[索引] 这种方式,__setitem__匹配 对象[索引]=value 这种方式,__delitem__匹配 del 对象[索引] 这种方式,例子如下:

class Foo:
    def __init__(self,name,age):
        self.name=name
        self.age=age
    def __getitem__(self, item):  # 匹配:对象[item]这种形式
        return item+10
    def __setitem__(self, key, value):  # 匹配:对象[key]=value这种形式
        print(key,value)
    def __delitem__(self, key):  # 匹配:del 对象[key]这种形式
        print(key)
 
li=Foo("alex",18)
print(li[10])
li[10]=100
del li[10]
执行结果:
20
10 100
10


9.__getslice__ __setslice__ __delslice__

这三种方式在python2.7中还存在,用来对对象进行切片的,但是在python3之后,将这些特殊方法给去掉了,统一使用上面的方式对对象进行切片,因此在使用__getitem__ __setitem__ 这两个方法之前要先判断传递进参数的类型是不是slice对象。例子如下:

class Foo:
    def __init__(self,name,age):
        self.name=name
        self.age=age
        self.li=[1,2,3,4,5,6,7]
    def __getitem__(self, item):  # 匹配:对象[item]这种形式
        if isinstance(item,slice):  # 如果是slice对象,返回切片后的结果
            return self.li[item]  # 返回切片结果
        elif isinstance(item,int):  # 如果是整形,说明是索引
            return item+10
    def __setitem__(self, key, value):  # 匹配:对象[key]=value这种形式
        print(key,value)
    def __delitem__(self, key):  # 匹配:del 对象[key]这种形式
        print(key)
    def __getslice__(self,index1,index2):
        print(index1,index2)
 
li=Foo("alex",18)
print(li[3:5])
#执行结果:
[4, 5]


10.__iter__

类的对象如果想要变成一个可迭代对象,那么对象中必须要有__iter__方法,并且这个方法返回的是一个迭代器。

for 循环的对象如果是一个可迭代的对象,那么会先执行对象中的__iter__方法,获取到迭代器,然后再执行迭代器中的__next__方法获取数据。如果for循环的是一个迭代器,那么直接执行迭代器中的__next__方法。

class Foo:
    def __init__(self,name,age):
        self.name=name
        self.age=age
    def __iter__(self):
        return iter([1,2,3,4,5])  # 返回的是一个迭代器
li=Foo("alex",18)
 
# 1.如果类中有__iter__方法,他的对象就是可迭代对象
# 2.对象.__iter()的返回值是一个迭代器
# 3.for循环的如果是迭代器,直接执行.next方法
# 4.for循环的如果是可迭代对象,先执行对象.__iter(),获取迭代器再执行next
 
for i in li:
    print(i)
#执行结果:
1
2
3
4
5


11.isinstance和issubclass

之前讲过isinstance可以判断一个变量是否是某一种数据类型,其实,isinstance不只可以判断数据类型,也可以判断对象是否是这个类的对象或者是这个类的子类的对象,代码如下:

class Foo:
    def __init__(self,name,age):
        self.name=name
        self.age=age
class Son(Foo):
    pass
obj=Son("xiaoming",18)
print(isinstance(obj,Foo))
执行结果:True


issubclass用来判断一个类是否是某个类的子类,返回的是一个bool类型数据,代码如下:

class Foo:
    def __init__(self,name,age):
        self.name=name
        self.age=age
class Son(Foo):
    pass
obj=Son("xiaoming",18)
print(issubclass(Son,Foo))
执行结果:True


3、类与对象
__new__和__metaclass__

在python中,一切皆对象,我们定义的类其实。。。也是一个对象,那么,类本身是谁的对象呢?在python2.2之前(或者叫经典类中),所有的类,都是class的对象,但是在新式类中,为了将类型(int,str,float等)和类统一,所以,所有的类都是type类型的对象。当然,这个规则可以被修改,在类中有一个属性 __metaclass__ 可以指定当前类该由哪个类进行实例化。而创建对象过程中,其实构造器不是__init__方法,而是__new__方法,这个方法会返回一个对象,这才是对象的构造器。下面是一个解释类实例化对象内部实现过程的代码段:

class Mytype(type):
    def __init__(self, what, bases=None, dict=None):
        super(Mytype,self).__init__(what, bases, dict)
    def __call__(self, *args, **kwargs):
        obj=self.__new__(self)
        self.__init__(obj, *args, **kwargs)
        return obj
class Foo:
    __metaclass__=Mytype
    def __init__(self,name,age):
        self.name=name
        self.age=age
    def __new__(cls, *args, **kwargs):
        return object.__new__(cls)
obj=Foo("xiaoming",18)
print(obj.name,obj.age)
执行结果:xiaoming 18


4、异常处理
python中使用try except finally组合来实现异常扑捉,不像java中是使用try catch finally......其中,except中的Exception是所有异常的父类,下面是一个异常处理的示例:

try:
    int("aaa")  #可能出现异常的代码
except IndexError as e:  # 捕捉索引异常的子异常,注意,这里的as e在老版本的py中可以写成,e但是新版本中用as e,",e"未来可能会淘汰
    print("IndexError:",e)
except ValueError as e:  # 捕捉value错误的子异常
    print("ValueError:",e)
except Exception as e:  # 如果上面两个异常没有捕获到,那么使用Exception捕获,Exception能够捕获所有的异常
    print("Exception:",e)
else:  # 如果没有异常发生,执行else中的代码块
    print("true")
finally:  # 不管是否发生异常,在最后都会执行finally中的代码,假如try里面的代码正常执行,先执行else中的代码,再执行finally中的代码
    print("finally")
执行结果:
ValueError: invalid literal for int() with base 10: 'aaa'
finally


那么既然Exception是所有异常的父类,我们可以自已定义Exception的子类,实现自定义异常处理,下面就是实现例子:

class OldBoyError(Exception):  # 自定义错误类型
    def __init__(self,message):
        self.message=message
    def __str__(self):  # 打印异常的时候会调用对象里面的__str__方法返回一个字符串
        return self.message
try:
    raise OldBoyError("我错了...")  # raise是主动抛出异常,可以调用自定义的异常抛出异常
except OldBoyError as e:
    print(e)
执行结果:我错了...


异常处理里面还有一个断言,一般用在判断执行环境上面,只要断言后面的条件不满足,那么就抛出异常,并且后面的代码不执行了。

print(123)
assert 1==2  # 断言,故意抛出异常,做环境监测用,环境监测不通过,报错并结束程序
print("456")
执行结果:
    assert 1==2  # 断言,故意抛出异常,做环境监测用,环境监测不通过,报错并结束程序
123
AssertionError



5、反射/自省
python中的反射/自省的实现,是通过hasattr、getattr、setattr、delattr四个内置函数实现的,其实这四个内置函数不只可以用在类和对象中,也可以用在模块等其他地方,只是在类和对象中用的很多,所以单独提出来进行解释。

hasattr(key)返回的是一个bool值,判断某个成员或者属性在不在类或者对象中
getattr(key,default=xxx)获取类或者对象的成员或属性,如果不存在,则会抛出AttributeError异常,如果定义了default那么当没有属性的时候会返回默认值。
setattr(key,value)假如有这个属性,那么更新这个属性,如果没有就添加这个属性并赋值value
delattr(key)删除某个属性
注意,上面的key都是字符串,而不是变量,也就是说可以通过字符串处理类中的成员或者对象中的属性。下面是一个例子代码:

class Foo:
    def __init__(self,name,age):
        self.name=name
        self.age=age
    def show(self):
        return self.name,self.age
obj=Foo("xiaoming",18)
print(getattr(obj,"name"))
setattr(obj,"k1","v1")
print(obj.k1)
print(hasattr(obj,"k1"))
delattr(obj,"k1")
show_fun=getattr(obj,"show")
print(show_fun())
执行结果:
xiaoming
v1
True
('xiaoming', 18)


反射/自省能够直接访问以及修改运行中的类和对象的成员和属性,这是一个很强大的功能,并且并不像java中效率很低,所以用的很多。

下面是一个反射/自省用在模块级别的例子:

import s2
operation=input("请输入URL:")
if operation in s2.__dict__:
    getattr(s2,operation)()
else:
    print("404")
 
#模块s2中的代码:
def f1():
    print("首页")
def f2():
    print("新闻")
def f3():
    print("精选")
执行结果:
请输入URL:f1
首页


6、单例模式
这里介绍一个设计模式,设计模式在程序员写了两三年代码的时候,到一定境界了,才会考虑到设计模式对于程序带来的好处,从而使用各种设计模式,这里只是简单的介绍一个简单的设计模式:单例模式。在面向对象中的单例模式就是一个类只有一个对象,所有的操作都通过这个对象来完成,这就是面向对象中的单例模式,下面是实现代码:

class Foo:  # 单例模式
    __v=None
    @classmethod
    def ge_instance(cls):
        if cls.__v:
            return cls.__v
        else:
            cls.__v=Foo()
            return cls.__v
obj1=Foo.ge_instance()
print(obj1)
obj2=Foo.ge_instance()
print(obj2)
obj3=Foo.ge_instance()
print(obj3)
执行结果:
<__main__.Foo object at 0x000001D2ABA01860>
<__main__.Foo object at 0x000001D2ABA01860>
<__main__.Foo object at 0x000001D2ABA01860>



【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。