3、面向对象高级

动态绑定属性、方法

正常情况下,当我们定义了一个class,创建了一个class的实例后,我们可以给该实例绑定任何属性和方法,这就是动态语言的灵活性。

from types import MethodType

class Person(object):
    pass

per1 = Person()
per2 = Person()
# 动态绑定属性
per1.name = 'lucy'
per2.name = 'tom'

def showName(self):
    print(self.name)
# 动态绑定方法
per1.showName = MethodType(showName,per1)
# 可以调用
per1.showName()
# 无法调用,报错
per2.showName()

可以通过MethodType()在运行期间动态的给一个实例per1绑定方法,但是,Person类的的其他实例,例如per2无法使用这个方法

可以通过给Person这个类绑定,来实现所有实例使用showName这个方法

class Person(object):
    pass

per1 = Person()
per2 = Person()

per1.name = 'lucy'
per2.name = 'tom'

def showName(self):
    print(self.name)
# 对Person类动态绑定方法
Person.showName = showName

per1.showName()
per2.showName()

如果我们需要限制实例的属性和方法的话,那么可以使用__slots__属性,这个属性的值是一个元组Tuple

使用__slots__要注意,__slots__定义的属性仅对当前类实例起作用,对继承的子类是不起作用的

除非在子类中也定义__slots__,这样,子类实例允许定义的属性就是自身的__slots__加上父类的__slots__

from types import MethodType

class Person(object):
    #声明Person类的实例,只可以有name、age两个属性
    __slots__ = ('name','age') 

对于类的方法,装饰器一样起作用,Python内置的@property装饰器就是负责把一个方法变成属性调用的,省去了编写getter、setter的麻烦

注意:此处的方法名称,不要和属性名一样,以免造成堆栈溢出报错

from msilib.schema import Property


class Person(object):
	#此处实际的属性应该是__name,而对外暴露的是name,必须通过name来操作属性
    @property
    def name(self):
        return self.__name
    #__name属性的setter方法
    @name.setter
    def name(self,name):
        if(len(name) < 3):
            print('名字长度必须大于3位')
        else:
            self.__name = name

per = Person()
per.name = 'lucy'

print(per.name)

由于我们直接打印对象,输出的是<__main__.Student object at 0x109afb190>这样的字符串,所以,可以重写__str__(),来进行定制,类似java的toString方法

由于__str__()方法是返回给用户的字符串,但是如果在调试的时候,控制台上打印的字符串是__repr__()返回的,所以,在重写完__str__()后,使用__repr__ = __str__即可

class Person(object):
    def __init__(self,name,age):
        self.__name = name
        self.__age = age
    @property
    def name(self):
        return self.__name
    @name.setter
    def name(self,name):
        self.__name = name
    @property
    def age(self):
        return self.__age
    @age.setter
    def age(self,age):
        self.__age = age
    
    def __str__(self):
        return '姓名:%s,年龄:%s' % (self.__name,self.__age)
    __repr__ = __str__
    
per = Person('lucy','age')
print(per)

如果一个类想被用于for ... in循环,类似list或tuple那样,就必须实现一个__iter__()方法,该方法返回一个迭代对象,然后,Python的for循环就会不断调用该迭代对象的__next__()方法拿到循环的下一个值,直到遇到StopIteration错误时退出循环。

例如,1-10000之内的斐波那契数列(除第一第二项,其余每项都等于前两项之和)

class Feb(object):
    def __init__(self):
        self.a,self.b = 0,1
    def __iter__(self):
        return self
    def __next__(self):
        # 计算下一个值,下一个值等于前两个值之和
        self.a, self.b = self.b, self.a + self.b 
        if self.a > 100000: # 退出循环的条件
            raise StopIteration()
        return self.a # 返回下一个值

feb = Feb()

for i in feb:
    print(i)

正常情况下,当我们调用类的方法或属性时,如果不存在,就会报错,此时除了添加这个属性外,python还提供了一个机制,就是写一个__getattr__()方法,动态返回一个属性,也可以用来返回方法

