类的定义: class 类名称 ():
class ClassName:
语句块
类对象及类属性
class MyClass: # 定义类
"""axf""" #类注解 也是属性
x = """xabc""" #类属性
def fo(self): #类方法 类方法也属于类属性
print("abcdef")
- 类对象,类的定义执行后会生成一个类对象
- 类的属性,类定义中的变量和类中定义的方法都是类的属性
- 类变量,上例中x是类MyClass的变量
fo是方法method,本质上就是普通的函数对象function,它一般要求至少有一个参数。第一个形式参数可以是self(self只是个惯用标识符,可以换名字),这个参数位置就留给了self(类中所有的普通函数都应该有个self参数)。self 指代当前实例本身
实例生成 : 实例名 = 类名()
class Person:
def __init__(self, name, age):
self.name = name # 把name 赋给实例slef
self.age = age # 把age 赋给实例slef
tom = Person(“tom”,18) #实例化对象1
jerry = Person(“Jerry”,18) #实例化对象2
每次实例化的实例是不同的实例
Python类实例化后,会自动调用__init__方法。这个方法第一个形式参数必须留给self
__init__方法
- MyClass()实际上调用的是__init__(self)方法,可以不定义,如果没有定义会在实例化后隐式调用。作用:对实例进行初始化
- init()方法不能有返回值,也就是只能是return None
- 初始化函数可以多个参数,请注意第一个位置必须是self,例如__init__(self, name, age)
tom = Person("tom",30) # 实例化对象
tom.age #通过对象调用类的属性 __init__方法,进行初始化age
tom.age+=1 #可以对实例对象进行属性操作,当不会改变类的属性(操作对象属性不会改变类属性)
实例变量和类变量
class Person:
age = 25 #类变量(属性)
def __init__(self, name):
self.name = name
Person.age = 26 #类变量(属性),被更改
tom.age >>>26 # 通过实例本身的属性来访问
tom.class.age >>>26 通过类属性来访问
- 实例对象instance
- 类实例化后一定会获得一个类的实例,就是实例对象
- 实例变量是每一个实例自己的变量,是自己独有的;类变量是类的变量,是类的所有实例共享的属性和方法(类变量更改后,新生成的实例都会以新的变量属性生成实例)
- 类属性保存在类的__dict__中,实例属性保存在实例的__dict__中,如果从实例访问类的属性,也可以借助__class__找到所属的类,再通过类来访问类属性
-
总结
- 属性是类的,也是这个类所有实例的,其实例都可以访问到;
- 是实例的,就是这个属性是实例自己的,通过类访问不到。类变量是属于类的变量,这个类的所有实例可以共享这个变量
- 对象(实例或类)可以动态的给自己增加一个属性(赋值即定义一个新属性)。实例.__dict__[变量名]和实例.变量名都可以访问到实例自己的属性(注意这两种访问是有本质区别的)。实例的同名变量会隐藏掉类变量,或者说是覆盖了这个类变量。但是注意类变量还在那里,并没有真正被覆盖
-
实例属性的查找顺序
- 指的是实例使用.点号来访问属性,会先找自己的__dict__,如果没有,然后通过属性__class__找到自己的类,再去类的__dict__中找
- 注意:如果实例使用__dict__[变量名]访问变量,将不会按照上面的查找顺序找变量了,这是指明使用字典的key查找,不是属性查找
- 一般来说,类变量可使用全大写来命名
特殊属性 | 含义 |
---|---|
__name __ | 对象名 |
__class __ | 对象的类型 |
__dict __ | 对象的属性的字典 |
__qualname __ | 类的限定名 |
class Person:
def normal_method():
print('normal')
Person.normal_method() # 当做普通函数调用
# Person().normal_method() #这句运行不了
print(Person.__dict__)
class Person:
def normal_method(self):
print('normal')
Person().normal_method()
类方法和静态方法
__init__方法,方法本身都是类的属性,第一个参数必须是self,而self必须指向一个对象,也就是类实例化之后,由实例来调用这个方法
-
普通函数
Person.normal_method() 可以放在类中定义,因为这个方法只是被Person这个名词空间管理的一个普通的方法,normal_method是Person的一个属性而已。
由于normal_method在定义的时候没有指定self,所以不能完成实例对象的绑定,不能用Person().normal_method()调用 -
类方法
-
在类定义中,使用@classmethod装饰器修饰的方法,用类调用时,会把类作为参数传入方法中,用实例调用,则会把实例的类抽出作为参数传入到调用的方法中
-
必须至少有一个参数,且第一个参数留给了cls,cls指代调用者即类对象自身
-
cls这个标识符可以是任意合法名称,但是为了易读,请不要修改
-
通过cls可以直接操作类的属性
-
-
静态方法
- 在类定义中,使用@staticmethod装饰器修饰的方法
- 调用时,不会隐式的传入参数静态方法,只是表明这个方法属于这个名词空间。函数归在一起,方便组织管理
-
方法的调用
- 类几乎可以调用所有内部定义的方法,但是调用普通的方法时会报错,原因是第一参数必须是类的实例。实例也几乎可以调用所有的方法,普通的函数的调用一般不可能出现,因为不允许这么定义。
class Person:
def method(self):
print("{}'s method".format(self))
tom = Person()
tom.method()
# Person.method() # 不可以,因为类调用普通方法是,需要一个类对象,即一个实例
Person.method(tom)
tom.__class__.method(tom)
--------------------------------------------------------------------------
<__main__.Person object at 0x000001F6EE21CFD0>'s method
<__main__.Person object at 0x000001F6EE21CFD0>'s method
<__main__.Person object at 0x000001F6EE21CFD0>'s method
- 总结:
- 类除了普通方法都可以调用,普通方法需要对象的实例作为第一参数。例如:Person.method(Person())
- 实例可以调用所有类中定义的方法(包括类方法、静态方法),普通方法传入实例自身(Person.method(Person())),静态方法(不需要传入参数,指定义静态函数时的参数)和类方法(自动会传入实例的类,定义类方法时的参数会自动传入cls)需要找到实例的类
class Person:
def method(self):
print("{}'s method".format(self))
@classmethod
def class_method(cls): # cls就是类 类方法,至少有一个参数
print('class = {0.__name__} ({0})'.format(cls))
cls.HEIGHT = 170
@staticmethod
def static_methd(): #无需写入第一参数self
print(Person.HEIGHT)
tom = Person()
tom.method() # 普通方法 >>> <__main__.Person object at 0x000001D3DC0885F8>'s method
tom.class_method() #类方法 >>> class = Person (<class '__main__.Person'>)
tom.static_methd() #静态方法 >>> 170
访问控制
- 使用双下划线开头的属性名,就是私有属性
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 # 加双下划线后,属性变为私有的
def getage(self):
return self.__age
tom = Person('tom')
# tom.age #不能访问,语法错误
# print(tom.age) # 因为私有属性,被保护的,不能再被访问
# print(tom.__age) # 因为私有属性,被保护的,不能再被访问
print(tom.getage())
print(tom.__dict__)
tom.age = 20 # 赋值即定义
print(tom.__dict__) # 注意 实例属性字典增加了age键值对,此键值对可以更改
tom._Person__age = 100 # 赋值即定义,更改类属性 ==修改私有变量==
print(tom.age)
print(tom.getage())
----------------------------------------------------------------
18
{'name': 'tom', '_Person__age': 18}
{'name': 'tom', '_Person__age': 18, 'age': 20}
20
100
- 类定义的时候,如果声明一个实例变量的时候,使用双下划线,Python解释器会将其改名,转换名称为_类名__变量 名的名称,所以用原来的名字访问不到了
保护变量
- 在变量名前使用一个下划线,称为保护变量
class Person:
def __init__(self, name, age= 18):
self.name = name
self._age = age
tom = Person('Tom')
print(tom.__dict__)
---------------------------------------
{'name': 'Tom', '_age': 18} ## 双下划线 输出为:{'name': 'Tom', '_Person__age': 18}
- 私有方法的本质
- 单下划线的方法只是开发者之间的约定,解释器不做任何改变。
- 双下划线的方法,是私有方法,解释器会改名,改名策略和私有变量相同,_类名__方法名。
- 方法变量都在类的__dict__中可以找到
私有成员的总结
在Python中使用 _单下划线或者 __ 双下划线来标识一个成员被保护或者被私有化隐藏起来。但是,不管使用什么样的访问控制,都不能真正的阻止用户修改类的成员。
Python中没有绝对的安全的保护成员或者私有成员。因此,前导的下划线只是一种警告或者提醒,请遵守这个约定。除非真有必要,不要修改或者使用保护成员或者私有成员,更不要修改它们
补丁
可以通过修改或者替换类的成员。使用者调用的方式没有改变,但是,类提供的功能可能已经改变了。
猴子补丁(Monkey Patch):在运行时,对属性、方法、函数等进行动态替换。其目的往往是为了通过替换、修改来增强、扩展原有代码的能力(偷梁换柱)
## test1.py
from test2 import Person
from test3 import get_score
def monkeypatch4Person(cls):
Person.get_score = get_score
monkeypatch4Person(Person) # 打补丁
if __name__ == "__main__":
tom = Person()
print(Person().get_score())
# test2.py
class Person:
def get_score(self):# connect to mysql
ret = {'English':70, 'Chinese':88, 'History':80}
return ret
# test3.py
def get_score(self):
return dict(name=self.__class__.__name__,English=90, Chinese=98, History=95)
属性装饰器
class Person:
def __init__(self, name, age=18):
self.name = name
self.__age = age
print(Person.__dict__)
tom = Person('tom')
# print(tom.age) # 私有属性不能访问
# print(tom.__age) # 私有属性不能访问
print(tom.__dict__)
tom.age = 30 #实例属性动态赋值,私有属性并未改变,只是增加实例的属性,赋值即定义
print(tom.__dict__)
print(tom.age)
tom.weight = 100 #实例属性动态赋值
print(tom.weight)
print(tom.__dict__)
print(Person.__dict__) # 类属性并未改变
--------------------------------------------------
{'__module__': '__main__', '__init__': <function Person.__init__ at 0x00000215C0D459D8>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None}
{'name': 'tom', '_Person__age': 18}
{'name': 'tom', '_Person__age': 18, 'age': 30}
30
100
{'name': 'tom', '_Person__age': 18, 'age': 30, 'weight': 100}
{'__module__': '__main__', '__init__': <function Person.__init__ at 0x00000215C0D459D8>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None}
- 要访问或者改变或者删除私有属性可以通过装饰器
- property (就是getter)
- setter
- deleter
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):
# del self.age
print('is del')
-------------------------------------------
tom = Person('tom')
print(tom.age) >>> 18
tom.age = 25
print(tom.age) >>> 25
del tom.age >>> is del
-
特别注意:使用property装饰器的时候这三个方法同名
-
property装饰器后面跟的函数名就是以后的属性名。它就是getter。这个必须有,有了它至少是只读属性
-
setter装饰器与属性名同名,且接收2个参数,第一个是self,第二个是将要赋值的值。有了它,属性可写
-
deleter装饰器可以控制是否删除属性。很少用
-
property装饰器必须在前,setter、deleter装饰器在后。
-
property装饰器能通过简单的方式,把对方法的操作变成对属性的访问,并起到了一定隐藏效果