python学习之面向对象

python学习之面向对象

类定义

与python的函数定义类似,只不过把def关键字替换为class

class calculator():
    pass
a=calculator()
print(type(a))

输出

<class 'main.calculator'>

构造函数

与php的定义类似,只不过把__construct替换成__init__

class calculator():
    def __init__(self,x,y):
        self.x=x
        self.y=y
a=calculator(1,2)
print(a.x)
print(a.y)

输出

1
2

这里需要注意的是self,这里的self与C++中的对象指针或者JAVA中的对象引用是一样的,用途都是类作用域内提供对当前类对象的引用,但写法上有很大区别。在python中定义类的时候,类的所有函数,包括构造函数,首个参数都必须为self。

继承

python中继承的写法同样简单,在类定义的括号里加入父类的类名就可以了。

class game():
    def play(self):
        print("start play a game")
class RPG(game):
    def chooseACareer(self):
        print("choose your career")
class FPS(game):
    def shoot(self):
        print("shoot enemy")
game=game()
rpgGame=RPG()
fpsGame=FPS()
game.play()
rpgGame.play()
rpgGame.chooseACareer()
fpsGame.play()
fpsGame.shoot()

输出

start play a game
start play a game
choose your career
start play a game
shoot enemy
请按任意键继续. . .

重写

可以通过在子类中定义重名函数对父类函数进行重写

class game():
    def play(self):
        print("start play a game")
class RPG(game):
    def play(self):
        print("start play a RPG game")
    def chooseACareer(self):
        print("choose your career")
class FPS(game):
    def play(self):
        print("start play a FPS game")
    def shoot(self):
        print("shoot enemy")
game=game()
rpgGame=RPG()
fpsGame=FPS()
game.play()
rpgGame.play()
rpgGame.chooseACareer()
fpsGame.play()
fpsGame.shoot()

输出

start play a game
start play a RPG game
choose your career
start play a FPS game
shoot enemy
请按任意键继续. . .

父类句柄

可以通过调用super()获取父类句柄进而执行父类函数

class game():
    def play(self):
        print("start play a game")
class RPG(game):
    def play(self):
        super().play()
        print("start play a RPG game")
    def chooseACareer(self):
        print("choose your career")
class FPS(game):
    def play(self):
        super().play()
        print("start play a FPS game")
    def shoot(self):
        print("shoot enemy")
game=game()
rpgGame=RPG()
fpsGame=FPS()
game.play()
rpgGame.play()
rpgGame.chooseACareer()
fpsGame.play()
fpsGame.shoot()

输出

start play a game
start play a game
start play a RPG game
choose your career
start play a game
start play a FPS game
shoot enemy
请按任意键继续. . .

多继承

如果不是我搞错了的话,这个特性应该是我目前所知的所有编程语言中的独一份,无论是C++,JAVA还是PHP,他们统统是不支持多继承的,如果要给一个类附加多种特性,唯一的选择就是在单继承的基础上结合多个接口,从继承关系树来看这么做也是有必要的,如果一个类有多个父类,那类之间的继承关系将相当复杂,而且互相之间的重名问题也将难以理清。但是,python是支持的,在没有对这个特性更多深入理解的情况下不便做更多阐述,在这里我们先知道python的这一特性便可。

class game():
    def play(self):
        print("start play a game")
class RPG(game):
    def play(self):
        super().play()
        print("start play a RPG game")
    def chooseACareer(self):
        print("choose your career")
class FPS(game):
    def play(self):
        super().play()
        print("start play a FPS game")
    def shoot(self):
        print("shoot enemy")
class RPGandFPSGame(RPG,FPS):
    pass
xgame=RPGandFPSGame()
xgame.play()
xgame.chooseACareer()
xgame.shoot()

输出

start play a game
start play a FPS game
start play a RPG game
choose your career
shoot enemy
请按任意键继续. . .

这个代码示例的输出相当具有迷惑性,看起来xgame的play方法同时兼具两个父类的play方法的效果,但也不是先后执行的样子,因为game的play方法只执行了一次。在经过调试后可以发现,实际上现在的类继承级别变成了这样:RPGandFPSGame=>RPG=>FPS=>game,有兴趣的可以对6、12行代码注释后调试代码进行验证。

对象的类型判断

与JAVA类似,python也支持对对象的类型判断

class game():
    def play(self):
        print("start play a game")
class RPG(game):
    def play(self):
        super().play()
        print("start play a RPG game")
    def chooseACareer(self):
        print("choose your career")
class FPS(game):
    def play(self):
        super().play()
        print("start play a FPS game")
    def shoot(self):
        print("shoot enemy")
class RPGandFPSGame(RPG,FPS):
    pass
xgame=RPGandFPSGame()
xgame.play()
xgame.chooseACareer()
xgame.shoot()
print(isinstance(xgame,game))
print(isinstance(xgame,RPG))
print(isinstance(xgame,FPS))
print(isinstance(xgame,RPGandFPSGame))

输出

