类的定义:
class ClassName:
block
1、必须使用class关键字
2、类名必须是用大驼峰来命名
3、类定义完成后,就产生了一个类对象,绑定到了ClassName上。
定义一个类;
class MyClass:
x = 'abc' #类的属性
def foo(self): #类的方法
return 'My Class'
print(MyClass.x)
print(MyClass.foo)
print(MyClass.__doc__)
类对象及类属性:
类对象;类的定义就会生成一个类对象
类的属性:类定义中的变量和类中定义的方法都是类的属性
类变量:x是类MyClass的变量
实例化:
a = MyClass() #实例化
在类对象名称后面一个括号,就调用类的实例化方法。
实例化就真正创建一个该类的对象(实例)
实例化后获得的实例,是不同的实例,即使是使用同样的参数实例化,也得到不一样
的对象。python实例化后,会自动调用__init__方法。第一个参数必须留给self
__init__方法:
MyClass() 实际上调用的是__init__(self)方法,可以不定义,如果没有定义会在实例化
后隐式调用。它可以对实例进行初始化。
class MyClass:
def __init__(self):
print('init')
a = MyClass() #会调用__init__
初始化函数可以多个参数,但第一个必定self
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def showage(self):
print('{} is {}'.format(self.name, self.age))
tom = Person('tom', 20) #实例化
jerry = Person('Jerry', 25)
print(tom.name, jerry.age)
jerry.age += 1
print(jerry.age)
jerry.showage()
tom 25
26
Jerry is 26
__init__() 方法不能有返回值,也就是只能是None
实例化对象 instance
类实例化后一定会获得一个对象,就是实例对象
tom、jerry就是Person类的实例
__init__方法的第一个参数self就是指代某一个实例
类实例化出一个实例对象,实例对象会绑定方法,调用方法时采用Jerry.showage()的方式
showage(self),这个self就是jerry,Python会把方法的调用者作为第一参数self的实参传入
self.name就是jerry对象的name,name是保存在了jerry对象上,而不是Person类上,所以称为实例变量
self:
class MyClass:
def __init__(self):
print('self in init = {}'.format(id(self)))
c = MyClass()#调用__init__
print('c = {}'.format(id(c)))
运行结果:
self in init = 2481397483112
c = 2481397483112
self就是调用者,就是c对应的实例对象。
实例变量和类变量:
class Person:
age = 3
def __init__(self, name):
self.name = name
tom = Person('tom')
jerry = Person('Jerry')
print(tom.name, tom.age)
print(jerry.name, jerry.age)
print(Person.age)
print(Person.name)
Person.age = 30
print(Person.age, tom.age, jerry.age)
运行结果:
tom 3
jerry 3
3
30 30 30
实例变量是每一个实例自己的变量,是自己独有的;类变量是类的变量,是类的所有实例共享的属性和方法
__name__ 对象名
__class__ 对象的类型
__dict__ 对象的属性的字典
__qualname__ 类的限定名
class Person:
age = 3
def __init__(self, name):
self.name = name
print('-----class-------')
print(Person.__class__)#类对象类型
print(sorted(Person.__dict__.items()), end='\n\n') #属性字典
tom = Person('Tom')#实例化
print('----isinstance tom -----')
print(tom.__class__)
print(sorted(tom.__dict__.items()), end = '\n\n')
print("------tom 's class --------")
print(tom.__class__.__name__)
print(sorted(tom.__class__.__dict__.items()), end='\n\n')
类属性保存在类的__dict__中,实例属性保存在实例的__dict__中,如果从实例访问类的属性,
就需要借助__class__找到所属的类
class Person:
age = 3
height = 170
def __init__(self, name, age = 19):
self.name = name
self.age = age
tom = Person('Tom') #实例化
jerry = Person('jerry', 20)
Person.age = 30
print(Person.age, tom.age, jerry.age)
# 30 19 20
print(Person.height, tom.height, jerry.height)
#170 170 170
jerry.height = 175
print(Person.height, tom.height, jerry.height)
#170 170 175
tom.height += 15
print(Person.height, tom.height, jerry.height)
#170 185 175
Person.height +=70
print(Person.height, tom.height, jerry.height)
#240 185 175
print(tom.__dict__['height'])
#185
总结:
是类的,也是这个类所有实例的,其实例都可以访问到。
是实例的,就是这个实例自己的,通过类访问不到
类变量属于类的变量,这个类的所有实例可以共享这个变量
实例可以动态的给自己增加一个属性。实例.__dict__['变量名']
和实例.变量名都可以访问到。
实例的同名变量会隐藏类变量,或者覆盖类变量
实例属性的查找顺序:
实例使用.来访问属性,会先找自己的__dict__,如果没有,然后
通过属性__class__找到自己的类,再去类的__dict__中找
如果实例使用__dict__['变量名']访问变量,将不会按照上面的查找顺序找变量了
装饰一个类:
为一个类通过装饰,增加一些类属性
#增加类变量
def add_name(name, clz):
clz_Name = name #动态增加类属性
def add_name(name):
def wrapper(clz):
clz.Name = name
return clz
return wrapper
@add_name('Tom')
class Person:
AGE = 3
print(Person.Name)
之所以可以能够装饰,本质上是为类对象动态的增加了一个属性,而Person这个标识符指向这个类对象
类方法和静态方法:
前面的例子中定义的__init__等方法,这个方法本身都是类的属性,第一个参数必须是self,而self
必须指向一个对象,也就是类必须实例化后,由实例来调用这个方法。
普通函数:
class Person:
def normal_method():
print('normal')
Person.normal_method()
Person().normal_method()#bug
print(Person.__dict__)
Person.normal_method()
可以,因为这个方法只是被Person这个名词空间管理的一个普通的方法,normal_method这是Person的一个属性
由于normal_method在定义的时候没有指定self,所以不能完成实例对象的绑定,不能用Person().normal_method()调用
语法虽然正确,但很少人这样用。
类方法:
class Person:
@classmethod
def class_method(cls):
print('class = {0.__name__}({0})'.format(cls))
cls.HEIGHT= 180
Person.class_method()
print(Person.__dict__)
1.在类定义中,使用@classmethod装饰器修饰的方法。
2.必须至少有一个参数,第一个参数默认留给cls,cls指代调用者即类对象自身
3.cls这个标识符可以是任何合法名称,但是一般默认cls
4.通过cls可以直接操作类的属性
静态方法:
class Person:
@classmethod
def class_method(cls):
print('class = {0.__class__}({0})'.format(cls))
cls.HEIGHT = 180
@staticmethod
def static_method():
print(Person.HEIGHT)
Person.class_method()
Person.static_method()
print(Person.__dict__)
1.在类定义中,使用@staticmethod装饰器修饰的方法
2.调用时,不会隐式的传入参数
静态方法,只是表明这个方法属于这个名次空间。函数归在一起,方便组织管理
方法的调用:
类几乎可以调用所有内部定义的方法,但是调用普通的方法时会报错,因为第一参数必须是类的实例
实例也几乎可以调用所有的方法,普通函数的调用一般不可能出现,因为不允许这么定义
总结:
类除了普通方法都可以调用,普通方法需要对象的实例作为第一个参数
实例可以调用所有类中定义的方法(包括类、静态方法),普通方法传入实例自身,静态方法和类
方法需要找到实例的类。
访问控制:
私有属性:
class Person:
def __init__(self, name, age = 18):
self.name = name
self.age = age
def growup(self, i=1):
if i>0 and i < 150:
self.age += i
p1 = Person('tom')
p1.growup(20)#在范围内
p1.age = 160#超出范围 绕过循环
print(p1.age)
本来是通过方法控制属性,但是由于属性在外部可以访问,就直接绕过方法,直接修改属性
python的私有属性就可以解决这个问题。
私有属性:
使用双下划线开头的属性名,就是私有属性
class Person:
def __init__(self, name, age = 19):
self.name = name
self.__age = age
def growup(self, i = 1):
if i>0 and i < 150:
self.__age += i
p1 = Person('tom')
p1.growup(20)
print(p1.__age)
外部访问不到__age,age没有定义,也访问不到。
使用如下方法来访问:
class Person:
def __init__(self, name, age = 19):
self.name = name
self.__age = age
def growup(self, i =1):
if i>0 and i<150:
self.__age += i
def getage(self):
return self.__age
print(Person('tom').getage())
>>>19
私有变量的本质:
外部访问不到,能够动态增加一个__age
29
39
{'__age': 29, '_Person__age': 39, 'name': 'tom'}
类定义的时候,如果声明一个实例变量的时候,使用双下划线,
python解释器会将其改名,转换名称为_类名__变量名的名称,
所以原来的名字访问不到了。
修改其值:
p1._Person__age = 15
print(p1.getage())
print(p1.__dict__)
15
{'name': 'tom', '__age': 29, '_Person__age': 15}
知道了私有变量的新名称可以直接从外部访问到并修改其值
保护变量:
在变量名前使用一个下划线,称为保护变量
class Person:
def __init__(self, name, age = 19):
self.name = name
self._age = age
tom = Person('Tom')
print(tom._age)
print(tom.__dict__)
19
{'_age': 19, 'name': 'Tom'}
这个_age属性没有改变名称和普通的属性一样,解释器不做任何特殊处理。
这只是开发者共同的约定。如同私有变量不要直接使用
私有方法:
参照保护变量、私有变量,使用单下划线、双下划线命名方法:
class Person:
def __init__(self, name, age = 18):
self.name = name
self._age = age
def _getname(self):
return self.name
def __getage(self):
return self._age
tom = Person('Tom')
print(tom._getname())
#print(tom.__getage())
print(tom.__dict__)
print(tom.__class__.__dict__)
print(tom._Person__getage())
单下划线的方法只是开发者的约定,解释器不做任何改变
双下划线的方法,是私有方法,解释器会改名,改名策略和私有变量相同,
_类名__方法名。方法变量都在类的__dict__中可以找到。
私有成员的总结:
在python中,单下划线或者双下划线来标识一个成员被保护或者被私有化隐藏起来。
补丁:
猴子补丁:
在运行时,对属性进行动态替换。(黑魔法)
#test1.py
from test2 import Person
from test3 import get_score
def monkeypatch4Person():
Person.get_score = get_score
monkeypatch4Person() #打补丁
if __name__ = "__main__":
print(Person().get_scrore())
#test2.py
class Person:
def get_score(self):
ret = {'English':77, 'Chinese':86, 'History':82}
#test3.py
def get_score(self):
return dict(name = self.__class__.__name__, English = 99, Chinese = 78,History = 65)
Person类get_score方法是从数据库拿数据,但是测试的时候,不方便。
使用猴子补丁,替换了get_score方法,返回模拟的数据。
属性装饰器:
把实例的属性保护起来,不让外部直接访问,外部使用getter读取属性和setter方法设置属性
class Person:
def __init__(self, name, age = 14):
self.name = name
self.__age = age
def age(self):
return self.__age
def set_age(self, age):
self.__age = age
tom = Person('tom')#实例化
print(tom.age())
tom.set_age(20)
print(tom.age())
# In[5]:
'使用装饰器'
class Person:
def __init__(self, name , age = 18):
self.name = name
self.__age = age
@property
def age(self):
return self.__age
@age.setter
def age(self, age):
self.__age = age
@age.deleter
def age(self):
print('del')
tom = Person('tom')
print(tom.age)
tom.age = 20
print(tom.age)
# # 类装饰器
# property 装饰器:
# 后面跟的函数名就是以后的属性名。它就是getter,必须有这个,拥有后只读属性
# setter装饰器:
# 与属性名同名,接收2个参数,第一个self,第二个是将要赋值的参数,属性就可写
# deleter装饰器:
# 可以控制属性是否删除 很少用
#
# property装饰器必须在前,setter、deleter装饰器在后。
# property装饰器能通过简单的方式,把对方法的操作变成对属性的访问,有一定的隐藏效果。
#
# In[12]:
'其他写法'
class Person():
def __init__(self, name, age=19):
self.name = name
self.__age = age
def getage(self):
return self.__age
def setage(self, age):
self.__age = age
def delage(self):
print('del')
age = property(getage, setage, delage, 'age property')
tom = Person('tom')
print(tom.age)
tom.age = 20
print(tom.age)
del tom.age
'lambda写法'
class Person:
def __init__(self, name, age = 19):
self.name = name
self.__age = age
age = property(lambda self:self.__age)
tom = Person('tom')
print(tom.age)
# # 对象的销毁:
# 类中可以定义__del__方法,称为析构函数(方法)。
# 作用:
# 销毁类的实例的调用,以释放占用的资源
# 由于Python实现了辣鸡回收机制,这个方法不能确定何时执行。有必要时,请使用del语句
# 删除实例。
#
# In[15]:
class Person:
def __init__(self, name, age = 19):
self.name = name
self.__age = age
def __del__(self):
print('delete {}'.format(self.name))
tom = Person('tom')
del tom
# # 方法重载
# 在其他面向对象的高级语言中,都有重载的概念
# 所谓重载,就是同一个方法名,但是参数数量,类型不一样。就是同一个方法的重载
# python中没有重载。
# python在方法定义中,形参非常灵活,不必需要指定类型(即使指定了也只是一个说明而非
# 约束),参数个数也不固定(可变参数)。一个函数的定义可以实现很多种不同形式实参的调用。
# 所以python不需要方法的重载
# # 封装:
# 面向对象的三大要素之一
# 将数据和操作组织到类中,即属性和方法
# 将数据隐藏起来,给使用者提供操作,使用者通过操作就可以获取或者修改数据。getter和setter
# 通过访问控制,暴露适当的数据和操作给用户,该隐藏的隐藏起来。保护成员或私有成员