6.面向对象高级

给实例动态增加属性和方法
限制动态设置
对属性值进行约束
只读属性
多重继承
定制类
str()
iter()
getitem()
getattr()
call()
枚举
元类
metaclass

1.给实例动态增加属性和方法

from types import MethodType


class Person(object):
    pass


p = Person()


def sayhello(self, msg):
    print(msg)


p.name = 'Mary'  # 动态定义一个属性
print(p.name)

p.sayhello = MethodType(sayhello, p)  # 动态定义一个方法
p.sayhello('hello')
输出结果:
Mary
hello

需要注意的是,给一个实例动态设置的方法或属性,只对这个实例有作用,其它实例还是没有的

p1 = Person()
print(p1.name)

输出结果:
Traceback (most recent call last):
  File "E:/python/project/test6/test6_1.py", line 22, in <module>
    print(p1.name)
AttributeError: 'Person' object has no attribute 'name'

如果想作用到所有实例,需要动态的给类定义方法和属性

from types import MethodType


class Person(object):
    pass


p = Person()


def sayhello(self, msg):
    print(msg)


Person.name = 'Mary'  # 动态的给类定义一个属性
print(p.name)

Person.sayhello = sayhello # 动态的给类设置一个方法
p.sayhello('hello')

p1 = Person()
print(p1.name)
输出结果:
Mary
hello
Mary

2.限制动态设置
限制某个类只能被定义某些属性的方法:

从自动提示中也能看出,只有这两个属性显示出来了,如果强行设置一个别的属性会报错

class Person(object):
    __slots__ = ('name', 'sex')


p = Person()
p.age = 18
输出结果:
Traceback (most recent call last):
  File "E:/python/project/test6/test6_2.py", line 6, in <module>
    p.age = 18
AttributeError: 'Person' object has no attribute 'age'

注意:父类定义的slots只对自身有效,对其子类无效

class Person(object):
    __slots__ = ('name', 'sex')


class Student(Person):
    pass
    # __slots__ = ('addr', 'id')


s = Student()
s.name = 'mary'
s.id = 123
print(s.name, s.id)
输出结果:
mary 123

如果子类也定义了slots,那么子类所允许定义的属性就是子类自身的slots加上其父类的slots

3.对属性值进行约束

class Person(object):
    @property
    def name(self):
        self._name

    @name.setter
    def name(self, value):
        if type(value) != str:
            raise ValueError('name must be string')
        self._name = value


p = Person()
p.name = 11
输出结果:
Traceback (most recent call last):
  File "E:/python/project/test6/test6_3.py", line 14, in <module>
    p.name = 11
  File "E:/python/project/test6/test6_3.py", line 9, in name
    raise ValueError('name must be string')
ValueError: name must be string

设置只读属性:

class Person(object):
    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, value):
        if type(value) != str:
            raise ValueError('name must be string')
        self._name = value

    @property
    def age(self):
        return 20


p = Person()
p.name = 'Mary'

print(p.age, p.name)
p.age = 11
输出结果:
20 Mary
Traceback (most recent call last):
  File "E:/python/project/test6/test6_3.py", line 21, in <module>
    p.age = 11
AttributeError: can't set attribute

4.多重继承

class Person(object):
    def f1(self):
        print('f1')


class Person1(object):
    def f2(self):
        print('f2')


class Person2(Person, Person1):
    pass


p = Person2()
p.f1()
p.f2()
输出结果:
f1
f2

5.定制类

5.1、_str_(),相当于java中的toString()方法

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

    def __str__(self):
        return "Person (name:%s)" % self.name


p = Person('Tom')
print(p)
输出结果:
Person (name:Tom)

5.2、_iter_()