start play a game
start play a FPS game
start play a RPG game
choose your career
shoot enemy
True
True
True
True
请按任意键继续. . .

访问修饰符

在其它高级语言中,访问修饰符绝对是面向对象内容中的重点之一,会衍生出众多问题。但是,python中完全没有!对,python的所有对象属性的访问权限都是public,只不过存在所谓的伪私有属性这种情况。

class ClassAcess():
    def __init__(self):
        self.publicMember="public member"
        self.__privateMember="private member"
a=ClassAcess()
print(a.publicMember)
print(a.__privateMember)

输出

public member
Traceback (most recent call last):
File "D:workspacepython est.py", line 7, in
print(a.__privateMember)
AttributeError: 'ClassAcess' object has no attribute '__privateMember'
请按任意键继续. . .

可以看到,对类对象命名时加上双下划线就可以起到类似访问限定符private的作用

class ClassAcess():
    def __init__(self):
        self.publicMember="public member"
        self.__privateMember="private member"
#a=ClassAcess()
#print(a.publicMember)
#print(a.__privateMember)
class SubClassAcess(ClassAcess):
    def printParentPrivateMember(self):
        print(super().__privateMember())
b=SubClassAcess()
b.printParentPrivateMember()

输出

Traceback (most recent call last):
File "D:workspacepython est.py", line 12, in
b.printParentPrivateMember()
File "D:workspacepython est.py", line 10, in printParentPrivateMember
print(super().__privateMember())
AttributeError: 'super' object has no attribute '_SubClassAcess__privateMember'
请按任意键继续. . .

可以看出子类也是不能访问的,的确起到了和private限定符类似的作用。

class ClassAcess():
    def __init__(self):
        self.publicMember="public member"
        self.__privateMember="private member"
a=ClassAcess()
print(a.publicMember)
print(a._ClassAcess__privateMember)
class SubClassAcess(ClassAcess):
    def printParentPrivateMember(self):
        print(super()._ClassAcess__privateMember)
b=SubClassAcess()
b.printParentPrivateMember()

输出

public member
private member
Traceback (most recent call last):
File "D:workspacepython est.py", line 12, in
b.printParentPrivateMember()
File "D:workspacepython est.py", line 10, in printParentPrivateMember
print(super()._ClassAcess__privateMember)
AttributeError: 'super' object has no attribute '_ClassAcess__privateMember'
请按任意键继续. . .

可以看出,在访问时候加上_类名就可以直接访问了,但是子类依然不能访问。所以说python中的访问限定只是一种伪私有方式,是一种约定俗成的类对象命名规则。其实质上是通过替换类属性的访问时名称来实现对类属性的访问保护。更多的介绍可以阅读这里

总结一下类属性的特殊命名

  • __memberName等效于private

  • _memberName等效于protected

  • __memberName__属于系统特殊定义,也称作魔术方法,比如构造函数__init__

访问器

在理解python的访问限定符的基础上,在python中使用访问器也就是getter和setter来访问私有成员也同样是一种受推荐的编程习惯。

class ClassAcess():
    def __init__(self):
        self.publicMember="public member"
        self.__privateMember="private member"
    def getPrivateMember(self):
        return self.__privateMember
    def setPrivateMember(self, newPrivateMember):
        self.__privateMember=newPrivateMember
a=ClassAcess()
print(a.getPrivateMember())
a.setPrivateMember("new private member")
print(a.getPrivateMember())

输出

private member
new private member
请按任意键继续. . .

动态类

type关键字不仅可以用来检测变量类型,还可以用于动态构建类。

ClassAcess2=type("ClassAcess2",(object,),{"publicMember":"public member","__privateMember":"private member","getPrivateMember":lambda self : self.__privateMember})
b=ClassAcess2()
print(b.getPrivateMember())

输出

private member
请按任意键继续. . .

在构建类时,type接受三个参数,第一个是类名,第二个是父类,第三个是属性和方法,其中方法用匿名函数的形式构建。

在这个动态构建的示例中并没有实现setter访问器,因为发现会报错,原因目前还没有找到,猜测是匿名函数lambda args : expression中的expression并不能执行赋值语句。

关于匿名函数的更多用法请阅读这里

运算符重载

python中的类如果C++中一样,可以通过运算符重载实现一些如同魔法的效果。

class MyClass():
    def __call__(self):
        print("MyClass function call is called")
a=MyClass()
a()
a.__call__()

输出

MyClass function call is called
MyClass function call is called
请按任意键继续. . .

上边的代码是对()运算符的重载,效果是在该类的实例通过instance()方式被调用时触发。

关于__call__的重载相关可以阅读这里

class MyClass():
    def __init__ (self,x):
        self.x=x
    def __add__(self,other):
        return MyClass(self.x+other.x)
a=MyClass(1)
b=MyClass(2)
c=a+b
print(c.x)

输出

3
请按任意键继续. . .

上边是对+运算符的重载。

类的构建过程

因为一直都是参照简明教程并对比其它语言面向对象的知识,不免对python的整个面向对象构建机制有所疑惑。

