Python中有些方法和属性名前后都添加了双下划线,它们都属于特殊方法和属性,开发者可以通过它们来实现特殊功能,最常见的就是前面的__init__构造方法。

# 常见特殊方法

# __repr__

该方法类似于Java中的ToString(),默认是输出"类名 object at Address",可以自己修改:

class CustomClass():
    name=None
    def __init__(self,name):
        self.name=name
    def __repr__(self):
        return "this obj name is:"+self.name

c1 = CustomClass("li")
c2 = CustomClass("wang")

print(c1)
print(c2)
1
2
3
4
5
6
7
8
9
10
11
12

# __del__

这个方法在Python删除对象时自动执行,注意一个对象有多个引用时,只删除其中的一个引用垃圾回收并不会回收对象,只有该对象的所有引用都删除后才会回收该对象所占用的内存空间。注意父类有这个方法,子类要重写时,需要显式调用父类的__del__方法。

class DelClass:
    def __init__(self,name):
        self.name=name
    def __del__(self):
        print("删除对象:%s" % self.name)

c1 = DelClass("c1")
c2 = c1

del c1
#del c2

print("--------program end-------")
1
2
3
4
5
6
7
8
9
10
11
12
13

# __dir__

该方法列出对象内部的所有属性方法名,它是包含所有属性方法名的一个序列。当程序对某个对象执行dir(对象名)时实际上是调用该对象的__dir__()方法,该方法会返回内置属性和方法。

class ClassDir:
    def __init__(self,name):
        self.name=name
    def __dir__(self):
        return "hello"

c1 = ClassDir("li")
print(dir(c1))
1
2
3
4
5
6
7
8

# __dict__

该属性用于查看对象属性名及其值。

class ClassDict:
    age=10
    def __init__(self,name):
        self.name=name
        self.score=100
    def GetClass(self):
        return "hi"

c1 = ClassDict("li")

print(ClassDict.__dict__)
print(c1.__dict__)
print(c1.age)
1
2
3
4
5
6
7
8
9
10
11
12
13

注意以对象名c1调用该属性时并不输出age属性,以其类名调用时会输出age属性,这点要注意。

# __getattr__,__setattr___

前面类中提到过设置属性,当获取或设置一个不存在的属性时,就会调用这些方法:

class ClassAttr:
    @property
    def Age(self):
        print("get Age:%s" % self.age)
        return self.age

    @Age.setter
    def Age(self,age):
        print("set Age:%s" % age)
        self.age=age

    @Age.deleter
    def Age(self):
        print("delete Age!")
        del self.age

    #def __getattribute__(self,name):
    #    return "get already existed attr:%s" % name

    def __getattr__(self,name):
        return "get value:%s" % name

    def __setattr__(self,name,value):
        print("set attr in general access: %s,%s" % (name,value))

    def __delattr__(self,name):
        print("delete attr in general access: %s" % name)



c1 = ClassAttr()
c1.Age=100
c1.age=200

print(c1.Age)
print(c1.score)

del c1.Age
del c1.score
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

从上面可以看出:

  • __getattribute__,__setattr__,__delattr__会覆盖自己定义属性的getter,setter和deleter方法。
  • 在没有__getattribute__时,在获取任何字段时会先调用__getattr__方法。

# 反射相关的方法

反射指的是在运行过程中能够知道这个类的属性和方法,并且使用它们,在程序中动态获取类的相关信息并修改的这种机制被称为反射。

class TestReflect:
    name="TestReflectClass"
    def GetName(self):
        print("this is TestReflect,name:%s" % self.name)

c1 = TestReflect()

print(hasattr(c1,"name"))
print(hasattr(c1,"Getname"))
print(hasattr(c1,"GetName"))

print(getattr(c1,"name"))
setattr(c1,"name","hello world!")
print(getattr(c1,"name"))
print(getattr(c1,"GetName"))

def DGetName():
    print("this is outside define method" )

setattr(c1,"DGetName",DGetName)
c1.DGetName()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

上面涉及三个方法:

  • hasattr检查某个对象有没有包含指定属性或方法。
  • getattr获取对象的某个属性值。
  • setattr设置对象的某个属性值。

# __call__

使用hasattr只能判断有没有这个属性或方法,但无法具体判断是方法还是属性,这时候就需要判断对象是否有__call__属性。

class TestReflect:
    name="TestReflectClass"
    def GetName(self):
        print("this is TestReflect,name:%s" % self.name)
    def __call__(self):
        print("TestReflect is called")

c1 = TestReflect()

print(hasattr(c1.name,'__call__'))
print(hasattr(c1.GetName,'__call__'))

c1.GetName.__call__()
c1.GetName()
c1()

def TestFunc():
    print("this is a function!")

TestFunc.__call__()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 序列相关

使用类完善几个内置方法可以实现跟序列一样的功能:

class TestList:
    def __init__(self):
        self.__list={}
    def __len__(self):
        return len(self.__list)
    def __getitem__(self,key):
        return self.__list[key]
    def __setitem__(self,key,value):
        self.__list[key]=value
    def __delitem__(self,key):
        del self.__list[key]
    def __contains__(self,item):
        print("------search the value------")
        for elem in self.__list:
            if self.__list[elem] == item :
                return elem
        return False
    def GetAllElem(self):
        for elem in self.__list:
            print(elem)

