文章目录
python学习总结9 - 类
类把数据、功能绑定在一起,创建新类就是创建新的对象 类型,从而创建该类型的新 实例 。类实例支持维持自身状态的属性,还支持(由类定义的)修改自身状态的方法。
9.1 Python 作用域和命名空间
- 点号之后的名称是 属性,例如:表达式 z.real 中,real 是对象 z 的属性
- 对模块中名称的引用是属性引用
- 模块属性和模块中定义的全局名称之间存在直接的映射:它们共享相同的命名空间!
- nonlocal 赋值会改变 scope_test 对 spam 的绑定
- global 赋值会改变模块层级的绑定。
def scope_test():
def do_local():
spam = 'local spam'
# 碰到nonlocal,调用本函数,以下面的变量值为主
def do_nolocal():
nonlocal spam
spam = 'nolocal spam'
# global同上,另外global对应的变量值是整个py文件的
def do_global():
global spam
spam = 'global spam'
spam = 'test spam'
do_local()
print("After local assignment:", spam)
do_nolocal()
print("After nonlocal assignment:", spam)
do_global()
print("After global assignment:", spam)
scope_test()
print("In global scope:", spam)
C:\Python38\python.exe C:/Users/X21201/Desktop/python/tiaoce.py
After local assignment: test spam
After nonlocal assignment: nolocal spam
After global assignment: nolocal spam
In global scope: global spam
9.3 初探类
9.3.1. 类定义语法
class ClassName:
<statement-1>
.
.
.
<statement-N>
9.3.2. Class 对象
类对象支持2种操作:属性引用、实例化
class MyClass:
"""A simple example class"""
i = 12345
def f(self):
return 'hello world'
- 属性引用
MyClass.i
和MyClass.f
就是类的属性引用,
- 实例化
- 创建类的新 实例 并将此对象分配给局部变量
x = MyClass()
- 许多类喜欢创建 带有特定初始状态的自定义实例,为此类定义可能包含
__init__()
,当一个类定义了__init__()
方法时,
类的实例化操作会自动为新创建的类实例发起调用 init()
9.3.3. 实例对象
上面把类实例化了,并把实例化的类给了变量X,x = MyClass()
现在我们能用实例对象做什么? 实例对象的唯一操作:属性引用,有两种有效的属性名称:数据属性和方法。
9.3.4. 方法对象
- 调用 x.f() 时并没有带参数,方法的特殊之处就在于实例对象会作为函数的第一个参数被传入
- 调用
x.f()
其实就相当于MyClass.f(x)
class MyClass:
"""A simple example class"""
i = 12345
def f(self):
return 'hello world'
x = MyClass()
x.f()
MyClass.f(x)
9.3.5. 类和实例变量
- 一般来说,实例变量用于每个实例的唯一数据,正确的类设计应该使用实例变量,如:
class Dog:
# tricks = [] # 错误的类变量使用
def __init__(self, name):
self.name = name
self.tricks = [] # 正确的类变量使用
def add_trick(self, trick):
self.tricks.append(trick)
d = Dog('旺财')
d.add_trick('小跑')
print(d.tricks) # ['小跑']
e = Dog('泰迪')
e.add_trick('冲刺')
print(d.tricks) # ['小跑']
- 错误的类变更使用,类变量用于类的所有实例共享的属性和方法,如下类变量中的
tricks
被所有实例调用,如下:
class Dog:
tricks = [] # mistaken use of a class variable
def __init__(self, name):
self.name = name
def add_trick(self, trick):
self.tricks.append(trick)
d = Dog('旺财')
d.add_trick('小跑')
print(d.tricks) # ['小跑']
e = Dog('泰迪')
e.add_trick('冲刺')
print(d.tricks) # ['小跑', '冲刺']
9.4. 补充说明
如果同样的属性名称同时出现在实例和类中,则属性查找会优先选择实例,如下:
class Warehouse:
purpose = 'storage'
region = 'west'
w1 = Warehouse()
print(w1.purpose, w1.region)
w2 = Warehouse()
w2.region = '改值了'
print(w2.region, w2.purpose)
C:\Python38\python.exe C:/Users/X21201/Desktop/python/tiaoce.py
storage west
改值了 storage
方法可以通过使用 self 参数的方法属性调用其他方法,例如:
class Bag:
def __init__(self):
self.data = []
def add(self, x):
self.data.append(x)
def addtwice(self, x):
self.add(x)
self.add(x)
任何一个作为类属性的函数都为该类的实例定义了一个相应方法,函数定义的文本并非必须包含于类定义之内:将一个函数对象赋值给一个局部变量也是可以的,例如:
# 函数f1被定义到了C类外面
def f1(self, x, y):
return min(x, x+y)
class C:
f = f1
def g(self):
return 'hello world'
h = g
9.5. 继承
利用继承机制,新的类可以从已有的类中派生。那些用于派生的类称为这些特别派生出的类的“基类”。如果不支持继承,语言特性就不值得称为“类”。
- 派生类定义的语法:
class DerivedClassName(BaseClassName):
<statement-1>
.
.
.
<statement-N>
- 允许用其他任意表达式代替基类名称所在的位置,当基类定义在另一个模块中的时候:
class DerivedClassName(modname.BaseClassName):
- 派生类定义的执行过程与基类相同,如果请求的属性在类中找不到,搜索将转往基类中进行查找。 如果基类本身也派生自其他某个类,则此规则将被递归地应用。
- 派生类可能会重写其基类的方法,因为方法在调用同一对象的其他方法时没有特殊权限,基类方法最终可能会调用覆盖它的派生类的方法,
有一种方式可以简单地直接调用基类方法:即调用BaseClassName.methodname(self, arguments)
- Python有两个内置函数可被用于继承机制: isinstance() \ issubclass()
9.5.1. 多重继承
Python 也支持一种多重继承。 最简单的情况下,你可以认为搜索从父类所继承属性的操作是深度优先、从左至右的,当层次结构中存在重叠时不会在同一个类中搜索两次。,
带有多个基类的类定义语句如下所示:
class DerivedClassName(Base1, Base2, Base3):
<statement-1>
.
.
.
<statement-N>
要了解更多细节,请参阅 https://www.python.org/download/releases/2.3/mro/
9.6. 私有变量
大多数 Python 代码都遵循这样一个约定:
_spam
:无论它是函数、方法、数据成员(类属性),带有一个下划线的名称 (例如 _spam) 应该被当作是API 的非公有部分
,__spam
:由于存在对于类私有成员的有效使用场景(例如避免名称与子类所定义的名称相冲突),因此存在对此种机制的有限支持,称为名称改写
。 任何形式为 __spam 的标识符(至少带有两个前缀下划线,至多一个后缀下划线)的文本将被替换为 _classname__spam,其中 classname 为去除了前缀下划线的当前类名称。 这种改写不考虑标识符的句法位置,只要它出现在类定义内部就会进行。改写规则的设计主要是为了避免意外冲突;访问或修改被视为私有的变量仍然是可能的
class Mapping:
def __init__(self, iterable):
self.items_list = []
# self.__update(iterable)
self.__update = 'tengfei'
def update(self, iterable):
for item in iterable:
self.items_list.append(item)
# __update = update # private copy of original update() method
class MappingSubclass(Mapping):
def __init__(self, iterable):
super().__init__(iterable)
self.__update = 'jiao'
print(self.__update)
def update(self, keys, values):
# provides new signature for update()
# but does not break __init__()
for item in zip(keys, values):
self.items_list.append(item)
print(self.items_list)
x = MappingSubclass('腾哥儿')
print(x)
当注释掉self.__update
后运行报错:AttributeError: 'MappingSubclass' object has no attribute '_MappingSubclass__update'
,上面的示例即使在 MappingSubclass
引入了一个 __update
标识符的情况下也不会出错,因为它会在 Mapping
类中被替换为 _Mapping__update
而在 MappingSubclass
类中被替换为 _MappingSubclass__update
9.7. 杂项说明
将一些命名数据项捆绑在一起。 这种情况适合定义一个空类:
class Employee:
pass
john = Employee()
john.name = 'John Doe'
john.dept = 'computer lab'
john.salary = 1000
john.jiao = 'jiao'
print(john.name)
一段需要特定抽象数据类型的 Python 代码往往可以被传入一个模拟了该数据类型的方法的类作为替代。 例如,如果你有一个基于文件对象来格式化某些数据的函数,你可以定义一个带有 read() 和 readline() 方法从字符串缓存获取数据的类,并将其作为参数传入。
9.8 迭代器
for element in [1,2,3]:
print(element)
for element in (1,2,3):
print(element)
for key in {'one':1, 'two':2}:
print(key)
for char in '123':
print(char)
for line in open('myfile.txt'):
print(line, end='')
for 语句会在容器对象上调用 iter()
,该函数返回一个定义了 __next__()
方法的迭代器对象,此方法逐一访问容器中的元素,当元素用尽时,__next__()
将引发 StopIteration
异常来通知终止 for 循环,如下运作机制:
it = iter('adc')
print(it)
print(next(it))
print(next(it))
print(next(it))
print(next(it))
给类添加迭代器,定义一个 __iter__()
方法来返回一个带有 __next__()
方法的对象。 如果类已定义了 __next__()
,则 __iter__()
可以简单地返回 self
class Reverse:
"""用于向后循环序列的迭代器。"""
def __init__(self, data):
self.data = data
self.index = len(data)
def __iter__(self):
return self
def __next__(self):
if self.index == 0:
raise StopIteration
self.index = self.index - 1
return self.data[self.index]
rev = Reverse('spam')
iter(rev)
for char in rev:
print(char)
9.9. 生成器
generator – 生成器
- 返回一个 generator iterator 的函数。它看起来很像普通函数,不同点在于其包含 yield 表达式以便产生一系列值供给 for-循环使用或是通过 next() 函数逐一获取。
- 通常是指生成器函数,但在某些情况下也可能是指 生成器迭代器。如果需要清楚表达具体含义,请使用全称以避免歧义。
- 生成器 是一个用于创建迭代器的简单而强大的工具。它们的写法类似于标准的函数,但当它们要返回数据时会使用 yield 语句。 每次在生成器上调用 next() 时,它会从上次离开的位置恢复执行(它会记住上次执行语句时的所有数据值)。
def reverse(data):
for index in range(len(data)-1, -1, -1):
yield data[index]
for char in reverse('golf'):
print(char)
- 可以用生成器来完成的操作同样可以用前一节所描述的基于类的迭代器来完成。 但生成器的写法更为紧凑,因为它会自动创建
__iter__()
和__next__()
方法
9.10 生成器表达式
某些简单的生成器可以写成简洁的表达式代码,类似于列表推导式,但外层为圆括号而非方括号。 这种表达式被设计用于生成器将立即被外层函数所使用的情况。
a = sum(i*i for i in range(10))
print(a)
xvec = [10,20,30]
yvec = [7,5,3]
b = sum(x*y for x,y in zip(xvec,yvec))
print(b)
# unique_words = set(word for line in page for word in line.split())
# valedictorian = max((student.gpa, student.name) for student in graduates)
data = 'golf'
c = list(data[i] for i in range(len(data)-1, -1, -1))
print(c)
C:\Python39\python.exe C:/Users/mc/Desktop/python基础/tiaoce.py
285
260
['f', 'l', 'o', 'g']