class Fib(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 > 2000:
            raise StopIteration()
        return self.a

f = Fib()
for i in Fib():
    print(i)

输出结果:
1
1
2
3
5
8
13
21
34
55
89
144
233
377
610
987
1597

5.3虽然可以遍历取值了,但是不能像list一样,f[0]就获取到第一个值,这时可以通过getitem()来实现:
class Fib(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 > 2000:
            raise StopIteration()
        return self.a

    def __getitem__(self, item):
        a, b = 1, 1
        for n in range(item):
            a, b = b, a + b
        return a


f = Fib()
for i in Fib():
    print(i)

print(f[0])
输出结果:
1
1
2
3
5
8
13
21
34
55
89
144
233
377
610
987
1597
1

虽然可以通过f[0]这种方式取值了,但是没有实现List里的切片取值功能,此时需要修改getitem()方法:

class Fib(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 > 100:
            raise StopIteration()
        return self.a

    # def __getitem__(self, item):
    #     a, b = 1, 1
    #     for n in range(item):
    #         a, b = b, a + b
    #     return a

    def __getitem__(self, item):
        if isinstance(item, int):
            a, b = 1, 1
            for n in range(item):
                a, b = b, a + b
            return a
        if isinstance(item, slice):
            start = item.start
            end = item.stop
            if start is None:
                start = 0
            l = []
            a, b = 1, 1
            for n in range(end):
                if n >= start:
                    l.append(a)
                a, b = b, a + b
            return l


f = Fib()
for i in Fib():
    print(i)

print(f[0:3])
输出结果:
1
1
2
3
5
8
13
21
34
55
89
[1, 1, 2]

5.4、_getattr_()方法:

class Student(object):
    def __init__(self):
        self.name = 'Tom'
s = Student()
print(s.name)
print(s.age)
输出结果:
  Tom
    File "E:/python/project/test6/test6_5.py", line 64, in <module>
    print(s.age)
AttributeError: 'Student' object has no attribute 'age'

当调用已定义的属性或方法时是正常的,但是调用未定义的属性或方法时就会出现异常,这时可以通过getattr()方法来做一些事情:

class Student(object):
    def __init__(self):
        self.name = 'Tom'

    def __getattr__(self, item):
        if item == 'age':
            return 20

s = Student()
print(s.name)
print(s.age)
输出结果:
Tom
20

当调用一个不存在的值时,Python解释器就会去调用getattr()方法来取值;这里不仅可以取值,也可以返回一个方法:

class Student(object):
    def __init__(self):
        self.name = 'Tom'

    def f1(self):
        return lambda: 1 + 1

    def __getattr__(self, item):
        if item == 'age':
            return 20
        if item == 'addr':
            return self.f1()
        if item == 'name':
            return "sss"


s = Student()
print(s.name)  # 如果已经定义了name,就不会到__getattr__()里找了
print(s.age)
print(s.addr())  # 返回一个可执行的方法,例如返回一个str会报异常
输出结果:
Tom
20
2

如果调用一个连getattr()里也没有覆盖到的值,就会返回None,这是因为getattr()方法默认返回None,所以如果想要getattr()只响应特定的几个属性,就要按照约定,抛出AttributeError的错误:

def __getattr__(self, item):
        if item == 'age':
            return 20
        if item == 'addr':
            return self.f1()
        if item == 'name':
            return "sss"
        raise AttributeError('Student object has no attribute \'%s\'' % item)

利用这个特性写出一个简单的链式调用:

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


print(Chain('www.baidu.com').login.user)  # 拼接一个接口url

输出结果:
www.baidu.com/login/user

5.5、_call_()
一般的实例调用方法时,都是用obj.method()这种形式调用,如果直接用对象本身执行就会报异常:

class Obj(object):
    pass


o = Obj()
o()

输出结果:
  File "E:/python/project/test6/test6_5.py", line 103, in <module>
  o()
TypeError: 'Obj' object is not callable

这时可以通过实现call()方法来使对象自身可执行

class Obj(object):
    def __call__(self, *args, **kwargs):
        print('sss')


o = Obj()
o()
输出结果:
sss

print(callable(o)) #判断该实例是否可执行
print(callable(Student()))
输出结果:
True
False

5.6枚举

from enum import Enum

e = Enum('E', ('A', 'B', 'C'))

for name, member in e.__members__.items():
    print(name, ':', member, ',', member.value)

print(e.A.value, e.C.value)  # value是自动赋值给枚举成员的int常量,默认从1开始


输出结果:
A : E.A , 1
B : E.B , 2
C : E.C , 3
1 3

自定义枚举:

@unique
class Week(Enum):
    Sun = 0
    Mon = 1
    Tue = 2
    Wed = 3
    Thu = 4
    Fri = 5
    Sta = 6


print(Week.Mon)
print(Week['Tue'])
print(Week(0))
print(Week['Wed'].value)

输出结果:
Week.Mon
Week.Tue
Week.Sun
3

5.7、元类
Python是动态语言,类和函数的定义是在运行时动态创建,而不是像JAVA一样在编译时创建的,之前用到的type()函数可以查看类型,例如一个类的类型是type,一个实例对象的类型是class,type()还可以用来创建一个新的类型,例如可以通过type来创建一个Person类,而不需要通过class Person(object):来创建:

def f(self, msg='python'):
    print('hi %s' % msg)


TestObj = type('TestObj', (object,), dict(hi=f))

t = TestObj()
t.hi()
t.hi(msg='sss')
输出结果:
hi python
hi sss

要通过type创建一个类型,type()函数依次传入三个参数:
1:要创建的类名
2:继承的父类,可能有多个,只有一个时需要注意tuple的单元素写法
3:创建的类型里的方法绑定,例如创建的TestObj里的hi函数定义为f函数

通过type来创建类型其实和class 类名..的方式来创建是一样的,Python解释器在遇到class Xxx(object)这种定义时,也是先扫瞄下该class定义的语法,然后再在内部通过type来创建类型的。

metaclass

class MyMetaclass(type):
    def __new__(cls, name, bases, attrs):
        attrs['sayHi'] = lambda self, value: cls.f1(self, value)  #给根据这个metaclass创建的这个类自动定义一个sayHi方法,方法定义形式为f1
        return type.__new__(cls, name, bases, attrs)

    def f1(self, value):
        print(value)


class Person(object, metaclass=MyMetaclass):
    pass


p = Person()
p.sayHi('hi python')
输出结果:
hi python

定义一个继承list类的Mylist类型并定义一个sayHi方法和一个adds方法

class MyMetaclass(type):
    def __new__(cls, name, bases, attrs):
        attrs['sayHi'] = lambda self, value: cls.f1(self, value)
        attrs['adds'] = lambda self, value: self.append(value)
        return type.__new__(cls, name, bases, attrs)

    def f1(self, value):
        print(value)



class Mylist(list, metaclass=MyMetaclass):
    pass


mlist = Mylist()
mlist.adds("sss")
print(mlist)
mlist.sayHi('hi python')
输出结果:
['sss']
hi python

当我们传入关键字参数metaclass时,metaclass约束就生效了,它指示Python解释器在创建MyList时,要通过MyMetaclass.new()来创建,在此,我们可以修改类的定义,比如,加上新的方法,然后,返回修改后的定义。

new()方法接收到的参数依次是:

  1. 当前准备创建的类的对象;
  2. 类的名字;
  3. 类继承的父类集合;
  4. 类的方法集合。

猜你喜欢

转载自blog.csdn.net/aislli/article/details/81163535