c1 = TestList()
c1["math"]=100
c1["english"]=60
c1["history"]=200

print(c1["math"])
print(len(c1))
print(200 in c1)
del c1["math"]

c1.GetAllElem()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

上面有几个关键方法:

  • __len__ 得到该容器中元素个数
  • __getitem__ 得到容器中的某个元素
  • __contains__ 该容器是否包含某个元素
  • __setitem__ 设置指定键的值
  • __delitem__ 删除指定的元素

# 迭代器

前面使用for-in循环遍历列表,元组和字典,这些对象都是可迭代的,如果我们希望自己定义的对象也是可迭代的,需要实现下面两个方法:

  • iter(self):这个方法返回一个迭代器,这个迭代器需要有一个__next__()方法。如果没有这个方法,这个对象是不能用for-in来进行迭代的,因为这意味着它不可迭代。
  • reversed(self):为reversed()函数提供支持。
class IterObj:
    def __init__(self,num):
        self.arr=[]
        for i in range(num):
            self.arr.append(i*3)
        self.sum=0
    def __next__(self):
        print("进入迭代")
        if len(self.arr) == 0:
            raise StopIteration
        self.sum += self.arr[0]
        del self.arr[0]
        return self.sum
    def __iter__(self):
        return self
    def __reversed__(self):
        print("hello")

c1 = IterObj(5)
print(next(c1))
print(next(c1))

for item in c1:
    print(item)

reversed(c1)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

前面说活列表元组和字典,如果想基于它们定义自己的迭代器,可以继承它们然后自己再添加一些方法。

# 生成器

和迭代器类似,它也有__next__()方法,这意味着对它可以使用next()。

迭代器是先定义一个迭代器类,然后通过实例来创建迭代器,这意味着这个对象可以迭代;而生成器则是先定义一个包含yield语句的函数,以此来创建生成器。

def Generator():
    for i in range(10):
        yield i

gen = Generator()

for i in gen:
    print(i)
1
2
3
4
5
6
7
8

生成器有个好处就是它是按需获取的,这意味着只有每次调用next()获取下个数据时生成器才会执行一次,这为程序提高了性能,而且如果不使用生成器就需要使用列表或元组来存放这些数据,当这些数据很大时,就会有不可忽略的内存开销,所以生成器也节省了内存开销。

如何同生成器交换数据呢?

def Generator(val):
    for i in range(val):
        returnVal = yield i
        print("returnVal:%s" % returnVal)

gen = Generator(10)

print("value:%s" % next(gen))
print("--------------")
print("value:%s" % next(gen))
print("--------------")
print("value:%s" % gen.send(5))
print("--------------")
print("value:%s" % next(gen))
1
2
3
4
5
6
7
8
9
10
11
12
13
14

send(val)相当于(yield i)=val,再来看个复杂点的:

def Generator(val):
    flag =None
    for i in range(val):
        temp = (yield flag) if flag is not None else (yield i)
        #(yield flag)如果去掉括号这个语句执行效果会不同。
        print("val:%s,flag:%s,temp:%s" % (val,flag,temp))

gen = Generator(10)

print("value:%s" % next(gen))
print("--------------")
print("value:%s" % next(gen))
print("--------------")
print("value:%s" % gen.send(5))
print("--------------")
print("value:%s" % next(gen))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

上面一定要注意就是(yield flag)是加括号的,如果它去掉括号会有不一样的结果。

如果在生成器内想停止迭代怎么办呢?可以使用这两个方法:

def Generator(val):
    for i in range(val):
        returnVal = yield i
        print("returnVal:%s" % returnVal)

gen = Generator(10)

print("value:%s" % next(gen))
print("--------------")
print("value:%s" % next(gen))
print("--------------")
print("value:%s" % gen.send(5))
print("--------------")
#gen.close()
gen.throw(ValueError)
print("value:%s" % next(gen))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 运算符重载

除此之外还有其他一些类的内置方法用来完成特定的功能:

# 数值运算相关

方法 运算符
__add__ +
__sub__ -
__mul__ *
__matmul__ @
__truediv__ /
__floordiv__ //
__mod__ %
__divmod__ divmod
__pow__ **
__lshift__ <<
__rshift__ >>
__and__ &
__xor__ ^
__or__ \

上面这些操作符默认都是在该类的右边,比如执行x+y时,如果x没有定义__add__,那么要检查y是否定义了__radd__,也就是操作符在y的左边。所以上面的所有方法都还有一个以r为开头的版本,检查操作符在类的左边时执行。

正常情况下"+"有一个"+="版本,上面的方法以i为开头就是进行类似的转换。

# 比较运算符

方法 运算符
__lt__ <
__le__ <=
__eq__ ==
__ne__ !=
__gt__ >
__ge__ >=

# 单目运算符

方法 运算符
__neg__ 单目求负
__pos__ 单目求正
__invert__ 单目取反

# 类型转换相关

方法 函数名
__str__ str()
__bytes__ bytes()
__complex__ complex()
__int__ int()
__float__ float()

# 常见内建函数

方法 函数名
__format__ format()
__hash__ hash()
__abs__ abs()
__round__ round()
__trunc__ trunc()
__floor__ floor()
__ceil__ ceil()