Python 元类
模块
模块名.py
(不应使用中文,这里仅做示范)- 如果是放在包里的:
ercilan.模块名
- 这个包是一个目录,目录下需要有标识文件(可为空):
__init__.py
。__init__.py
表示这个目录也是一个模块。
- 可以有多级目录,但如果是要作为包的都需要有
__init__.py
。 - 自己创建的如果与 Python 内部模块重名,则内部模块会被覆盖无法使用。
- Python 模块的标准文件模板:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
'本模块的文档注释' # 任何模块代码的第一个字符串都被视为模块的文档注释;
__author__ = '二次蓝' # 搞了个署名。。
import sys # 通过sys模块可以获取命令行参数
if name == '__main__': # 命令行调用会将__name__设为__main__
print('使用命令行调用了') # 让一个模块通过命令行运行时执行一些额外的代码,常见于运行测试。
sys.exit() # 你的模块代码
- sys 模块有一个
argv
列表变量,储存了调用模块时的命令行的所有参数。第一个值是模块的文件名。- 比如命令行调用
1.py
模块带参数1
,那么sys.argv
:['1.py', '参数1']
- 比如命令行调用
sys.path
包含了解释器查找模块的所有目录。- 写多少个
import test
都只会导入一个 test 模块。
作用域
- 以
__
开头结尾的是特殊变量,自己一般不要使用这种命名; _xxx
和__xxx
这样的函数或变量就是非公开的(private),不应该被直接引用,比如_abc
,__abc
等;- 因为 Python 并无限制访问权限,所以这种命名是一种约定俗成的习惯,应该遵守。
- 在类里面会将
__abc
修改为其他名字,间接实现私有。(单下划线没有)
安装第三方模块
pip install Pillow # 安装Pillow模块
更新 pip:python -m pip install --upgrade pip
- 镜像安装模块:
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple 包名
使用 Anaconda
Anaconda 是一个用于科学计算的 Python 发行版,支持 Linux, Mac, Windows, 包含了众多流行的科学计算、数据分析的 Python 包。
anaconda 英 [ˌænəˈkɒndə] 美 [ˌænəˈkɑːndə] n. 水蟒;蟒蛇
- 可以快捷的获取包并管理,创建不同的环境运行 Python。
- 包含了conda、Python在内的超过180个科学包及其依赖项。
镜像下载:Index of /anaconda/archive/ | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror
推荐阅读:Anaconda介绍、安装及使用教程 - 简书
- Miniconda 是一个 Anaconda 的轻量级替代,默认只包含了 python 和 conda,但是可以通过 pip 和 conda 来安装所需要的包。
Miniconda 安装包可以到 https://mirrors.tuna.tsinghua.edu.cn/anaconda/miniconda/ 下载。
更新源修改
查看已设置软件源:
conda config --show
# 找到channels,-defaults为预设值
删除指定 url:conda config --remove channels url地址
添加更新源:
# 清华大学开源软件镜像
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/r
设置搜索时显示通道地址:
conda config --set show_channel_urls yes
清除索引缓存,保证用的是镜像站提供的索引:conda clean -i
anaconda | 镜像站使用帮助 | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror
类
class Student(object): # object为继承的父类,Python里object类是小写诶
job = 'student' # 类的属性
def __init__(self, name, age):
self.name = name
self.age = age
def get_age(self): # 必须要有self
print(self.age)
__init__
函数相当于 java 中的构造器self
用来表示 java 中的this
,是类的每个函数必需的第一个参数- 实例化时只需要且必须要传入
name
、age
- 注意:与静态语言 Java 不同,动态语言 Python 可以对实例对象动态的添加不同的变量。因此一个类的不同实例可能有不同的属性/变量数量。
stu1 = Student('小蓝', 22) # 实例化一个Student对象
stu1.score = 99 # 给这个对象动态添加属性
类变量和成员变量
--> 类属性和实例属性
直接在 class 下面:job = 'student'
,这个是类的属性;
self.name
是实例属性。
- 类的属性是所有实例可以修改的,大家共享一个。类似于 Java 的
static
。 - 访问类对象使用
Student.job
,无论在类内部还是外面,这样好使。 - 为一个类动态添加方法或属性也这么使用
Student.sing = ...
访问权限限制
将类的变量名前添加 __
即可变成私有变量(private)。
# ...
self.__age = age # 私有变量,访问出现:AttributeError: 'Student' object has no attribute '__age'
# 实际上实例对象的这个变量被改为了 __Student__age
# 要是你自己“修改”:stu1.__age = 18
# 实际上是个这个实例对象添加了属性,而不是修改
#...
- 注意:变量名前后都有
__
的是特殊变量,可以访问,不是私有的 _
单下划线开头 Python 不会修改变量名,但是一般我们也不要去直接访问它
多态
假设我们又写了两个子类 BoyStu 和 GirlStu,继承自 Student。
isinstance(BoyStu, Student)
为True
。可以使用type
- 鸭子类型:理解有这个类型就行了。然后知道下面加粗部分。
def run_twice(student):
student.run()
student.run()
Java 中,调用方法需要传入对象需要为其父类或子类。而动态类型 Python 不同,只要有 run()
函数,就能运行。
- 动态语言的“鸭子类型”:一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。
- 其实也就是动态语言没有强制要求函数的参数类型。有这个方法,就能运行起来。
获取对象信息
- 查询判断变量类型:
value_type = type(o)
# 然后可与以下内置变量对比
types.FunctionType
types.BuiltinFunctionType
types.LambdaType
types.GeneratorType
判断是不是这些类型的其中一种:
# 判断lista是不是其中的一种类型
isinstance(lista, (list, tuple))
- 获取对象的所有属性和方法:
dir(o)
getattr(o, 'x', 404)
:获取对象 o 的 x 属性/方法,不存在返回值:404
hasattr(o, 'x')
:判断对象 o 是否有 x 属性/方法
setattr(o, 'x', 18)
:给对象 o 设置 x 属性为 18(当然也可以设置为方法,变量可以指向函数嘛)
类:限制设置属性
限制 Student 类,只能绑定 name 和 age 属性:
class Student(object):
__slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称
- 注意:子类不会默认继承这个限制,就会无限制。-
- 在子类继续使用
__slots__
,那么子类的限制绑定就是父类的__slot__
加上自己的___slot__
。 - 超出绑定:
AttributeError: 'Student' object has no attribute 'height'
@property
装饰器
可以将方法返回的值变成属性,方法名作为属性名;在这个方法里设置好对属性的检查等。
@property
放在一个方法前,方法的名字表示属性的名字。这个方式是该属性的 getter,返回一个值即可。
# ...
@property
def width(self):
return self._width
# ...
- 这里返回了
self._width
,那么就需要在 setter 里设置这个值 - 这里写的代码是通过中间值
_width
来储存的属性
- 接下来是 setter 的设置,不设置这个的属性是一个可读属性。
@width.setter
def width(self, value): # value是新值
# if ... 等检查值的合法性,raise错误
self._width = value # 最后符合条件的设置值
- 完成,外部可以正常操作对象的属性 width:
# 先调用setter设置属性值
o.width = 11
# 再调用getter获取属性值
print(o.width)
虽然从理论上,self._width
的赋值是在 setter 里的,且外部也是这样的调用顺序。但是因为装饰器要生成 @width.setter
,所以是写在后头的。
类:多重继承
在类的设计时,优先考虑类的多重继承组合功能,而不是设计多层次复杂的单继承。
拓扑、C3算法
- 找的规则:没有被继承的(刚开始就是子类本身)
- 去掉这个类的所有联系,再找上一个(去掉的类括号内继承的父类里调)
- 括号里有多个,看他们定义位置,写在前面的优先继承;有被其他的继承了的类不能找。
class A(object):
def foo(self):
print('A foo')
def bar(self):
print('A bar')
class B(object):
def foo(self):
print('B foo')
def bar(self):
print('B bar')
class C1(A):
pass
class C2(B):
def bar(self):
print('C2-bar')
class D(C1,C2):
pass
if __name__ == '__main__':
print(D.__mro__)
d = D()
d.foo()
d.bar()
(标红表示确定了顺序,需要清空它的联系,划掉它)
D,继承自 C1、C2,这里面找;
C1 写在前面,且 C1 没有再被继承,C2 等待;
C1 括号里为 A,且 A 没有再被继承;
A 继承自 object,object 还被 B 继承了,不能找,到尽头了,切到等待的 C2;
C2 括号里为 B,且 B 没有再被继承;
B 括号里为 object,且没有再被继承;
。。。还是画图吧
(<class '__main__.D'>, <class '__main__.C1'>, <class '__main__.A'>, <class '__main__.C2'>, <class '__main__.B'>, <class 'object'>)
A foo
A bar
类:定制类
- 打印:
__str__
、__repr__
# ...
def __str__(self):
return 'Student object (name=%s)' % self.name
__repr__ = __str__
# ...
__str__
是 Python 调用的str()
函数。__repr__
是命令行模式下,敲一个变量直接回车显示的函数。- 相当于 Java 的
toString()
方法。
- 使得一个类可迭代:
__iter__
、__next__
、raise StopIteration()
class Fib(object): # 斐波那契数列
def __init__(self):
self.a, self.b = 0, 1 # 初始化两个计数器a,b
def __iter__(self):
return self # 实例本身就是迭代对象,故返回自己
def __next__(self):
self.a, self.b = self.b, self.a + self.b # 计算下一个值
if self.a > 100000: # 退出循环的条件
raise StopIteration()
return self.a # 返回下一个值
- 按下标可取出:
__getitem__()
class Fib(object):
# 上面的代码中添加:
def __getitem__(self, n):
a, b = 1, 1
for x in range(n): # 返回 range(0, n-1)
a, b = b, a + b
return a
- 支持切片功能的按下标取出:
__getitem__()
class Fib(object):
def __getitem__(self, n):
if isinstance(n, int): # n是索引
a, b = 1, 1
for x in range(n):
a, b = b, a + b
return a
if isinstance(n, slice): # n是切片
start = n.start
stop = n.stop
if start is None:
start = 0
a, b = 1, 1
L = []
for x in range(stop):
if x >= start:
L.append(a)
a, b = b, a + b
return L
slice
切片类型,有start
、stop
属性。- 除此之外,需要实现与 Python 内置切片相符合的功能,还要做检查是否为负数,间隔取值等等。
__setitem__()
方法,把对象视作list或dict来对集合赋值。
__delitem__()
方法,用于删除某个元素。
__getattr__
:当调用实例对象的属性未找到时,Python 会调用这个方法。
- 可以把一个类的所有属性和方法调用全部动态化处理
- 链式调用,【然后干啥?不太懂什么意思】
class Chain(object):
def __init__(self, path=''):
self._path = path
def __getattr__(self, path):
return Chain('%s/%s' % (self._path, path))
def __str__(self):
return self._path
__repr__ = __str__
试试:
>>> Chain().status.user.timeline.list
'/status/user/timeline/list'
- 这样,无论API怎么变,SDK都可以根据URL实现完全动态的调用,而且,不随API的增加而改变。???
- 将实例对象作为函数对象:
__call__
(这个对象可以()
运行)
class Student(object):
def __init__(self, name):
self.name = name
def __call__(self):
print(f'我的名字是{self.name}.')
s1 = Student('小蓝')() # 我的名字是小蓝
s2 = Student('企鹅')
s2() # 我的名字是企鹅
- 变成了 Callable 对象
- 查看对象是否可被调用:
callable(Student())
类:枚举类
- 创建枚举类
from enum import Enum
Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
Enum('类名', ('对象名1', '对象名2'))
- 它的值是按顺序从数字 1 开始增加
Month.__members__
是储存了所有对象的有序字典 OrderedDict,遍历:
for name, member in Month.__members__.items():
print(name, '=>', member, ',', member.value)
- 需要自定义值的时候就需要自定义类:
from enum import Enum, unique
@unique # 确保值唯一
class Weekday(Enum):
Sun = 0 # Sun的value被设定为0
Mon = 1
Tue = 2
Wed = 3
Thu = 4
Fri = 5
Sat = 6
- 作用:每个常量都是 class 的一个不可更改的唯一实例
- 访问枚举类:
Month.Jan # Month.Jan
Month['Jan'] # Month.Jan
Month(1) # Month.Jan 注意是从1开始的
# 访问值
Month.Jan.value # 1
类:元类
动态创建类
在 Java 中,类的类型是 class
,而 Python 中类的类型是 type
。
实例对象的类型是 ***类。
# Hello是一个类
>>> print(type(Hello))
<class 'type'>
# h是Hello类的实例
>>> print(type(h))
<class 'hello.Hello'>
type()
还可以创建类!跟 class Hello(object):
的功能一样。
def fn(self, name='world'): # 首先要有一个类的方法,创建类的时候使用
print('hello', name)
Hello = type('Hello', (object,), dict(greet=fn)) # 创建Hello class
- 第一个参数是类的名字
- 第二个参数是继承的父类的元组(记得元组单个元素时的写法)
- 第三个参数是设置属性/方法的字典
- 实际上
class
定义类底层也是调用的type()
方法。
metaclass
元类,就是说,通过元类创建类,再通过类创建实例对象。
metaclass 是 Python 面向对象里最难理解,也是最难使用的魔术代码。基本上很少用到。
- 元类命名通常以 Metaclass 结尾。
class ListMetaclass(type): # 需要继承自type类
def __new__(cls, name, bases, attrs):
attrs['add'] = lambda self, value: self.append(value)
return type.__new__(cls, name, bases, attrs)