Python总结
前言
这里是除了一些通用语法以外的python特性说明,用来实现一些比较简单的函数和类,还有装饰器
环境配置
类和对象
如果对象的属性跟方法名相同,属性会覆盖方法。(方法也就是类内函数)
self参数的作用是绑定方法,辨别具体哪个对象在调用方法,一定要有。
__shuxinfangfa这种命名可以假隐藏(shili.Lei__shuxinfangfa可以用调用)(并不提倡)
__init__方法会在类实例化时被自动调用,我们称之为魔法方法。你可以重写这个方法,为对象定制初始化方案。(没有返回)
当然可以直接访问类变量MyClass.name,但如果没有实例化,在类的外面就不能调用方法MyClass.myFun()
class C:
num = 0
def __init__(self):
self.x = 4
self.y = 5
C.count = 6
num 和 count 是类属性(静态变量),x 和 y 是实例属性。
大多数情况下,你应该考虑使用实例属性,而不是类属性(类属性通常仅用来跟踪与类相关的值)。
使用 issubclass(class, classinfo) ,如果第一个参数(class)是第二个参数(classinfo)的一个子类,则返回 True。
- 一个类被认为是其自身的子类
- classinfo 可以是类对象组成的元组,只要 class 与其中任何一个候选类的子类,则返回 True
- 在其他情况下,会抛出一个 TypeError 异常
使用 isinstance(object, classinfo) ,如果第一个参数(object)是第二个参数(classinfo)的实例对象,则返回 True。
- 如果 objec t是 classinfo 的子类的一个实例,也符合条件
- 如果第一个参数不是对象,则永远返回False
- classinfo 可以是类对象组成的元组,只要class与其中任何一个候选类的子类,则返回 True
- 如果第二个参数不是类或者由类对象组成的元组,会抛出一个 TypeError 异常
- isinstance([1, 2, 3], (list, tuple)),这个是True
总是优先使用isinstance()判断类型,可以将指定类型及其子类“一网打尽”。
加入新的实例方法,动态绑定
>>> from types import MethodType
>>> s.set_age = MethodType(set_age, s) # 给实例绑定一个新的方法
函数装饰器(装饰器让你在一个函数的前后去执行代码。)
有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码到装饰器中并继续重用。
概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。已经存在的内裤,添加外部长裤的装饰。
基础装饰器(函数名做参数传递)
def a_new_decorator(a_func):
def wrapTheFunction():
print("在运行a_func()之前")
a_func() #这里是传进来的内裤位置,把a_func当做参数,有括号意思是直接启动函数
print("在运行 a_func()之后")
#return func() # 把 foo 当做参数传递进来时,执行func()就相当于执行foo(),当然如果有输出,那就只能用a_func()了
return wrapTheFunction
def a_function_requiring_decoration():
print("我是需要装饰的函数!!!")
a_function_requiring_decoration() #第一次正常单薄的内裤,输出单薄
#outputs: "我是需要装饰的函数"
#把内裤传进长裤里面去,名字还是叫做内裤 这里可以用语法糖@来代替,直接和定义内裤一起写了
a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration)
# 因为装饰器 a_new_decorator(a_function_requiring_decoration) 返回的是函数对象 wrapper,
#这条语句相当于 a_function_requiring_decoration= wrapTheFunction
#now a_function_requiring_decoration is wrapped by wrapTheFunction()
a_function_requiring_decoration() #第二次加了外面长裤的内裤,输出丰富
#outputs:在运行a_func()之前
# 我是需要装饰的函数!!!
# 在运行 a_func()之后
语法糖@,就不用重复性改代码,只要改内裤的作用就行了,无论是功能改成保暖还是防锈~ ( 意思就是把foo()给传进去 )
@a_new_decorator #穿上长裤,下面就是编制内裤(免去上一段代码中把内裤传进去的过程,直接用长裤就可以了)
def a_function_requiring_decoration():
"""Hey you! Decorate me!"""
print("I am the function which needs some decoration to "
"remove my foul smell")
a_function_requiring_decoration()
#outputs: I am doing some boring work before executing a_func()
# I am the function which needs some decoration to remove my foul smell
# I am doing some boring work after executing a_func()
!!!
#语法糖的作用就是:
a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration)
这里需要注意,在里面用到魔法方法和输出注释文档,这样子是正常输出的:
def logged(func):
def with_logging(*args, **kwargs):
print (func.__name__ ) # 输出 'f'
print (func.__doc__) # 输出 'does some math'
return func(*args, **kwargs)
return with_logging
@logged
def f(x):
"""does some math"""
return x + x * x
f(8)
#f
#does some math
#72 返回值,没有打印出来
如果在外面用到内裤的某些魔法方法,比如 内裤.__name__
出来的结果就是被长裤改造后的长裤属性,不是原本的内裤属性
print(f.__name__)
# Output: with_logging
可以采用如下方法输出内裤属性:
from functools import wraps
def a_new_decorator(a_func):
@wraps(a_func)
def wrapTheFunction():
print("运行 a_func()之前")
a_func()
print("运行a_func()之后")
return wrapTheFunction
@a_new_decorator
def a_function_requiring_decoration():
"""这里是注释文档(docstring)"""
print("我是需要装饰的函数!!!")
print(a_function_requiring_decoration.__name__) #在外面用到内裤的内部属性,就可以解决了
# Output: a_function_requiring_decoration
print(a_function_requiring_decoration.__doc__)
# 这里是注释文档(docstring)
带参数的修饰
当装饰器不知道 foo 到底有多少个参数时,我们可以用 *args 来代替:
def wrapper(*args):
logging.warn("%s is running" % func.__name__)
return func(*args)
return wrapper
可以把 wrapper 函数指定关键字函数:
def wrapper(*args, **kwargs):
# args是一个数组,kwargs一个字典
logging.warn("%s is running" % func.__name__)
return func(*args, **kwargs)
return wrapper
带参数的装饰器
装饰器还有更大的灵活性,例如带参数的装饰器,在上面的装饰器调用中,该装饰器接收唯一的参数就是执行业务的函数 foo 。
装饰器的语法允许我们在调用时,提供其它参数,比如@decorator(a)。这样,就为装饰器的编写和使用提供了更大的灵活性。
比如,我们可以在装饰器中指定日志的等级,因为不同业务函数可能需要的日志级别是不一样的。
from functools import wraps
def logit(logfile='out.log'):
def logging_decorator(func):
@wraps(func)
def wrapped_function(*args, **kwargs):
log_string = func.__name__ + " was called"
print(log_string)
# 打开logfile,并写入内容
with open(logfile, 'a') as opened_file:
# 现在将日志打到指定的logfile
opened_file.write(log_string + '\n')
return func(*args, **kwargs)
return wrapped_function
return logging_decorator
@logit()
def myfunc1():
pass
myfunc1()
# Output: myfunc1 was called
# 现在一个叫做 out.log 的文件出现了,里面的内容就是上面的字符串
@logit(logfile='func2.log')
def myfunc2():
pass
myfunc2()
# Output: myfunc2 was called
# 现在一个叫做 func2.log 的文件出现了,里面的内容就是上面的字符串
允许带参数的装饰器,实际上是对原有装饰器的一个函数封装,并返回一个装饰器。我们可以将它理解为一个含有参数的闭包。
当我 们使用@logit(logfile='func2.log')
调用的时候,Python 能够发现这一层的封装,并把参数传递到装饰器的环境中。、
装饰器顺序
一个函数还可以同时定义多个装饰器,比如:
它的执行顺序是从里到外,最先调用最里层的装饰器,最后调用最外层的装饰器,它等效于f = a(b(c(f)))
也就是依次穿裤子。
@a
@b
@c
def f ():
pass
装饰器精髓(类装饰器做例子)
有时你只想打日志到一个文件。而有时你想把引起你注意的问题发送到一个email,同时也保留日志,留个记录。
这是一个使用继承的场景,但目前为止我们只看到过用来构建装饰器的函数。
from functools import wraps
class logit(object):
def __init__(self, logfile='out.log'):
self.logfile = logfile
def __call__(self, func):
@wraps(func)
def wrapped_function(*args, **kwargs):
log_string = func.__name__ + " was called"
print(log_string)
# 打开logfile并写入
with open(self.logfile, 'a') as opened_file:
# 现在将日志打到指定的文件
opened_file.write(log_string + '\n')
# 现在,发送一个通知
self.notify()
return func(*args, **kwargs) #上面把内裤的装饰作用一下,这里是运行一次内裤
return wrapped_function
def notify(self):
# logit只打日志,不做别的
pass
我们把内裤函数包裹进去
@logit()
def myfunc1():
pass
现在,我们给 logit 创建子类,来添加 email 的功能(虽然 email 这个话题不会在这里展开)。
class email_logit(logit):
'''
一个logit的实现版本,可以在函数调用时发送email给管理员
'''
def __init__(self, email='admin@myproject.com', *args, **kwargs):
self.email = email
super(email_logit, self).__init__(*args, **kwargs)
#把子类(email_logit)的父类(logit)的属性继承下来
def notify(self):
# 发送一封email到self.email
# 这里就不做实现了
pass
需要注意:super的语法就是继承属性下来。
从现在起,@email_logit 将会和 @logit 产生同样的效果,但是在打日志的基础上,还会多发送一封邮件给管理员。
类内属性重写 property (外部可以增删查改类内属性,把一或多个方法变成属性)
实现的是类外改变 实例.x 的值,就可以实现对类的属性微调。
比如定义一个矩形类,可以自定义生日,只读年龄
class Student(object):
@property
def birth(self):
return self._birth
@birth.setter
def birth(self, value):
self._birth = value
@property
def age(self):
return 2015 - self._birth
完整版如下
class C:
def __init__(self):
self._x = None
def getx(self):
return self._x #这里可以用内部变量进行封装
def setx(self, value):
self._x = value
def delx(self):
del self._x #外面就可以用property来进行重写,不影响里面变量的传递
x = property(getx, setx, delx, "I'm the 'x' property.")
下面这个方式和上面的代码一样的功能,用了函数内置的修饰符(原本是@出函数来,意为插入到@函数里)
class C:
def __init__(self):
self._x = None
@property
def x(self):
"""I'm the 'x' property."""
return self._x
@x.setter
def x(self, value):
self._x = value
@x.deleter
def x(self):
del self._x
内置修饰符还有staticmethod、classmethod 和 property!!!
class Hello(object):
def __init__:
...
@classmethod
def print_hello(cls):
print("Hello")
classmethod 修饰过后,print_hello() 就变成了类方法,可以直接通过 Hello.print_hello() 调用,而无需绑定实例对象了。
class Test(object):
def InstanceFun(self):
print("InstanceFun");
print(self);
@classmethod
def ClassFun(cls):
print("ClassFun");
print(cls);
@staticmethod
def StaticFun():
print("StaticFun");
总而言之
1、有self的,一定要绑定实例对象,其他可以直接类名调用(有self可以这样 Test.InstanceFun(t),否则规矩地 t.InstanceFun())
2、所有的,都可以实例调用,t.fangfa()(实例调用后,再 t.ClassFun(Test) 就多余了)
3、尽量按照类方法classmethod类调用,实例方法实例调用,静态方法staticmethod两者都调用的原则
继承详解
可以多继承(钻石继承从左到右找,然后往上找父类,否则顺着一个继承类找到基类后又返回找同级的继续继承,就会重复)
class A():
def __init__(self):
print("进入A…")
print("离开A…")
class B(A):
def __init__(self):
print("进入B…")
A.__init__(self)
print("离开B…")
class C(A):
def __init__(self):
print("进入C…")
A.__init__(self)
print("离开C…")
class D(B, C):
def __init__(self):
print("进入D…")
B.__init__(self)
C.__init__(self)
print("离开D…")
>>> d = D()
进入D…
进入B…
进入A…
离开A…
离开B…
进入C…
进入A…
离开A…
离开C…
离开D…
super继承方式(确保继承顺序下,每个类只被访问一次,金字塔平行向上),每个父类的内置属性__init__都会给继承过来。
class A():
def __init__(self):
print("进入A…")
print("离开A…")
class B(A):
def __init__(self):
print("进入B…")
super().__init__()
print("离开B…")
class C(A):
def __init__(self):
print("进入C…")
super().__init__()
print("离开C…")
class D(B, C):
def __init__(self):
print("进入D…")
super().__init__()
print("离开D…")
>>> d = D()
进入D…
进入B…
进入C…
进入A…
离开A…
离开C…
离开B…
离开D…
子类定义相同名字的属性或方法时,Python 不会删除,只是会先使用子类的具体方法而已,父类一样可以被继承重写(pass重写为空)。
class Bird:
def fly(self):
print("Fly away!")
class Penguin(Bird):
def fly(self):
pass
>>> bird = Bird()
>>> penguin = Penguin()
>>> bird.fly()
Fly away!
>>> penguin.fly()
继承进阶–组合
在Python里组合其实很简单,直接在类定义中把需要的类放进去实例化就可以了。
// 乌龟类
class Turtle:
def __init__(self, x):
self.num = x
// 鱼类
class Fish:
def __init__(self, x):
self.num = x
// 水池类
class Pool:
def __init__(self, x, y):
self.turtle = Turtle(x) // 组合乌龟类进来
self.fish = Fish(y) // 组合鱼类进来
def print_num(self):
print("水池里总共有乌龟 %d 只,小鱼 %d 条!" % (self.turtle.num, self.fish.num))
>>> pool = Pool(1, 10)
>>> pool.print_num()
实现
class Rectangle:
length = 5
width = 4
def setRect(self):
print("请输入矩形的长和宽...")
self.length = float(input('长:'))
self.width = float(input('宽:'))
def getRect(self):
print('这个矩形的长是:%.2f,宽是:%.2f' % (self.length, self.width))
def getArea(self):
return self.length * self.width
定制类
name
所有模块都有一个 name 属性,name 的值取决于如何应用模块,
在作为独立程序运行的时候,name 属性的值是 ‘main’,
而作为模块导入的时候,这个值就是该模块的名字了。
类的字符串属性__str__
class Student(object):
def __init__(self, name):
self.name = name
def __str__(self):
return 'Student object (name: %s)' %self.name
这样的话
print(Student('Michael'))
#Student object (name: Michael)
但是
>>> s = Student('Michael')
>>> s
<__main__.Student object at 0x109afb310>
直接显示变量调用的不是__str__(),而是__repr__(),str()返回用户看到的字符串,
而__repr__()返回程序开发者看到的字符串,也就是说,repr()是为调试服务的。
解决办法是再定义一个__repr__()。但是通常__str__()和__repr__()代码都是一样的,所以,有个偷懒的写法:
class Student(object):
def __init__(self, name):
self.name = name
def __str__(self):
return 'Student object (name=%s)' % self.name
__repr__ = __str__
类作为循环对象__next__(for n in 类名)
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 # 返回下一个值
>>> for n in Fib():
... print(n)
...
1
1
2
3
5
...
46368
75025
类的数组访问__getitem__
class Fib(object):
def __getitem__(self, n):
a, b = 1, 1
for x in range(n):
a, b = b, a + b
return a
也就是
>>> f = Fib()
>>> f[0]
1
>>> f[1]
1
>>> f[2]
2
>>> f[3]
3
>>> f[10]
89
>>> f[100]
573147844013817084101
类的数组切片
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
就可以
>>> f = Fib()
>>> f[0:5]
[1, 1, 2, 3, 5]
>>> f[:10]
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
但是却不能跨越取值f[:10:2]也没有对负数作处理,所以,要正确实现一个__getitem__()还是有很多工作要做的。 此外,如果把对象看成dict,__getitem__()的参数也可能是一个可以作key的object,例如str。 与之对应的是__setitem__()方法,把对象视作list或dict来对集合赋值。最后,还有一个__delitem__()方法,用于删除某个元素。
我们自己定义的类表现得和Python自带的list、tuple、dict没什么区别,这完全归功于动态语言的“鸭子类型”,不需要强制继承某个接口。
捕捉类属性或方法__getattr__()
当类调用不存在的属性,返回值
class Student(object):
def __init__(self):
self.name = 'Michael'
def __getattr__(self, attr):
if attr=='score':
return 99
>>> s = Student()
>>> s.name
'Michael'
>>> s.score
99
当类
class Student(object):
def __getattr__(self, attr):
if attr=='age':
return lambda: 25
>>> s.age()
25
应用:链式调用每个URL对应的API都写一个方法:
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__
无论API怎么变,SDK都可以根据URL实现完全动态的调用,而且,不随API的增加而改变!
>>> Chain().status.user.timeline.list
'/status/user/timeline/list'
还有些REST API会把参数放到URL中,比如GitHub的API:GET /users/:user/repos
调用时,需要把:user替换为实际用户名。
如果我们能写出这样的链式调用:
Chain().users('michael').repos
自定义类自身调用__call__
实例当做函数来用,返回的是类里面定义的东西
class Student(object):
def __init__(self, name):
self.name = name
def __call__(self):
print('My name is %s.' % self.name
>>> s = Student('Michael')
>>> s() # self参数不要传入
My name is Michael.
查看是否可以用实例来作为函数(
实现了__call__的类都是true,对于函数、方法、lambda 函式、 类以及实现了 __call__ 方法的类实例, 它都返回 True。)
>>> callable(Student())
True
类限制绑定属性__slots__
class Student(object):
__slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称
>>> s.score = 99 # 绑定属性'score'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'score'
对继承的子类不起作用,但是加进去了,起了作用,就是父子一起作用
除非在子类中也定义__slots__,这样,子类实例允许定义的属性就是自身的__slots__加上父类的__slots__。
其他
Python 允许我们自己定制容器,如果你想要定制一个不可变的容器(像 String),你就不能定义像 setitem() 和 delitem() 这些会修改容器中的数据的方法。
通过定义读 —— __getitem__(),写 —— __setitem__(),删除 —— __delitem__()
使得容器支持读、写和删除的操作
鸭子类型:
允许函数中使用未实例化的类方法(这个方法可以是两个不同类的同名方法),当然用的时候提前实例化
# I love FishC.com!
class Duck:
def quack(self):
print("呱呱呱!")
def feathers(self):
print("这个鸭子拥有灰白灰白的羽毛。")
class Person:
def quack(self):
print("你才是鸭子你们全家人是鸭子!")
def feathers(self):
print("这个人穿着一件鸭绒大衣。")
def in_the_forest(duck): #不同类的同名方法,要调用,之前就必须实例化这个函数的参数duck
duck.quack()
duck.feathers()
def game():
donald = Duck()
john = Person()
in_the_forest(donald)
in_the_forest(john)
game()
site-packages 文件夹就是用来存放你的模块文件的。
import urllib.request 语句,那么这个 urllib 是一个包,Python 把同类的模块放在一个文件夹中统一管理,这个文件夹称之为一个包。
urllib 是 Python 负责管理 URL 的包,用于访问网址。
文件夹中是否有 init.py 文件,必须在包文件夹中创建一个 init.py 的模块文件,内容可以为空。可以是一个空文件,也可以写一些初始化代码。这个是 Python 的规定,用来告诉 Python 将该目录当成一个包来处理。
动态创建类
正常情况下,你不会碰到需要使用metaclass的情况,所以,以下内容看不懂也没关系,因为基本上你不会用到。
先定义metaclass,就可以创建类,最后创建实例。
所以,metaclass允许你创建类或者修改类。换句话说,你可以把类看成是metaclass创建出来的“实例”。
metaclass是Python面向对象里最难理解,也是最难使用的魔术代码。
我们先看一个简单的例子,这个metaclass可以给我们自定义的MyList增加一个add方法:
定义ListMetaclass,按照默认习惯,metaclass的类名总是以Metaclass结尾,以便清楚地表示这是一个metaclass:
# metaclass是类的模板,所以必须从`type`类型派生:
class ListMetaclass(type):
def __new__(cls, name, bases, attrs):
attrs['add'] = lambda self, value: self.append(value)
return type.__new__(cls, name, bases, attrs)