1.类的属性和实例的属性
代码:
class Student():
name = '糊渡娃' # 这个属性是类的属性,但所有实例都可以访问
def __init__(self, age=10):
self.age = age # 创建类后,实例独有的属性
s = Student()
print(s.name) # 实例没有name属性,但是可以访问类的name属性
print((Student.name)) # 类的name属性
s.name = '刘德华' # 实例新增name属性,覆盖掉了类的name属性
print(s.name) # 访问的是实例的name属性
print((Student.name))
del s.name # 删除实例的name属性
print(s.name) # 再次访问,因为没有了name,所以访问的是类的name
print(dir(s)) # dir()函数,返回传入对象的所有属性,可以看到实例的独有属性age
print(dir(Student))
结果:(dir()返回所有属性太多,截取最后一部分)
糊渡娃
糊渡娃
刘德华
糊渡娃
糊渡娃
..., '__subclasshook__', '__weakref__', 'age', 'name']
..., '__subclasshook__', '__weakref__', 'name']
所以不建议把类的属性和实例的属性起相同的名字(吐槽下,敲代码起名字实在是太恶心了,尤其是英文不好的情况下(⊙﹏⊙))
2.动态增加实例的属性和方法及限制
python是动态语言,所以可以在实例创建后添加属性和方法,添加属性很简单,直接属性赋值即可。
代码:
class Student():
pass
s = Student()
s.name = '大娃'
print(s.name)
还可以添加一个方法
代码:
from types import MethodType # 添加方法要导入MethodType
class Student():
pass
def set_age(self, age): # 定义一个方法
self.age = age
s = Student()
s.name = '大娃'
s.set_age = MethodType(set_age, s) # 添加一个新属性(方法),传入set_age函数和s实例
s.set_age(18) # 调用函数,注意调用函数的时候同时给s实例新增加age属性
print(s.name)
print(s.age)
结果:
大娃
18
需要注意的是,对A实例添加的属性和方法,对B实例是无效的,若要所有实例都有效,则需要添加属性和方法到类中
代码:
...
def set_address(self, address): # 定义新的方法
self.address = address
Student.set_address = set_address # 直接将函数名赋值给类需要添加的方法名即可
s1 = Student()
s2 = Student() # 创建两个实例
s1.set_address('china') # 调用方法
s2.set_address('American')
print(s1.address)
print(s2.address)
结果:
china
American
若要对可添加的属性进行限制,可使用特殊变量__slots__
,例如
(PS:只限制实例的属性,类的属性不受限制)
代码:低版本可能需要继承object
class Student(object):
__slots__ = ('name', 'age') # 限制了实例,只能添加(或声明)name和age属性
s = Student()
s.name = '大娃'
s.age = 18
s.address = 'china' # 此句会报错
__slots__
变量对继承该类的子类是无效的,除非子类也声明了__slots__
变量,此时,子类能添加的属性既有父类受限制的还有自身受限制的
3.@property代替get和set方法
之前记录了类中属性的set()
和get()
方法,以防止外部随意的更改数据和无效的数据值,可用装饰器来简化代码,例如
代码:
class Student(object):
@property
def age(self): # get()方法,只需要加上@property即可
return self._age
@age.setter
def age(self, age): # set()方法,@get()方法的函数名.setter
if isinstance(age, int) and 0 < age < 100: # 对传入的数据限制
pass
else:
raise ValueError('错误的年龄') # 主动抛出异常,中断程序
self._age = age
s = Student()
s.age = 19
# s.age = 101
# s.age = -1
print(s.age)
结果:
19
# ValueError: 错误的年龄
# ValueError: 错误的年龄
@property
即为将函数声明为可读,@age.setter
即为将函数声明为可写,可单独只声明只读,跟java类似
4.类的多继承
通过多重继承,一个子类可以同时获得多个父类的所有功能。
代码:
class Person():
def say(self):
print ('说话')
class Child():
def study(self):
print ('玩耍')
class Student(Person, Child): # 多继承加逗号往后添加即可
pass
s = Student()
s.say() # 调用父类的say方法
s.study() # 调用父类的study方法
结果:(如果继承的多个类中的方法同名,则执行第一个继承的类的方法)
说话
玩耍
这样通过多继承的方法来设计的程序叫做MixIn
,一般把另外继承的类(即需要添加功能)名称加上MixIn
,例如ChildMixIn
,这样写程序的时候不需要考虑深层次的继承关系,只需要考虑功能组合,简化代码编写。
另:java不允许多继承
5.定制类
python里类似__xxx__
这样的变量是有特殊用途的,比如说限制实例可添加属性的__slots__
变量。以下列举其他几个特殊变量的使用
5.1__len__()
获取一个字符串的长度,使用函数len()
,其实是调用了传入参数对象的内部的__len__()
方法。例如
代码:
print (len('qwer'))
等价于
print ('qwer'.__len__())
所以只要在对象内定义__len()__
函数,即可使用len()
函数来调用该对象
代码:
class Person():
def __init__(self, name=''): # 构造函数中默认name=''
self.name = name
def __len__(self): # 调用len()函数,该类的实例传入的时候,会调用此方法
return len(self.name)
p1 = Person()
p2 = Person('Tom')
print (len(p1)) # 对象内部必须声明__len__()函数,否则无法调用len()
print (len(p2))
结果:
0
3
5.2__str__()
代码:
class Person():
def __init__(self, name, age, address):
self.name = name
self.age = age
self.address = address
def __str__(self):
return '名字是' + self.name + ',年龄是' + str(self.age) + ',地址是' + self.address
p = Person('Jack', 18, 'China')
print (p)
结果:
名字是Jack,年龄是18,地址是China
如果去掉__str__()
函数,则打印
<__main__.Person instance at 0x1068a5ab8>
其实就是跟java中的toString()
方法类似
5.3__iter__()
和__next__()
列表和元组可以被用于for...in...
循环,是因为对象内部有__iter__()
和__next__()
方法,如果一个对象要想能够被for循环,可在类内部定义这两个方法。例如
代码:
class Person(object):
def __init__(self, name=''): # 自身属性list用于存放传进来的实例,i表示迭代次数
self.name = name
self.i = -1
def addmember(self, *mem):
self.list = mem
def __iter__(self): # 返回迭代的对象,自身就是迭代对象
return self
def __next__(self): # for循环时调用next函数
if self.i == len(self.list) - 1: # 做迭代停止时的判断
raise StopIteration()
else:
self.i += 1
return self.list[self.i] # 从存储的列表中取出实例返回,正序倒序可自己写逻辑
class Student(Person): # 定义了3个类,都继承Person,都有name属性
pass
class Teacher(Person):
pass
class Trader(Person):
pass
stu = Student('ZhangSan')
tea = Teacher('LiSi')
tra = Trader('WangWu')
p = Person()
p.addmember(stu, tea, tra) # 将实例添加到p的属性list里
for mem in p: # P定义了__iter__()和__next__(),所以可以被循环
print (mem.name)
结果:
ZhangSan
LiSi
WangWu
5.4__getitem__
在上个代码中,如果使用p[0].name
则会抛出错误信息TypeError: 'Person' object does not support indexing
,此时Person只能循环,但不能像列表和元组一样用索引直接访问某个元素,此时,可在类中定义方法__getitem__()
方法,例如
代码:(添加方法__getitem__()
)
def __getitem__(self, item): # 第二个参数是下标,或者切片
return self.list[item]
...
print (p[0].name) # 此时即可用索引访问元素
结果:
ZhangSan
5.5__getattr__()
调用类的方法或者属性的时候,如果该方法或者属性不存在,就会调用类中的__getattr__()
函数,需要这样功能的类,可定义此函数,并做相应处理
5.6__call__()
当调用实例方法或者属性的时候使用xxx.xxx或xxx.xxx()
,其实实例也可以自己直接调用,例如xxx()
,前提是是类内部定义了__call__()
方法,例如
代码:
class Person():
def say(self):
print ('实例调用方法')
def __call__(self, *args, **kwargs):
print ('直接调用')
p = Person()
p.say()
p()
结果:
实例调用方法
直接调用
能被调用的对象都是一个Callable
,判断是否是可调用对象用callable()
函数,例如
代码:
...
print (callable(Person)) # Person类
print (callable(Person())) # Person类的实例
print (callable(abs)) # abs()函数名(变量)
print (callable('asdf'))
结果:
True
True
True
False