[Python-Liao-XX…]系列,系列根据廖雪峰的python3初级教程学习整理。
41. 实例属性和类属性
与java等中概念类似,如果一个成员变量是类共有(能通过类名访问,也能通过类的所有实例访问),这个成员变量就是类属性。
如果一个成员变量是对象被创建时生成,该成员变量在不同的对象中值不同,则该成员变量就是实例属性。
类属性与java中的public static final变量类似,实例属性就是类中定义的一般成员。
给实例动态绑定属性
因为python是动态语言,对象在被创建后,还能再追加属性。如下:
class Student(object):
def __init__(self,name):
self.name = name
bob = Student("bob")
bob.score = 99
类属性
上面给实例绑定了属性,但是只有该实例可以访问,如果要绑定一个属性,让所有类的实例都可以访问呢?
class Student(object):
age_range = [7,8,9,10,11,12]
def __init__(self,name):
self.name = name
wangling = Student("wangling")
lijun = Student("lijun")
类的属性,可以通过类的实例,以及类名来访问。
wangling.age_range
[7, 8, 9, 10, 11, 12]
lijun.age_range
[7, 8, 9, 10, 11, 12]
Student.age_range
[7, 8, 9, 10, 11, 12]
注意:类属性名,与实例属性名,最好不要相同,属性名相同时,实例的属性优先。
通过del,可以删除某个实例对象的属性。
del wangling.name
wangling.name
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-9-97fa5287875b> in <module>()
----> 1 wangling.name
AttributeError: 'Student' object has no attribute 'name'
del 也可以删除类的属性。
del Student.age_range
Student.age_range
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-11-8ab09a787e02> in <module>()
----> 1 Student.age_range
AttributeError: type object 'Student' has no attribute 'age_range'
42. 面向对象高级编程-使用__slots__
通过上面的介绍,我们了解了在python中可以动态给类的实例添加属性,其实除了添加属性外,还能添加方法。
# 定义一个函数
def set_age(self,age):
self.age = age
from types import MethodType
# 给实例绑定一个方法
wangling.set_age = MethodType(set_age, wangling)
# 实例调用方法
wangling.set_age(23)
wangling.age
23
注意,该实例绑定的方法,只能在该实例内使用。如下会出错:
lijun.set_age(24)
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-17-21bb8f58fbce> in <module>()
----> 1 lijun.set_age(24)
AttributeError: 'Student' object has no attribute 'set_age'
如果要在该类的所有实例中使用,需要绑定到类上,绑定到类上之后,所有实例都可以访问该方法:
# 给类绑定一个方法
Student.set_age = MethodType(set_age, Student)
lijun.set_age(24)
使用 __slots__
限定动态绑定的属性
class Student(object):
__slots__ = ("age","sex","name")
def __init__(self,name):
self.name = name
wangling2 = Student("wangling")
wangling2.age = 18
wangling2.sex = "female"
如果试图绑定没有在 __slots__
中绑定的属性的话,会出现如下错误:
wangling2.hometown = "wuhan"
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-30-ae3267b09c2f> in <module>()
----> 1 wangling2.hometown = "wuhan"
AttributeError: 'Student' object has no attribute 'hometown'
44. 使用@property
如果通过实例直接绑定属性的话,是无法对属性进行错误检查的,为了进行错误检查,常见的方式就是在类中添加setter和getter方法。如下:
class Student(object):
def get_score(self):
return self._score
def set_score(self,score):
if not isinstance(score,int):
raise ValueError("must be an int")
if score < 0 or score > 100:
raise ValueError("must be 0-100")
self._score = score
wangling3 = Student()
wangling3.set_score(0)
wangling3.get_score()
0
通过上面的方法实现了属性的检查,但是使用的时候比较繁琐,需要去调用setter和getter方法,通过装饰器,实现通过实例.属性
进行值的存取。
class Student(object):
@property
def score(self):
return self._score
@score.setter
def score(self,score):
if not isinstance(score,int):
raise ValueError("must be an int")
if score < 0 or score > 100:
raise ValueError("must be 0-100")
self._score = score
wangling4 = Student()
wangling4.score = 98 # 实际上转化为set_score(98)
wangling4.score # 实际上转化为get_score()
98
wangling4.score = 101
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-44-5bc5deb55c9e> in <module>()
----> 1 wangling4.score = 101
<ipython-input-38-530c14b67ef6> in score(self, score)
9 raise ValueError("must be an int")
10 if score < 0 or score > 100:
---> 11 raise ValueError("must be 0-100")
12 self._score = score
ValueError: must be 0-100
如果只给一个属性设定getter,不设定setter的话,那这个属性就是只读属性。
@property广泛应用在类定义中,可以让调用者写出简短的代码,同时保证对参数进行必要的检查。
45. 多重继承
通过多重继承,一个子类可以同时获得多个父类的功能。如下:
class Mammal(object):
pass
class Runnable(object):
pass
class Dog(Mammal,Runnable):
pass
例如上面的子类Dog,继承了两个父类,则拥有了两个父类的功能。
MixIn
在设计类的继承关系时,通常,主线都是单一继承下来,如上面的Dog继承于Mammal,另外通过Runnable扩展了功能。如果还要混入额外的功能,通过多重继承可以实现,比如Dog如果还要混入 肉食动物功能的话,再定义一个父类 EatMeatMixIn用于继承,这种设计方式通常称之为 MixIn。
通过这种设计方式,我们不需要复杂的继承链条,只要选择组合不同的类的功能,就可以快速构造出所需的子类。
在python中允许多重继承,因为MixIn是一种常见的设计方式,只允许单一继承的语言,比如java就不能使用MixIn的设计。 (虽然java不能多重继承,但是可以扩展某个接口,以达到类似的功能)
46. 定制类
在上面针对实例限制动态属性绑定时,用到了__slots__
变量,类似_xxx_
的变量或函数名要注意,这些在python中有特殊用途。
之前已经了解了__slots__
和__len__()
的用法,除此之外,python的class中还有很多这样特殊用途的函数,可以用来定制类。
与其他语言类似,覆盖掉基类中的默认方法,定制属于该类的方法和变量。
__str__
自定义
class Dog2(Mammal,Runnable):
def __len__(self):
return 999
def __str__(self):
return "Dog2"
utasuke2 = Dog2()
print(utasuke2)
Dog2
len(utasuke2)
999
上面相当于重载了object中的len和str方法,如果没有重载的话,结果如下:
dog = Dog()
print(dog)
<__main__.Dog object at 0x0401F390>
len(dog)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-58-246878d32b07> in <module>()
----> 1 len(dog)
TypeError: object of type 'Dog' has no len()
但是还有个问题,如果不用print,直接输出utasuke2的话,仍是其内存地址,因为print调用的是str,而直接输出变量使用的是__repr__()
。
utasuke2
<__main__.Dog2 at 0x401f430>
简单一点的方式,即直接将__repr__
指向__str__
。
class Dog3(Mammal,Runnable):
def __str__(self):
return "Dog3"
__repr__ = __str__
utasuke3 = Dog3()
utasuke3
Dog3
__iter__
自定义
如果一个类想被用于 for…in 循环,类似于list或tuple那样,就需要实现一个__iter__()
方法,该方法返回一个迭代对象。然后python的循环就会不断调用该迭代对象的__next__()
方法拿到循环的下一个,知道遇到StopIteration错误时推出循环。
以斐波那契数为例,写一个Fib类,可以作用于for循环。
class Fib(object):
def __init__(self):
print("① init")
self.a,self.b = 0,1
def __iter__(self):
print("② iter")
return self
def __next__(self):
print("next")
self.a,self.b = self.b,self.a + self.b
if self.a > 100:
print("③ 100 over")
raise StopIteration()
return self.a
for n in Fib():
print(n)
① init
② iter
next
1
next
1
next
2
next
3
next
5
next
8
next
13
next
21
next
34
next
55
next
89
next
③ 100 over
__getitem__
自定义
Fib实例虽然能作用于for 循环,看起来与list有点像,但是,把它当成list来使用还是不行的。 比如Fib()[5]
如果要实现像list那样按照下标取出元素,则需要实现__getitem__()
方法,如下:
class Fib(object):
def __getitem__(self,n):
a,b = 1,1
for x in range(n):
a,b = b,a+b
return a
Fib()[10]
89
上面实现了下标索引,下面的代码实现了类似list的切片功能。
class Fib2(object):
def __getitem__(self,n):
if isinstance(n,int):
a,b = 1,1
for x in range(n):
a,b = b,a+b
return a
if isinstance(n,slice):
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
Fib2()[0:5]
[1, 1, 2, 3, 5]
但是没有对step参数做处理,负数即反向切片也没有处理,可见要完整实现__getitem__
还是有很多工作要做的。
上面举了__getitem__()
的例子,类似的还有 setitem()和delitem()等,通过这些方法,使得自己定义的类表现得与python自带的list/dict等没有什么差别,这完全归功于动态语言的鸭子特性,即不需要强制继承某个接口
__getattr__
自定义
class Student(object):
def __init__(self):
self.name = "uta"
utasuke = Student()
utasuke.name
'uta'
utasuke.score
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-99-4cd6c680a386> in <module>()
----> 1 utasuke.score
AttributeError: 'Student' object has no attribute 'score'
试图访问不存在的属性后,就出现了上述的错误,如果要避免上述错误,需要自定义__getattr__
函数。
class Student2(object):
def __init__(self):
self.name = "uta"
def __getattr__(self,attr):
if attr == "score":
return 999
utasuke = Student2()
utasuke.score
999
也可以返回一个函数,比如:
class Student3(object):
def __init__(self):
self.name = "uta"
def __getattr__(self,attr):
if attr == "age1":
return lambda:18
utasuke = Student3()
utasuke.age1()
18
__getattr__
的应用(REST API)
__call__
自定义
47. 使用枚举类
python中,通常如果我们需要定义一个常量,会使用大写字母的方式,这种方式简单,但是缺点是类型为int,而且本质上仍然是个变量。
JAN = 1
FEB = 2
更好的方法是为这样的枚举类型定义一个class
类型,然后,每个常量都是class
的一个唯一实例。Python提供了Enum类来实现这个功能:
类名为Month,每个月份如Jan,都是类Month的一个实例。
from enum import Enum
Month = Enum("Month",("Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"))
value 属性则是自动赋给成员的 int 常量,默认从 1 开始计数。
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
type(Month)
enum.EnumMeta
每个常量都是class的一个唯一实例:
type(Month.Feb)
<enum 'Month'>
更精确地控制枚举类型
from enum import Enum,unique
@unique
class Weekday(Enum):
Sun = 0 # 将Sun的value设定为0
Mon = 1
Tue = 2
Wed = 3
Thu = 4
Fri = 5
Sat = 6
@unique 装饰器可以帮助我们检查保证没有重复值。
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
Weekday.Mon
<Weekday.Mon: 1>
Weekday["Mon"]
<Weekday.Mon: 1>
Weekday(1)
<Weekday.Mon: 1>
Weekday["Mon"].value
1
小结
Enum可以把一组相关常量定义在一个 class 中,且 class 不可变,而且成员可以直接比较。
48. 使用元类
1. 使用type()创建类
class Hello(object):
def hello(self,name="world"):
print("hello,%s" %name)
h = Hello()
h.hello()
hello,world
print(type(Hello))
<class 'type'>
print(type(h))
<class '__main__.Hello'>
上面Hello
这个class,本质上也是一个对象,该对象由type()创建。
type()函数既可以返回一个对象的类型,有可以创建一个新的类型,比如:
- 通过type()创建一个Hello类,而无需通过class Hello(object)…定义:
def fn(self,name="world"):
print("Hello,%s" %name)
Hello = type("Hello",(object,),dict(hello=fn))
h = Hello()
h.hello()
Hello,world
print(type(Hello))
<class 'type'>
print(type(h))
<class '__main__.Hello'>
要使用type()创建一个class对象,type()函数要依次传入三个参数:
- class名词
- 继承的父类名,通过tuple可以定义多继承
- class 的方法名称与函数绑定,这里把函数 fn 绑定到方法名 hello 上
通过type()函数创建的类和直接写 class 是完全一样的,因为 Python 解释器遇到class定义时, 仅仅是扫描一下class定义的语法,然后调用type()函数创建出class。
2. 使用metaclass
元类
一般要创建一个实例(即对象),需要先定义一个类,如果要创建一个类呢,必须要先定义metaclass。 所以最终的顺序就是,先定义metaclass,就可以创建类,创建了类就可以创建对象实例。
可以将类class看作是metaclass创建出的实例。
metaclass 是 Python 面向对象里最难理解,也是最难使用的魔术代码。 现阶段也不需要使用,暂时跳过。