class MyClass():
    pass
a=MyClass()
print(type(a))
print(type(MyClass))

输出

<class 'main.MyClass'>
<class 'type'>
请按任意键继续. . .

可以看出用户自定义类其实是type的实例,而type有点像是JAVA中的Class,承担对用户自定义类的映射作用,在python中它还承担类定义的创建。

现在我们已经知道了类的动态创建和运算符重载,我们要思考一下一个对象创建时候发生了什么。

在类动态创建时,我们通过newCls=type(clsName,*parents,**members)的方式创建了一个类。结合运算符重载,那我们完全可以构建一个自己的类创建器,和type实现相同的效果。

class MyTypeClass():
    def __call__(self,clsName,parents,members):
        return type(clsName,parents,members)
myType=MyTypeClass()
Test=myType("Test",(),{"name":"is a test cls instance"})
print(Test.name)

输出

is a test cls instance
请按任意键继续. . .

可以看到,我们构建了一个叫做myType的可执行对象,其效果等同于type。现在我们可以猜想python是用了类似的过程创建用户自定义类,比如,先构建一个type实例作为类创建器,然后读取类定义中的类名、基类、成员等信息,然后调用type的类定义(这里就简单称作type的模板)中的__call__重载创建类定义。也就是说,当定义了一个类def cls():后,就会触发type模板的__call__重载。

有了上边的概念后,我们可以进一步理解metaclass。

metaclass也叫元类,其实可以理解为自定义的type,就好比上边示例中的MyTypeClass,它提供了一种可以快速修改类行为的途径。

class MyMetaClass(type):
    def __call__(self):
        print("MyMetaClass.__call__()")
        print(self)
class Test(metaclass=MyMetaClass):
    pass
test=Test()

输出

MyMetaClass.call()
<class 'main.Test'>
请按任意键继续. . .

可以看到MyMetaClass起到了替代type的作用,通过接管type的工作来改变类行为。

需要注意的是此类用途极为罕见,与其说会用到日常工作中,不如说更有助于我们对python机制的理解,我们需要谨记不能为了使用某种特性而使用,所有特性都是为了实现功能服务。

更多metaclass的内容可以阅读这里

魔术方法

python中可以通过dir()获取类的属性和方法。

print(dir(str))

输出

['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isascii', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'removeprefix', 'removesuffix', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']
请按任意键继续. . .

其中__add__之类的方法叫做魔术方法,前边有提到过。魔术方法普遍作用于对象实例,比如__new____init__用于实例的创建和初始化,__call__可以将实例变为可执行对象,__add__可用于运算符+的重载。让我们再看两个例子:

str1="abc"
str2="abc"
print(str1.__len__())
print(len(str1))
print(str1.__eq__(str2))
print(str1==str2)

输出

3
3
True
True
请按任意键继续. . .

可以看出,这些魔术方法类似C++中的运算符重载,而且用途更广,更像是java中的一些接口,比如comparable之类的。他们都在语言层面提供了对象的一些常规操作,把这些常规操作封装后在调用时更具统一性。进而给用户的自定义类提供了借鉴的途径。这里举一个例子:

a=[1,2,3]
print(a)
print(a[1])
class MyList():
    def __init__(self,listA):
        if not isinstance(listA,list):
            print(listA.__str__()+"is not a list")
            return
        self.listA = listA
    def __str__(self):
        return "this is a list:"+str(self.listA)
    def __getitem__(self,n):
        return "index : "+str(n)+", value : "+str(self.listA[n])
b=MyList(a)
print(b)
print(b[1])

输出

[1, 2, 3]
2
this is a list:[1, 2, 3]
index : 1, value : 2
请按任意键继续. . .

上边的例子实现了一个自定义的MyList类,并且通过魔术方法实现了类似JAVA中toStringiterator接口。从这个例子看,python用更简洁的代码实现了JAVA需要实现诸多接口的代码,但这也不能说孰优孰劣,毕竟在可读性和功能上各有优缺点,但这的确是python的特点。

这里列举一些常用的魔术方法

  • __init__ : 构造函数,在生成对象时调用
  • __del__ : 析构函数,释放对象时使用
  • __repr__ : 打印,转换
  • __setitem__ : 按照索引赋值
  • __getitem__: 按照索引获取值
  • __len__: 获得长度
  • __cmp__: 比较运算
  • __call__: 函数调用
  • __add__: 加运算
  • __sub__: 减运算
  • __mul__: 乘运算
  • __truediv__: 除运算
  • __mod__: 求余运算
  • __pow__: 乘方

本文仅是初学python的一些感悟,还有诸多不足,待以后理解深入后补足。

参考资料

https://www.runoob.com/python3/python3-class.html

https://www.jmjc.tech/tutorial/python/sub-2

本篇文章首发自魔芋红茶的博客https://www.cnblogs.com/Moon-Face/ 请尊重其他人的劳动成功,转载请注明。
原文地址:https://www.cnblogs.com/Moon-Face/p/14455323.html