class Person(object):
    def __init__(self,name):
        self.name = name
    def __getattr__(self,attr):
        if attr == 'age':
            return 18
        if attr == 'sayHello':
            return lambda :print('Hello')
    
per = Person('lucy')
print(per.age)
per.sayHello()

注意,只有在没有找到属性的情况下,才调用__getattr__,已有的属性,比如name,不会在__getattr__中查找,此外,注意到任意调用如per.abc都会返回None,这是因为我们定义的__getattr__默认返回就是None

class Chain(object):
    def __init__(self,path=''):
        self.path = path
    def __getattr__(self,path):
        return Chain('%s/%s' % (self.path,path))
    def __str__(self):
        return self.path
    __reqr__ = __str__

ch = Chain().status.users.list
print(ch)

# /status/users/list

任何类,只需要定义一个__call__()方法,就可以直接对实例进行调用

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

    def __call__(self):
        print('姓名:%s,年龄:%s' % (self.name,self.age))

per = Person('lucy',18)

per()

枚举

Python提供了Enum类来实现这个功能,定义一个class类型,然后,每个常量都是class的一个唯一实例

value属性则是自动赋给成员的int常量,默认从1开始计数

#导入Enum
from enum import Enum
#定义枚举类Month,并声明枚举项
Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))

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

'''
Jan => Month.Jan => 1
Feb => Month.Feb => 2
Mar => Month.Mar => 3
Apr => Month.Apr => 4
May => Month.May => 5
Jun => Month.Jun => 6
Jul => Month.Jul => 7
Aug => Month.Aug => 8
Sep => Month.Sep => 9
Oct => Month.Oct => 10
Nov => Month.Nov => 11
Dec => Month.Dec => 12
'''

如果需要更精确地控制枚举类型,可以从Enum派生出自定义类

from enum import Enum,unique

# unique装饰器用来帮助检查有没有重复值
@unique
class Weekday(Enum):
    Sun = 0
    Mon = 1
    Tue = 2
    Wed = 3
    Thu = 4
    Fri = 5
    Sat = 6

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

'''
Sun => Weekday.Sun => 0
Mon => Weekday.Mon => 1
Tue => Weekday.Tue => 2
Wed => Weekday.Wed => 3
Thu => Weekday.Thu => 4
Fri => Weekday.Fri => 5
Sat => Weekday.Sat => 6
'''

访问枚举

from enum import Enum,unique

# unique装饰器用来帮助检查有没有重复值
@unique
class Weekday(Enum):
    Sun = 0
    Mon = 1
    Tue = 2
    Wed = 3
    Thu = 4
    Fri = 5
    Sat = 6

day = Weekday.Sun
print(day) # Weekday.Sun

print(Weekday.Mon) # Weekday.Mon

print(Weekday['Tue']) # Weekday.Tue

print(day.value) # 0

print(day == Weekday.Sun) # True

print(Weekday(1)) # Weekday.Mon

type()

要定义一个Hello的class,就写一个hello.py模块

class Hello(object):
    def sayHello(self,name = 'world'):
        print('hello %s' % name)

main.py

from hello import Hello

he = Hello()
he.sayHello()

print(type(he)) # <class 'hello.Hello'>

print(type(Hello)) # <class 'type'>

type()函数可以查看一个类型或变量的类型,Hello是一个class,它的类型就是type,而he是一个实例,它的类型就是class Hello

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

type()函数既可以返回一个对象的类型,又可以创建出新的类型,可以通过type直接创建一个Hello类型,而不需要通过定义,语法:类名 = type('类名',继承的父类元组,dict(类方法=函数)

def func(self,name = 'world'):
    print('hello %s' % name)

Hello = type('Hello',(object,),dict(sayHello = func))

he = Hello()

he.sayHello()

通过type()函数创建的类和直接写class是完全一样的,因为Python解释器遇到class定义时,仅仅是扫描一下class定义的语法,然后调用type()函数创建出class

metaclass