Python 类中,凡是以双下划线 "__"
开头和结尾命名的成员(属性和方法),都被称为类的特殊成员(特殊属性和特殊方法)。例如,类的 __init__(self)
构造方法就是典型的特殊方法。
Python 类中的特殊成员,其特殊性类似 C++ 类的 private 私有成员,即不能在类的外部直接调用,但允许借助类中的普通方法调用甚至修改它们。如果需要,还可以对类的特殊方法进行重写,从而实现一些特殊的功能。
总结:
- __new__(),先于init初始化使用
- __repr__(),__str__()重写:repr更为底层,实例化对象时,str在打印实例时显示str返回,repr则不管打印还是直接实例(交互式)都显示返回的str
- __del__()重写,python垃圾回收机制,ARC-自动引用计数器,当没有对象引用时,计数器为0时,执行该方法
- dir(),instance.__dir__(),返回类的方法和实例属性,dir(class)返回类的方法和实例属性并全部排序
- instance.__dict__返回的是实例属性,class.__dict__返回类的属性方法,
- __call__(),重写,实例()就可以类似于函数一样,实现__call__方法, instance() 等价于instance.__cal__(),类实例的方法也可以调用,类实例的属性不能调用
- hasattr(obj, name),判断某个类实例对象是否包含指定名称的属性或方法
- getattr(obj, name[, default]),获取某个类实例对象中指定属性的值
- setattr(obj, name, value),基础的功能是修改类实例对象中的属性值,还可以实现为实例对象动态添加属性或者方法
- issubclass(cls, class_or_tuple):检查 cls 是否为后一个类或元组包含的多个类中任意类的子类
- isinstance(obj, class_or_tuple):检查 obj 是否为后一个类或元组包含的多个类中任意类的对象
- __bases__,可以查看该类的所有直接父类
- __subclasses__(),查看该类的所有直接子类
1.__new__()
__new__()
是一种负责创建类实例的静态方法,它无需使用 staticmethod
装饰器修饰,且该方法会优先 __init__()
初始化方法被调用。
一般情况下,覆写 __new__()
的实现将会使用合适的参数调用其超类的 super().__new__()
,并在返回之前修改实例。例如:
class demoClass:
instances_created = 0
def __new__(cls,*args,**kwargs):
print("__new__():",cls,args,kwargs)
instance = super().__new__(cls)
instance.number = cls.instances_created
cls.instances_created += 1
return instance
def __init__(self,attribute):
print("__init__():",self,attribute)
self.attribute = attribute
test1 = demoClass("abc")
test2 = demoClass("xyz")
print(test1.number,test1.instances_created)
print(test2.number,test2.instances_created)
输出结果为:
__new__(): <class '__main__.demoClass'> ('abc',) {}
__init__(): <__main__.demoClass object at 0x0000026FC0DF8080> abc
__new__(): <class '__main__.demoClass'> ('xyz',) {}
__init__(): <__main__.demoClass object at 0x0000026FC0DED358> xyz
0 2
1 2
__new__()
通常会返回该类的一个实例,但有时也可能会返回其他类的实例,如果发生了这种情况,则会跳过对 __init__()
方法的调用。而在某些情况下(比如需要修改不可变类实例(Python 的某些内置类型)的创建行为),利用这一点会事半功倍。比如:
class nonZero(int):
def __new__(cls,value):
return super().__new__(cls,value) if value != 0 else None
def __init__(self,skipped_value):
#此例中会跳过此方法
print("__init__()")
super().__init__()
print(type(nonZero(-12)))
print(type(nonZero(0)))
运行结果为:
__init__()
<class '__main__.nonZero'>
<class 'NoneType'>
那么,什么情况下使用 __new__()
呢?答案很简单,在 __init__()
不够用的时候。
例如,前面例子中对 Python 不可变的内置类型(如 int、str、float 等)进行了子类化,这是因为一旦创建了这样不可变的对象实例,就无法在 __init__()
方法中对其进行修改。
有些读者可能会认为,__new__()
对执行重要的对象初始化很有用,如果用户忘记使用 super()
,可能会漏掉这一初始化。虽然这听上去很合理,但有一个主要的缺点,即如果使用这样的方法,那么即便初始化过程已经是预期的行为,程序员明确跳过初始化步骤也会变得更加困难。不仅如此,它还破坏了__init__()
中执行所有初始化工作”的潜规则。
注意,由于 __new__()
不限于返回同一个类的实例,所以很容易被滥用,不负责任地使用这种方法可能会对代码有害,所以要谨慎使用。一般来说,对于特定问题,最好搜索其他可用的解决方案,最好不要影响对象的创建过程,使其违背程序员的预期。比如说,前面提到的覆写不可变类型初始化的例子,完全可以用工厂方法(一种设计模式)来替代。
2.__repr__()
及__str__()
: 显示属性
通常情况下,直接输出某个实例化对象,本意往往是想了解该对象的基本信息,例如该对象有哪些属性,它们的值各是多少等等。但默认情况下,我们得到的信息只会是“类名+object at+内存地址”,对我们了解该实例化对象帮助不大。
那么,有没有可能自定义输出实例化对象时的信息呢?答案是肯定,通过重写类的 __repr__()
方法即可。事实上,当我们输出某个实例化对象时,其调用的就是该对象的 __repr__()
方法,输出的是该方法的返回值。
以本节开头的程序为例,执行 print(clangs) 等同于执行 print(clangs.__repr__())
,程序的输出结果是一样的(输出的内存地址可能不同)。
和 __init__(self)
的性质一样,Python 中的每个类都包含 __repr__()
方法,因为 object 类包含 __reper__()
方法,而 Python 中所有的类都直接或间接继承自 object 类。
默认情况下,__repr__()
会返回和调用者有关的 “类名+object at+内存地址”
信息。当然,我们还可以通过在类中重写这个方法,从而实现当输出实例化对象时,输出我们想要的信息。
举个例子:
class CLanguage:
def __init__(self):
self.name = "C语言中文网"
self.add = "http://c.biancheng.net"
def __repr__(self):
return "CLanguage[name="+ self.name +",add=" + self.add +"]"
clangs = CLanguage()
print(clangs)
程序运行结果为:
CLanguage[name=C语言中文网,add=http://c.biancheng.net]
由此可见,__repr__()
方法是类的实例化对象用来做“自我介绍”的方法,默认情况下,它会返回当前对象的“类名+object at+内存地址”,而如果对该方法进行重写,可以为其制作自定义的自我描述信息。
__repr__
和__str__
这两个方法都是用于显示的,__str__
是面向用户的,而__repr__
面向程序员。
打印操作会首先尝试__str__和str内置函数(print运行的内部等价形式),它通常应该返回一个友好的显示。
__repr__
用于所有其他的环境中:用于交互模式下提示回应以及repr
函数,如果没有使用__str__
,会使用print和str。它通常应该返回一个编码字符串,可以用来重新创建对象,或者给开发者详细的显示。
当我们想所有环境下都统一显示的话,可以重构__repr__
方法;当我们想在不同环境下支持不同的显示,例如终端用户显示使用__str__
,而程序员在开发期间则使用底层的__repr__
来显示,实际上__str__
只是覆盖了__repr__
以得到更友好的用户显示。
case:
class Test(object):
def __init__(self, value='hello, world!'):
self.data = value
>>> t = Test()
>>> t
<__main__.Test at 0x7fa91c307190>
>>> print t
<__main__.Test object at 0x7fa91c307190>
# 看到了么?上面打印类对象并不是很友好,显示的是对象的内存地址
# 下面我们重构下该类的__repr__以及__str__,看看它们俩有啥区别
# 重构__repr__
class TestRepr(Test):
def __repr__(self):
return 'TestRepr(%s)' % self.data
>>> tr = TestRepr()
>>> tr
TestRepr(hello, world!)
>>> print tr
TestRepr(hello, world!)
# 重构__repr__方法后,不管直接输出对象还是通过print打印的信息都按我们__repr__方法中定义的格式进行显示了
# 重构__str__
class TestStr(Test):
def __str__(self):
return '[Value: %s]' % self.data
>>> ts = TestStr()
>>> ts
<__main__.TestStr at 0x7fa91c314e50>
>>> print ts
[Value: hello, world!]
# 你会发现,直接输出对象ts时并没有按我们__str__方法中定义的格式进行输出,而用print输出的信息却改变了
3.__del__()
__del__()
方法,功能正好和 __init__()
相反,其用来销毁实例化对象。
在编写程序时,如果之前创建的类实例化对象后续不再使用,最好在适当位置手动将其销毁,释放其占用的内存空间(整个过程称为垃圾回收(简称GC))。
大多数情况下,Python 开发者不需要手动进行垃圾回收,因为 Python 有自动的垃圾回收机制(下面会讲),能自动将不需要使用的实例对象进行销毁。
无论是手动销毁,还是 Python 自动帮我们销毁,都会调用 __del__()
方法。举个例子:
class CLanguage:
def __init__(self):
print("调用 __init__() 方法构造对象")
def __del__(self):
print("调用__del__() 销毁对象,释放其空间")
clangs = CLanguage()
del clangs
程序运行结果为:
调用 __init__() 方法构造对象
调用__del__() 销毁对象,释放其空间
千万不要误认为,只要为该实例对象调用 __del__()
方法,该对象所占用的内存空间就会被释放。举个例子:
class CLanguage:
def __init__(self):
print("调用 __init__() 方法构造对象")
def __del__(self):
print("调用__del__() 销毁对象,释放其空间")
clangs = CLanguage()
#添加一个引用clangs对象的实例对象
cl = clangs
del clangs
print("***********")
程序运行结果为:
调用 __init__() 方法构造对象
***********
调用__del__() 销毁对象,释放其空间
注意,最后一行输出信息,是程序执行即将结束时调用 __del__()
方法输出的
可以看到,当程序中有其它变量(比如这里的 cl)引用该实例对象时,即便手动调用__del__()
方法,该方法也不会立即执行。这和 Python 的垃圾回收机制的实现有关。
Python 采用自动引用计数(简称 ARC)的方式实现垃圾回收机制。该方法的核心思想是:每个 Python 对象都会配置一个计数器,初始 Python 实例对象的计数器值都为 0,如果有变量引用该实例对象,其计数器的值会加 1,依次类推;反之,每当一个变量取消对该实例对象的引用,计数器会减 1。如果一个 Python 对象的的计数器值为 0,则表明没有变量引用该 Python 对象,即证明程序不再需要它,此时 Python 就会自动调用 __del__()
方法将其回收。
以上面程序中的 clangs 为例,实际上构建 clangs 实例对象的过程分为 2 步,先使用 CLanguage() 调用该类中的 __init__()
方法构造出一个该类的对象(将其称为 C,计数器为 0),并立即用 clangs 这个变量作为所建实例对象的引用( C 的计数器值 + 1)。在此基础上,又有一个 clang 变量引用 clangs(其实相当于引用 CLanguage(),此时 C 的计数器再 +1 ),这时如果调用del clangs语句,只会导致 C 的计数器减 1(值变为 1),因为 C 的计数器值不为 0,因此 C 不会被销毁(不会执行 __del__()
方法)。
如果在上面程序结尾,添加如下语句:
del cl
print("-----------")
则程序的执行结果为:
调用 __init__() 方法构造对象
***********
调用__del__() 销毁对象,释放其空间
-----------
可以看到,当执行 del cl 语句时,其应用的对象实例对象 C 的计数器继续 -1(变为 0),对于计数器为 0 的实例对象,Python 会自动将其视为垃圾进行回收。
需要额外说明的是,如果我们重写子类的 __del__()
方法(父类为非 object 的类),则必须显式调用父类的 __del__()
方法,这样才能保证在回收子类对象时,其占用的资源(可能包含继承自父类的部分资源)能被彻底释放。为了说明这一点,这里举一个反例:
class CLanguage:
def __del__(self):
print("调用父类 __del__() 方法")
class cl(CLanguage):
def __del__(self):
print("调用子类 __del__() 方法")
c = cl()
del c
程序运行结果为:
调用子类 __del__() 方法
4.__dir__()
dir() 函数,通过此函数可以某个对象拥有的所有的属性名和方法名,该函数会返回一个包含有所有属性名和方法名的有序列表。
举个例子:
class CLanguage:
def __init__ (self,):
self.name = "C语言中文网"
self.add = "http://c.biancheng.net"
def say():
pass
clangs = CLanguage()
print(dir(clangs))
程序运行结果为:
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'add', 'name', 'say']
注意,通过 dir() 函数,不仅仅输出本类中新添加的属性名和方法(最后 3 个),还会输出从父类(这里为 object 类)继承得到的属性名和方法名。
值得一提的是,dir()
函数的内部实现,其实是在调用参数对象 __dir__()
方法的基础上,对该方法返回的属性名和方法名做了排序。
所以,除了使用 dir()
函数,我们完全可以自行调用该对象具有的 __dir__()
方法:
class CLanguage:
def __init__ (self,):
self.name = "C语言中文网"
self.add = "http://c.biancheng.net"
def say():
pass
clangs = CLanguage()
print(clangs.__dir__())
程序运行结果为:
['name', 'add', '__module__', '__init__', 'say', '__dict__', '__weakref__', '__doc__', '__repr__', '__hash__', '__str__', '__getattribute__', '__setattr__', '__delattr__', '__lt__', '__le__', '__eq__', '__ne__', '__gt__', '__ge__', '__new__', '__reduce_ex__', '__reduce__', '__subclasshook__', '__init_subclass__', '__format__', '__sizeof__', '__dir__', '__class__']
显然,使用 __dir__()
方法和 dir()
函数输出的数据是相同,仅仅顺序不同。
5.__dict__
在 Python 类的内部,无论是类属性还是实例属性,都是以字典的形式进行存储的,其中属性名作为键,而值作为该键对应的值。
为了方便用户查看类中包含哪些属性,Python 类提供了 __dict__
属性。需要注意的一点是,该属性可以用类名或者类的实例对象来调用,用类名直接调用 __dict__
,会输出该由类中所有类属性组成的字典;而使用类的实例对象调用 __dict__
,会输出由类中所有实例属性组成的字典。
举个例子:
class CLanguage:
a = 1
b = 2
def __init__ (self):
self.name = "C语言中文网"
self.add = "http://c.biancheng.net"
#通过类名调用__dict__
print(CLanguage.__dict__)
#通过类实例对象调用 __dict__
clangs = CLanguage()
print(clangs.__dict__)
程序输出结果为:
{'__module__': '__main__', 'a': 1, 'b': 2, '__init__': <function CLanguage.__init__ at 0x0000022C69833E18>, '__dict__': <attribute '__dict__' of 'CLanguage' objects>, '__weakref__': <attribute '__weakref__' of 'CLanguage' objects>, '__doc__': None}
{'name': 'C语言中文网', 'add': 'http://c.biancheng.net'}
不仅如此,对于具有继承关系的父类和子类来说,父类有自己的 __dict__
,同样子类也有自己的 __dict__
,它不会包含父类的 __dict__
。例如:
class CLanguage:
a = 1
b = 2
def __init__ (self):
self.name = "C语言中文网"
self.add = "http://c.biancheng.net"
class CL(CLanguage):
c = 1
d = 2
def __init__ (self):
self.na = "Python教程"
self.ad = "http://c.biancheng.net/python"
#父类名调用__dict__
print(CLanguage.__dict__)
#子类名调用__dict__
print(CL.__dict__)
#父类实例对象调用 __dict__
clangs = CLanguage()
print(clangs.__dict__)
#子类实例对象调用 __dict__
cl = CL()
print(cl.__dict__)
运行结果为:
{'__module__': '__main__', 'a': 1, 'b': 2, '__init__': <function CLanguage.__init__ at 0x000001721A853E18>, '__dict__': <attribute '__dict__' of 'CLanguage' objects>, '__weakref__': <attribute '__weakref__' of 'CLanguage' objects>, '__doc__': None}
{'__module__': '__main__', 'c': 1, 'd': 2, '__init__': <function CL.__init__ at 0x000001721CD15510>, '__doc__': None}
{'name': 'C语言中文网', 'add': 'http://c.biancheng.net'}
{'na': 'Python教程', 'ad': 'http://c.biancheng.net/python'}
显然,通过子类直接调用的 __dict__
中,并没有包含父类中的 a 和 b 类属性;同样,通过子类对象调用的 __dict__
,也没有包含父类对象拥有的 name 和 add 实例属性。
除此之外,借助由类实例对象调用 __dict__
属性获取的字典,可以使用字典的方式对其中实例属性的值进行修改,例如:
class CLanguage:
a = "aaa"
b = 2
def __init__ (self):
self.name = "C语言中文网"
self.add = "http://c.biancheng.net"
#通过类实例对象调用 __dict__
clangs = CLanguage()
print(clangs.__dict__)
clangs.__dict__['name'] = "Python教程"
print(clangs.name)
程序运行结果为:
{'name': 'C语言中文网', 'add': 'http://c.biancheng.net'}
Python教程
注意,无法通过类似的方式修改类变量的值。
6.__call__()
__call__()
,该方法的功能类似于在类中重载 () 运算符,使得类实例对象可以像调用普通函数那样,以“对象名()”的形式使用。
举个例子:
class CLanguage:
# 定义__call__方法
def __call__(self,name,add):
print("调用__call__()方法",name,add)
clangs = CLanguage()
clangs("C语言中文网","http://c.biancheng.net")
程序执行结果为:
调用__call__()方法 C语言中文网 http://c.biancheng.net
可以看到,通过在 CLanguage 类中实现 __call__()
方法,使的 clangs 实例对象变为了可调用对象。
Python 中,凡是可以将 () 直接应用到自身并执行,都称为可调用对象。可调用对象包括自定义的函数、Python 内置函数以及本节所讲的类实例对象。
对于可调用对象,实际上“名称()”可以理解为是“名称.__call__()”
的简写。仍以上面程序中定义的 clangs 实例对象为例,其最后一行代码还可以改写为如下形式:
clangs.__call__("C语言中文网","http://c.biancheng.net")
运行程序会发现,其运行结果和之前完全相同。
这里再举一个自定义函数的例子,例如:
def say():
print("Python教程:http://c.biancheng.net/python")
say()
say.__call__()
程序执行结果为:
Python教程:http://c.biancheng.net/python
Python教程:http://c.biancheng.net/python
不仅如此,类中的实例方法也有以上 2 种调用方式,这里不再举例,有兴趣的读者可自行编写代码尝试。
用 __call__()
弥补 hasattr()
函数的短板
前面章节介绍了 hasattr()
函数的用法,该函数的功能是查找类的实例对象中是否包含指定名称的属性或者方法,但该函数有一个缺陷,即它无法判断该指定的名称,到底是类属性还是类方法。
要解决这个问题,我们可以借助可调用对象的概念。要知道,类实例对象包含的方法,其实也属于可调用对象,但类属性却不是。举个例子:
class CLanguage:
def __init__ (self):
self.name = "C语言中文网"
self.add = "http://c.biancheng.net"
def say(self):
print("我正在学Python")
clangs = CLanguage()
if hasattr(clangs,"name"):
print(hasattr(clangs.name,"__call__"))
print("**********")
if hasattr(clangs,"say"):
print(hasattr(clangs.say,"__call__"))
程序执行结果为:
False
**********
True
可以看到,由于 name 是类属性,它没有以 __call__
为名的 __call__()
方法;而 say
是类方法,它是可调用对象,因此它有 __call__()
方法。
7.setattr()、getattr()、hasattr()
Python hasattr()函数
hasattr()
函数用来判断某个类实例对象是否包含指定名称的属性或方法。该函数的语法格式如下:
hasattr(obj, name)
其中 obj 指的是某个类的实例对象,name 表示指定的属性名或方法名。同时,该函数会将判断的结果(True 或者 False)作为返回值反馈回来。
举个例子:
class CLanguage:
def __init__ (self):
self.name = "C语言中文网"
self.add = "http://c.biancheng.net"
def say(self):
print("我正在学Python")
clangs = CLanguage()
print(hasattr(clangs,"name"))
print(hasattr(clangs,"add"))
print(hasattr(clangs,"say"))
程序输出结果为:
True
True
True
显然,无论是属性名还是方法名,都在 hasattr()
函数的匹配范围内。因此,我们只能通过该函数判断实例对象是否包含该名称的属性或方法,但不能精确判断,该名称代表的是属性还是方法。
Python getattr() 函数
getattr()
函数获取某个类实例对象中指定属性的值。没错,和 hasattr()
函数不同,该函数只会从类对象包含的所有属性中进行查找。
getattr() 函数的语法格式如下:
getattr(obj, name[, default])
其中,obj 表示指定的类实例对象,name 表示指定的属性名,而 default 是可选参数,用于设定该函数的默认返回值,即当函数查找失败时,如果不指定 default 参数,则程序将直接报 AttributeError 错误,反之该函数将返回 default 指定的值。
举个例子:
class CLanguage:
def __init__ (self):
self.name = "C语言中文网"
self.add = "http://c.biancheng.net"
def say(self):
print("我正在学Python")
clangs = CLanguage()
print(getattr(clangs,"name"))
print(getattr(clangs,"add"))
print(getattr(clangs,"say"))
print(getattr(clangs,"display",'nodisplay'))
程序执行结果为:
C语言中文网
http://c.biancheng.net
<bound method CLanguage.say of <__main__.CLanguage object at 0x000001FC2F2E3198>>
nodisplay
可以看到,对于类中已有的属性,getattr()
会返回它们的值,而如果该名称为方法名,则返回该方法的状态信息;反之,如果该明白不为类对象所有,要么返回默认的参数,要么程序报 AttributeError 错误。
Python setattr()函数
setattr()
函数的功能相对比较复杂,它最基础的功能是修改类实例对象中的属性值。其次,它还可以实现为实例对象动态添加属性或者方法。
setattr() 函数的语法格式如下:
setattr(obj, name, value)
首先,下面例子演示如何通过该函数修改某个类实例对象的属性值:
class CLanguage:
def __init__ (self):
self.name = "C语言中文网"
self.add = "http://c.biancheng.net"
def say(self):
print("我正在学Python")
clangs = CLanguage()
print(clangs.name)
print(clangs.add)
setattr(clangs,"name","Python教程")
setattr(clangs,"add","http://c.biancheng.net/python")
print(clangs.name)
print(clangs.add)
程序运行结果为:
C语言中文网
http://c.biancheng.net
Python教程
http://c.biancheng.net/python
甚至利用 setattr()
函数,还可以将类属性修改为一个类方法,同样也可以将类方法修改成一个类属性。例如:
def say(self):
print("我正在学Python")
class CLanguage:
def __init__ (self):
self.name = "C语言中文网"
self.add = "http://c.biancheng.net"
clangs = CLanguage()
print(clangs.name)
print(clangs.add)
setattr(clangs,"name",say)
clangs.name(clangs)
程序运行结果为:
C语言中文网
http://c.biancheng.net
我正在学Python
显然,通过修改 name 属性的值为 say(这是一个外部定义的函数),原来的 name 属性就变成了一个 name() 方法。
使用 setattr()
函数对实例对象中执行名称的属性或方法进行修改时,如果该名称查找失败,Python 解释器不会报错,而是会给该实例对象动态添加一个指定名称的属性或方法。例如:
def say(self):
print("我正在学Python")
class CLanguage:
pass
clangs = CLanguage()
setattr(clangs,"name","C语言中文网")
setattr(clangs,"say",say)
print(clangs.name)
clangs.say(clangs)
程序执行结果为:
C语言中文网
我正在学Python
可以看到,虽然 CLanguage 为空类,但通过 setattr() 函数,我们为 clangs 对象动态添加了一个 name 属性和一个 say() 方法。
8.issubclass和isinstance函数:检查类型
Python 提供了如下两个函数来检查类型:
- issubclass(cls, class_or_tuple):检查 cls 是否为后一个类或元组包含的多个类中任意类的子类。
- isinstance(obj, class_or_tuple):检查 obj 是否为后一个类或元组包含的多个类中任意类的对象。
通过使用上面两个函数,程序可以方便地先执行检查,然后才调用方法,这样可以保证程序不会出现意外情况。
如下程序示范了通过这两个函数来检查类型:
# 定义一个字符串
hello = "Hello";
# "Hello"是str类的实例,输出True
print('"Hello"是否是str类的实例: ', isinstance(hello, str))
# "Hello"是object类的子类的实例,输出True
print('"Hello"是否是object类的实例: ', isinstance(hello, object))
# str是object类的子类,输出True
print('str是否是object类的子类: ', issubclass(str, object))
# "Hello"不是tuple类及其子类的实例,输出False
print('"Hello"是否是tuple类的实例: ', isinstance(hello, tuple))
# str不是tuple类的子类,输出False
print('str是否是tuple类的子类: ', issubclass(str, tuple))
# 定义一个列表
my_list = [2, 4]
# [2, 4]是list类的实例,输出True
print('[2, 4]是否是list类的实例: ', isinstance(my_list, list))
# [2, 4]是object类的子类的实例,输出True
print('[2, 4]是否是object类及其子类的实例: ', isinstance(my_list, object))
# list是object类的子类,输出True
print('list是否是object类的子类: ', issubclass(list, object))
# [2, 4]不是tuple类及其子类的实例,输出False
print('[2, 4]是否是tuple类及其子类的实例: ', isinstance([2, 4], tuple))
# list不是tuple类的子类,输出False
print('list是否是tuple类的子类: ', issubclass(list, tuple))
通过上面程序可以看出,issubclass()
和 isinstance()
两个函数的用法差不多,区别只是 issubclass()
的第一个参数是类名,而 isinstance()
的第一个参数是变量,这也与两个函数的意义对应:issubclass 用于判断是否为子类,而 isinstance() 用于判断是否为该类或子类的实例。
issubclass() 和 isinstance() 两个函数的第二个参数都可使用元组。例如如下代码:
data = (20, 'fkit')
print('data是否为列表或元组: ', isinstance(data, (list, tuple))) # True
# str不是list或者tuple的子类,输出False
print('str是否为list或tuple的子类: ', issubclass(str, (list, tuple)))
# str是list或tuple或object的子类,输出True
print('str是否为list或tuple或object的子类 ', issubclass(str, (list, tuple, object)))
此外,Python 为所有类都提供了一个 bases 属性,通过该属性可以查看该类的所有直接父类,该属性返回所有直接父类组成的元组。例如如下代码:
class A:
pass
class B:
pass
class C(A, B):
pass
print('类A的所有父类:', A.__bases__)
print('类B的所有父类:', B.__bases__)
print('类C的所有父类:', C.__bases__)
运行上面程序,可以看到如下运行结果:
类A的所有父类: (<class 'object'>,)
类B的所有父类: (<class 'object'>,)
类C的所有父类: (<class '__main__.A'>, <class '__main__.B'>)
从上面的运行结果可以看出,如果在定义类时没有显式指定它的父类,则这些类默认的父类是 object 类。
Python 还为所有类都提供了一个 __subclasses__()
方法,通过该方法可以查看该类的所有直接子类,该方法返回该类的所有子类组成的列表。例如在上面程序中增加如下两行:
print('类A的所有子类:', A.__subclasses__())
print('类B的所有子类:', B.__subclasses__())
运行上面代码,可以看到如下输出结果:
类A的所有子类: [<class '__main__.C'>]
类B的所有子类: [<class '__main__.C'>]
9.运算符重载,Python可重载运算符有哪些
Python 中的各个序列类型,每个类型都有其独特的操作方法,例如列表类型支持直接做加法操作实现添加元素的功能,字符串类型支持直接做加法实现字符串的拼接功能,也就是说,同样的运算符对于不同序列类型的意义是不一样的,这是怎么做到的呢?
其实在 Python 内部,每种序列类型都是 Python 的一个类,例如列表是 list 类,字典是 dict 类等,这些序列类的内部使用了一个叫作“重载运算符”的技术来实现不同运算符所对应的操作。
所谓重载运算符,指的是在类中定义并实现一个与运算符对应的处理方法,这样当类对象在进行运算符操作时,系统就会调用类中相应的方法来处理。
这里给大家举一个与重载运算符相关的实例:
class MyClass: #自定义一个类
def __init__(self, name , age): #定义该类的初始化函数
self.name = name #将传入的参数值赋值给成员交量
self.age = age
def __str__(self): #用于将值转化为字符串形式,等同于 str(obj)
return "name:"+self.name+";age:"+str(self.age)
__repr__ = __str__ #转化为供解释器读取的形式
def __lt__(self, record): #重载 self<record 运算符
if self.age < record.age:
return True
else:
return False
def __add__(self, record): #重载 + 号运算符
return MyClass(self.name, self.age+record.age)
myc = MyClass("Anna", 42) #实例化一个对象 Anna,并为其初始化
mycl = MyClass("Gary", 23) #实例化一个对象 Gary,并为其初始化
print(repr(myc)) #格式化对象 myc,
print(myc) #解释器读取对象 myc,调用 repr
print (str (myc)) #格式化对象 myc ,输出"name:Anna;age:42"
print(myc < mycl) #比较 myc<mycl 的结果,输出 False
print (myc+mycl) #进行两个 MyClass 对象的相加运算,输出 "name:Anna;age:65"
输出结果为:
name:Anna;age:42
name:Anna;age:42
name:Anna;age:42
False
name:Anna;age:65
这个例子中,MyClass 类中重载了 repr、str、<、+
运算符,并用 MyClass 实例化了两个对象 myc 和 mycl。
通过将 myc 进行 repr、str 运算,从输出结果中可以看到,程序调用了重载的操作符方法 __repr__
和 __str__
。而令 myc 和 mycl 进行 < 号的比较运算以及加法运算,从输出结果中可以看出,程序调用了重载 < 号的方法 __lt__
和 __add__
方法。
那么,Python 类支持对哪些方法进行重载呢?这里提供一个表格(表 1),列出了 Python 中常用的可重载的运算符,以及各自的含义。