给实例动态增加属性和方法
限制动态设置
对属性值进行约束
只读属性
多重继承
定制类
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()方法接收到的参数依次是:
- 当前准备创建的类的对象;
- 类的名字;
- 类继承的父类集合;
- 类的方法集合。