面对对象高级编程

对于python这种动态语言,我们可以在创建了class实例之后,给其绑定属性甚至绑定方法

class My_TestClass(object):
    """docstring for My_TestClass"""
    pass

test1=My_TestClass()
test1.name="liyuyuan" # 给实例绑定对象

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

from types import MethodType
test1.set_name=MethodType(set_name, test1) #实例对象绑定方法
test1.set_name("lyy")
print(test1.name)  # lyy

给一个实例对象绑定的方法,对另外的实例对象是不起作用的,应该给class绑定方法
动态语言允许我们在程序运行过程中动态给class增加功能

My_TestClass.set_name=set_name
test2=My_TestClass()
test2.set_name("wtt")
print(test2.name) # wtt

slots

通过__slots__来对实例的属性进行限制能添加的属性

class My_Slots_Class(object):
    """docstring for My_Slots_Class"""
    __slots__=("name", "score")
    # def __init__(self, age): #不能有 name、score外的其他属性
    #   self.age=age
    pass
        

slot1=My_Slots_Class()
slot1.age=10 #AttributeError: 'My_Slots_Class' object has no attribute 'age'

__slots__定义属性仅对当前类实例起作用,对继承的子类是不起作用的,可以在子类中也定义__slots__,这样,子类实例允许定义的属性就是自身的__slots__加上父类的__slots__

class My_Slots_Subclass(My_Slots_Class):
    """docstring for My_Slots_Subclass"""
    __slots__=("age")
    pass
#此时允许的属性为子类的__slots__加上父类的__slots__
slot2=My_Slots_Subclass()
slot2.age=22
slot2.ttt=10  #AttributeError: 'My_Slots_Subclass' object has no attribute 'ttt'

@property

python内置的@property装饰器就负责把一个方法变为属性调用

class My_Property_Class(object):
    @property
    def age(self):
        print("获取age",self._age)
        return self._age

    @age.setter
    def age(self, age):
       #可以在此处检查传入参数处理
        print("设置age",age)
        self._age=age

property1=My_Property_Class()
property1.age=10 #设置age 10
property1.age # 获取age 10

如果我们只定义@property那么该属性就是只读的

多继承

python中支持多继承,一个子类可以同时获得多个父类的所有功能

MixIn

一般设计为单继承,但是当需要额外功能时可以用过多继承实现,这就是
MixIn

定制类

类似__slots__限制类的属性
__len__()方法让class能作用域len()函数

str

__str__() 对于打印出来的对象信息修改
__repr__()对程序员看到的对象信息修改

class Student(object):
...     def __init__(self, name):
...         self.name = name
...     def __str__(self):
...         return 'Student object (name: %s)' % self.name
            __repr__ = __str__ #经常把两个写作相同
...
print(Student('Michael')) #Student object (name: Michael)
#当开发者在命令行直接输入对象 会打印对象信息,这是调用的是__repr__()

iter

如果类需要被for...in循环,必须实现__iter__()方法,返回一个可迭代对象,然后调用对象的__next__()方法获取循环下一个值,直到遇到StopIteration错误退出循环

学习迭代器一章知道,调用iter()方法转为迭代器,然后调用next方法获取下一个值,其内部实现就是__iter__()__next__()


class My_Iter_Class(object):
    """docstring for My_Iter_Class"""
    def __init__(self):
        self.a=0
    def __iter__(self):
        return self

    def __next__(self):
        self.a+=1
        while self.a>10:
            raise StopIteration()
        print(self.a)


for x in My_Iter_Class():
    pass


getitem

对于实现了__iter__()__next__()可以用for遍历,但是并不能像list一样取某个下标或者进行切片操作,这时需要实现__getitem__()方法

class Fib(object):
    def __getitem__(self, n):
        if isinstance(n, int): # n是索引
            a, b = 1, 1
            for x in range(n):
                a, b = b, a + b
            return a
        #判断是否为切片对象
        if isinstance(n, slice): # n是切片  
            start = n.start
            stop = n.stop
            if start is None:
                start = 0
            a, b = 1, 1
            L = []
            for x in range(stop):
                if x >= start:
                    L.append(a)
                a, b = b, a + b
            return L

__getitem__()方法中,需要判断是取下标索引传入参数为int,还是传入的为切片slice进行切片操作
上面对切片操作并不完善,比如对于负数的处理

对应于__getitem__()还有__setitem__()来对类进行赋值 delitem()删除某个元素

getattr

当我们调用类中不存在的属性或方法时时,Python解释器会试图调用__getattr__(self, 'attr')来获得属性或方法,我们就可以通过这个方法来返回对应不存在属性值或方法

如果我们实现了这个方法,默认返回是None无法提示用户该属性不存在,我们可以返回一个raise AttributeError错误

call

当我们在类内部实现__call__()方法后,我们就可以直接对实例进行调用

class My_Call_Class(object):
    def __call__(self):
        print("调用自身call")

obj1=My_Call_Class()
obj1() # 调用自身call

#判断一个对象是否可调用
if callable(obj1): #True
    print("可调用")  #可调用

对于可直接调用对象,可以将其看做函数,可以传参数

枚举类

Python为定义大量常量的枚举 提供了Enum

使用Enum实现一个简单的枚举

Month = Enum("Month", ("Jan", "Feb", "Mar", "Apr"))
print(Month.Jan) # Month.Jan
print(Month.Mar.value) # 3

# __members__返回所有成员的name->value的map
for name, member in Month.__members__.items():
    print(name, '=>', member, ',', member.value)

对于某些情况我们可能需要自定义枚举,可以从Enum派生出自定义类

class WeekDay(Enum):
    Sun=7
    Mon=1
    Tue=2
    Wed=3
    Thu=4
    Fri=5
    Sat=6

print(WeekDay.Sun) #WeekDay.Sun
print(WeekDay.Fri.value) #5

for name, member in WeekDay.__members__.items():
    print(name, '=>', member, ',', member.value)

Enum可以把相关常量定义在一个不可变class中,成员之间可以直接比较

元类

动态语言和静态语言最大的不同就是,函数和类的定义不是编译时定义的而是运行时动态创建的

当python解释器载入模块时,就会依次执行该模块中的所有语句

class的定义是在运行时动态创建的,创建class的方法就是使用type()函数

利用type函数创建一个class

def hello_func(self, name="world"):
    print("hello, ", name)

#用type创建Hello class
Hello_Class=type("Hello", (object,), dict(hello=hello_func))

h1= Hello_Class()
h1.hello() # hello,  world

用type定义的类与class定义的完全相同,python解释器遇到class定义类也是仅仅扫描一下后调用type()方法来定义类

当前执行的模块名字为__main__

metaclass

metaclass允许我们创建或者修改类

metaclass是类的模板,所以必须从type类型派生

该节不常用,自行参考廖雪峰的python学习元类