一、面向对象介绍
对象: 一个程序的基本单元, 一个对象包含了数据和操作数据的方法
1.1 基础概念
- 类(class):对具有相同数据或者方法的一组对象的集合
- 对象(obj):对象是一个类的具体事例
- 实例化:由类生成一个对象. 实例具有类的所用属性和方法.
- 类属性:属于一个类中所有对象的属性
- 实例属性:一个对象就是一组属性的集合
- 类方法:那些无须特定的对性实例就能够工作的从属于类的函数
- 实例(对象)方法:所有存取或者更新对象某个实例一条或者多条属性函数的集合
1.2 认识类
类提供了一种组合数据和功能的方法。 创建一个新类意味着创建一个新的对象 类型,从而允许创建一个该类型的新 实例 。 每个类的实例可以拥有保存自己状态的属性。 一个类的实例也可以有改变自己状态的(定义在类中的)方法。
Python 的类提供了面向对象编程的所有标准特性:
- 类继承机制允许多个基类
- 派生类可以覆盖它基类的任何方法
- 一个方法可以调用基类中相同名称的的方法
- 对象可以包含任意数量和类型的数据。
- 类也拥有 Python 天然的动态特性:它们在运行时创建,可以在创建后修改。
在python中一切皆对象, 类型的本质就是类, 类本身也是对象
1.3 名称和对象
- 对象具有个性,多个名称(在多个作用域内)可以绑定到同一个对象, 这在其他语言中称为别名
- 别名对于不可变类型(数字、字符串、元组)可以安全地忽略它.
- 别名对与可变对象(列表、集合、字典等), 在某些方面表现得像指针
1.4 python作用域和命名空间
1.4.1 命名空间(namespace
)
namespace(命名空间)
是一个从名字到对象的映射。 大部分命名空间当前都由 Python
字典实现,但一般情况下基本不会去关注它们(除了要面对性能问题时),而且也有可能在将来更改
常见命名空间
- 存放内置函数的集合(包含
abs()
这样的函数,和内建的异常等) - 模块中的全局名称
- 函数调用中的局部名称
- 从某种意义上说,对象的属性集合也是一种命名空间的形式
- 不同命名空间中的名称之间绝对没有关系;例如,两个不同的模块都可以定义一个 maximize 函数而不会产生混淆 --- 模块的用户必须在其前面加上模块名称。
属性
- 我把任何跟在一个点号(
.
)之后的名称都称为 属性 --- 例如,在表达式 z.real 中,real 是对象 z 的一个属性
1. 对模块中名称的引用属于属性引用
2. 在表达式modname.funcname
中,modname
是一个模块对象而funcname
是它的一个属性。在此情况下在模块的属性和模块中定义的全局名称之间正好存在一个直观的映射:它们共享相同的命名空间
3. 属性可以是只读或可写的。如果为可写的,那么对属性的赋值是可行的, 删除属性也是可行的
命名空间的生成周期
- 在不同时刻创建的命名空间拥有不同的生存期
1. 包含内置名称的命名空间是在 Python 解释器启动时创建的,永远不会被删除
2. 模块的全局命名空间在模块定义被读入时创建;通常,模块命名空间也会持续到解释器退出
3. 一个函数的本地命名空间在这个函数被调用时创建,并在函数返回或抛出一个不在函数内部处理的错误时被删除。每次递归调用都会有它自己的本地命 名空间
1.4.2 作用域
作用域 是一个命名空间可直接访问的 Python 程序的文本区域。 这里的 “可直接访问” 意味着对名称的非限定引用会尝试在命名空间中查找名称。
作用域被静态确定,但被动态使用。 在程序运行的任何时间,至少有三个命名空间可被直接访问的嵌套作用域
- 最先搜索的最内部作用域包含局部名称
- 从最近的封闭作用域开始搜索的任何封闭函数的作用域包含非局部名称,也包括非全局名称
- 倒数第二个作用域包含当前模块的全局名称
- 最外面的作用域(最后搜索)是包含内置名称的命名空间
全局作用域
- 如果一个名称被声明为全局变量,则所有引用和赋值将直接指向包含该模块的全局名称的中间作用域
- 要重新绑定在最内层作用域以外找到的变量,可以使用
nonlocal
或global
语句声明为非本地变量 - 如果没有被声明为非本地变量,这些变量将是只读的(尝试写入这样的变量只会在最内层作用域中创建一个 新的 局部变量,而同名的外部变量保持不变)。
i = 10
def func():
i = 20
print("函数内部:",i)
print("函数外部", i)
func()
输出结果:
函数外部 10
函数内部: 20
作用域实例
def scope_test():
def do_local():
spam = "local spam"
def do_nonlocal():
nonlocal spam
spam = "nonlocal spam"
def do_global():
global spam
spam = "global spam"
spam = "test spam"
do_local()
print("After local assignment:", spam) # 内层作用域的命名,不会修改外层作用域
do_nonlocal()
print("After nonlocal assignment:", spam) # 声明非本地变量后, 可以在内层作用域修改上层作用域变量
do_global()
print("After global assignment:", spam) # 声明为全局变量后, 可在局部作用域中修改全局作用域内的变量
scope_test()
print("In global scope:", spam)
输出:
After local assignment: test spam
After nonlocal assignment: nonlocal spam
After global assignment: nonlocal spam
In global scope: global spam请注意 局部 赋值(这是默认状态)不会改变 scope_test 对 spam 的绑定。 nonlocal 赋值会改变 scope_test 对 spam 的绑定,而 global 赋值会改变模块层级的绑定。
二、类
python使用关键字class
定义类. 类名的命名采用大驼峰命名法
语法如下:
class ClassName:
class_attr = value # 类属性
def object_method_name(self, args, key=value, *args, **kwargs): # 实例方法
pass
- 类定义与函数定义 (def 语句) 一样必须被执行才会起作用
- 类定义内的语句通常都是函数定义
- 在类内部的函数定义通常具有一种特别形式的参数列表
- 类定义时,将创建一个新的命名空间,并将其用作局部作用域 因此,所有对局部变量的赋值都是在这个新命名空间之内。 特别的,函数定义会绑定到这里的新函数名称。
- 当(从结尾处)正常离开类定义时,将创建一个 类对象
2.1 类对象
类对象支持两种操作:属性引用 和 实例化
2.1.1 属性引用
属性引用 使用 Python 中所有属性引用所使用的标准语法: obj.name
。 有效的属性名称是类对象被创建时存在于类命名空间中的所有名称。 因此,如果类定义是这样的:
class Person:
sex = "male"
name = "dyp"
def test(self):
return "hello test class"
- 那么
Person.sex
和Person.test
就是有效的属性引用 Person.sex
返回字符串对象,Person.test
返回函数对象- 类属性也可以被赋值,因此可以通过赋值来更改
Person.sex
的值
2.1.2 实例化
类的 实例化 使用函数表示法。 可以把类对象视为是返回该类的一个新实例的不带参数的函数。 举例来说(假设使用上述的类):
person = Person()
- 创建类的新 实例 并将此对象分配给局部变量
person
。
实例化操作(“调用”类对象)会创建一个空对象。 很多时候, 喜欢创建带有特定初始状态的自定义实例。 为此类定义可能包含一个名为 __init__()
的特殊方法,就像这样:
class Person:
def __init__(self, name, sex):
self.name = name
self.sex = sex
def test(self):
print("hello world")
person = Person("dyp", "male")
- 当一个类定义了
__init__()
方法时,类的实例化操作会自动为新创建的类实例发起调用__init__()
2.2 实例对象
实例对象理解的唯一操作是属性引用。 有两种有效的属性名称:数据属性 和 方法。
2.2.1 数据属性
数据属性 对应于 Smalltalk 中的“实例变量”,以及 C++ 中的“数据成员”。 数据属性不需要声明;像局部变量一样,它们将在第一次被赋值时产生
class Person:
def __init__(self, name, sex):
self.name = name
self.sex = sex
self.counter = 0
def test(self):
print("hello world")
person = Person("dyp", "male")
while person.counter < 10:
person.counter += 2
print(person.counter)
2.2.2 方法
- 另一类实例属性引用称为 方法。方法是“从属于”对象的函数。在 Python 中,方法这个术语并不是类实例所特有的:其他对象也可以有方法。 例如,列表对象具有 append, insert, remove, sort 等方法
- 实例对象的有效方法名称依赖于其所属的类。 根据定义,一个类中所有是函数对象的属性都是定义了其实例的相应方法
- 方法就是在类中定义的函数.
- 方法被调用时, 会将实例本身由解释器传入到方法的第一个参数.
person.test() = Person.test(person)
2.3 类和实例变量
实例变量用于每个实例的唯一数据,而类变量用于类的所有实例共享的属性和方法:
class Dog:
kind = 'canine' # 类变量, 所有实例共享的数据
def __init__(self, name):
self.name = name # 实例变量, 单个实例的变量
2.4 补充说明
- 如果同样的属性名称同时出现在实例和类中,则属性查找会优先选择实例
- 方法的第一个参数常常被命名为 self。 这也不过就是一个约定: self 这一名称在 Python 中绝对没有特殊含义。
三、属性
3.1 类属性
- 查询类属性
ClassName.class_attr
class Person:
sex = "male"
name = "dyp"
print(Person.name, Person.sex) # 输出 dyp male
- 修改类属性
ClassName.class_attr = value
class Person:
sex = "male"
name = "dyp"
print(Person.name) # 输出 dyp
Person.name = "Jone"
print(Person.name) # 输出Jone
- 增加类属性
ClassName.class_attr = value
class Person:
sex = "male"
name = "dyp"
print(dir(Person))
Person.age = 21
print(dir(Person))
- 删除属性
del ClassName.class_attr
class Person:
sex = "male"
name = "dyp"
del Person.name
print(Person.name) # 报错, 没有name属性
3.2 实例属性
实例 = 类名()
: 实例化, 复制类的所用属性和方法. 一个类可以生成多个实例, 每个实例有单独的内存空间.
- 实例属性查看
实例.obj_attr
class Person:
sex = "male"
name = "dyp"
person = Person() # 实例化Person
print(person.name, person.sex) # 查看实例的属性
- 修改实例属性
实例.obj_attr = value
class Person:
sex = "male"
name = "dyp"
person = Person() # 实例化Person
print(id(Person.name), id(person.name)) # 实例的属性未发生变化时, 引用 类的属性
person.name = "Jone" # 修改属性, 在实例的命名空间中新建一个对象, 赋值给实例属性名
print(id(Person.name), id(person.name)) # 实例属性发生变化后, 新建一个
- 增加实例属性
实例.obj_attr = value
class Person:
sex = "male"
name = "dyp"
person = Person() # 实例化Person
person.age = 20
print(person.age)
- 删除实例属性
del 实例.obj_attr
class Person:
sex = "male"
name = "dyp"
person = Person() # 实例化Person
person.age = 20
del person.age
四、方法
4.1 实例方法
class Cat:
def __init__(self, name): # 类的初始话方法, 当类实例化时, 触发此方法. 用于设置实例的特征属性
self.name = name
def eat(self): # 实例方法, 通过类生成的实例调用. 通过类调用时会报错
print(f"小猫{self.name}, 吃东西了")
cat = Cat("花花")
cat.eat() # 调用方法
五、受保护的属性和方法
只有当前类和当前类生成的实例可以访问, 继承此类的其他类和其生成的实例不能访问.
属性名或方法名前面有一个下划线(_
)成为受保护的属性或方法
class Person:
def __init__(self, name, birthday):
self.name = name
self._birthday = birthday # 受保护的属性, 属性名前面有一个下划线 _
def _age(self):
self.age = 24
person = Person("Jone", "1996-12-22")
print(person._birthday) # 查看受保护的属性
person._birthday = "1996-12-11" # 修改受保护的属性
person._age() # 调用受保护的方法
print(person.age)
六、私有属性和方法
只有类的内部才能访问属性和方法, 用于隐藏隐私信息
属性名或方法名前面有两个下划线(__
)成为私有的属性或方法
可以通过_ClassName__attr
访问私有属性, _ClassName_method()
调用受保护方法
class Cat:
__name = "花花"
def __test(self):
print(f"{self.__name}") # 在类的内部才能访问
def test(self):
self.__test()
cat = Cat()
# print(cat.__name) # 不能被访问
# print(Cat.__name) # 类外部, 类名也是不能访问是由属性或方法的
print(cat._Cat__name) # 私有的属性会被python重命名为 _ClassName__attrName
print(Cat._Cat__name) # 私有的属性会被python重命名为 _ClassName__attrName
cat._Cat__test() # 私有的方法会被python重命名为 _ClassName__methodName