a. 整型 int、 长整型 long、浮点型 float、 复数 complex
b. 字符串 str、 列表 list、 元祖 tuple
c. 字典 dict 、 集合 set
d. Python3 中没有 long,只有无限精度的 int
不可变:数值类型(int、float、bool)、string(字符串)、tuple(元组)
可变:list(列表)、dict(字典)、集合 set
a. 在python里凡是继承了object的类,都是新式类
b. Python3里只有新式类
c. Python2里面继承object的是新式类,没有写父类的是经典类
d. 经典类目前在Python里基本没有应用
e. 保持class与type的统一对新式类的实例执行a.class与type(a)的结果是一致的,对于旧式类来说就不
一样了。
f.对于多重继承的属性搜索顺序不一样新式类是采用广度优先搜索,旧式类采用深度优先搜索。
is:比较的是两个对象的id值是否相等,也就是比较俩对象是否为同一个实例对象。是否指向同一个内
存地址
== : 比较的两个对象的内容/值是否相等,默认会调用对象的eq()方法。
函数作用域的LEGB顺序
1.什么是LEGB?
L: local 函数内部作用域
E: enclosing 函数内部与内嵌函数之间
G: global 全局作用域
B: build-in 内置作用
python在函数里面的查找分为4种,称之为LEGB,也正是按照这是顺序来查找的
在Python中其实是引用传递。也就是函数外和函数内的变量都指向同一个对象。
但是当在函数里修改变量或重新赋值时,就需要区分:
当参数是可变对象时,函数里变量都引用了同一个对象,那么在函数里改变变量,会影响其他引用的变量;
当参数是不可变对象时,函数里的变量被拷贝了一份赋值给了函数内自己的变量,在函数内改变不会影响原来的值。
循环引用会怎么样?
参考:记一次面试问题——Python 垃圾回收机制 · TesterHome
python 采用的是引用计数机制为主,标记 - 清除和分代收集两种机制为辅的策略。
**引用计数:**当一个对象被创建或者被引用时,该对象的引用计数就会加1,当对象被销毁时相应的引用计数就会减1,一旦引用计数减为0时,表示该对象已经没有被使用.可以将其所占用的内存资源释放掉。
引用计数无法解决循环引用问题。
**分代回收:**以空间换时间的操作方式,Python 将内存根据对象的存活时间划分为不同的集合,每个集合称为一个代,Python 将内存分为了 3“代”,分别为年轻代(第 0 代)、中年代(第 1 代)、老年代(第 2 代),他们对应的是 3 个链表,它们的垃圾收集频率与对象的存活时间的增大而减小。新创建的对象都会分配在年轻代,年轻代链表的总数达到上限时,Python 垃圾收集机制就会被触发,把那些可以被回收的对象回收掉,而那些不会回收的对象就会被移到中年代去,依此类推,老年代中的对象是存活时间最久的对象,甚至是存活于整个系统的生命周期内。分代回收是建立在标记清除技术基础之上。分代回收同样作为 Python 的辅助垃圾收集技术处理那些容器对象。
标记清除(Mark—Sweep):是一种基于追踪回收(tracing GC)技术实现的垃圾回收算法。它分为两个阶段:第一阶段是标记阶段,GC 会把所有的『活动对象』打上标记,第二阶段是把那些没有标记的对象『非活动对象』进行回收。那么 GC 又是如何判断哪些是活动对象哪些是非活动对象的呢?
对象之间通过引用(指针)连在一起,构成一个有向图,对象构成这个有向图的节点,而引用关系构成这个有向图的边。从根对象(root object)出发,沿着有向边遍历对象,可达的(reachable)对象标记为活动对象,不可达的对象就是要被清除的非活动对象。根对象就是全局变量、调用栈、寄存器。 mark-sweepg 在上图中,我们把小黑圈视为全局变量,也就是把它作为 root object,从小黑圈出发,对象 1 可直达,那么它将被标记,对象 2、3 可间接到达也会被标记,而 4 和 5 不可达,那么 1、2、3 就是活动对象,4 和 5 是非活动对象会被 GC 回收。
标记清除算法作为 Python 的辅助垃圾收集技术主要处理的是一些容器对象,比如 list、dict、tuple,instance 等,因为对于字符串、数值对象是不可能造成循环引用问题。Python 使用一个双向链表将这些容器对象组织起来。不过,这种简单粗暴的标记清除算法也有明显的缺点:清除非活动的对象前它必须顺序扫描整个堆内存,哪怕只剩下小部分活动对象也要扫描所有对象。
在函数嵌套的前提下,内部函数使用了外部函数的变量,并且外部函数返回了内部函数,我们把这个使用外部函数变量的内部函数称为闭包。
构成条件:
在函数嵌套(函数里面在定义函数)的前提下
内部函数使用了外部函数的变量(还包括外部函数的参数)
外部函数返回了内部函数
实现:
除了使用嵌套函数外,还可以使用类实现闭包:
class Out:
def __init__(self, x):
self.x = x
def __call__(self, y):
return x + y
out = Out(3)
out(4)
定义类使用__call__
实现对象可被调用。
装饰器就是一个特殊的闭包,把一个函数当作外部函数的参数,在内部函数中使用这个函数。使用装饰器则是使用语法糖@来装饰函数。
开发中使用的比较多,比如:
1.flask钩子函数、Django中间件
2.flask路由函数@route
3.自定义接口返回格式的时候的装饰器
4.日志记录装饰器
class Computer:
def __init__(self, name, mem, cpu):
self.__name = name
@property
def name(self): # 只读, getter方法
return self.__name
pc2 = Computer('admin', '8G', 8)
print(pc2.name )
# pc2.name = "aa" # 修改会报错AttributeError: can't set attribute
类方法: 是类对象的方法,在定义时需要在上方使用 @classmethod 进行装饰,形参为cls,表示类对象,类对象和实例对象都可调用
类实例方法: 是类实例化对象的方法,只有实例对象可以调用,形参为self,指代对象本身;
静态方法: 是一个任意函数,在其上方使用 @staticmethod 进行装饰,可以用对象直接调用,静态方法实际上跟该类没有太大关系
new是在实例创建之前被调用的,任务是创建实例然后返回该实例对象,是个静态方法,至少要有一个参数cls(代表当前类),必须有返回值(即该实例对象)。
init是在实例创建完成后被调用的,设置对象属性的一些初始值,通常用在初始化一个类实例的时候,是一个实例方法,至少有一个参数self(代表当前的实例),无需返回值。
init在new之后被调用,new的返回值(实例)传递给init的第一个参数,然后由init设置一些实例的参数。
可以使用dir()
和__dict__
查询对象属性。
dir()
是python提供的API函数,利用对象的继承关系来查询该对象的所有有效属性,查询顺序为从对象本身向上级查询,下级的属性查询不到。
__dict__
本身作为对象的一种属性,查询范围区别于dir()
利用继承关系查询,__dict__
仅限于对象本身属性。但是并不是所有对象都有__dict__
属性。如果类的属性中有__slots__
属性,则该类的实例没有__dict__
属性。
hasattr和setattr
if hasattr(Parent, 'x'):
print(getattr(Parent, 'x'))
setattr(Parent, 'x',3)
print(getattr(Parent,'x'))
GIL是python的全局解释器锁,同一进程中假如有多个线程运行,一个线程在运行python程序的时候会霸占python解释器(加了一把锁即GIL),使该进程内的其他线程无法运行,等该线程运行完后其他线程才能运行。如果线程运行过程中遇到耗时操作,则解释器锁解开,使其他线程运行。所以在多线程中,线程的的运行仍是有先后顺序的,并不是同时进行。
多进程中因为每个进程都能被系统分配资源,相当于每个进程有了一个python解释器,所以多进程可以实现多个进程的同时运行,缺点是进程系统资源开销大。
不能。
python中一个线程有两种情况释放GIL:一种情况是在该线程进入IO操作之前,会主动释放GIL,另一种情况是解释器不间断运行了1000字节码(Py2)或运行15毫秒(Py3)后,该线程也会放弃GIL。既然一个线程可能随时会失去GIL,那么就涉及到线程安全。GIL设计的出发点是考虑到线程安全,但是这种线程安全是粗粒度(不需要程序员自己对线程加锁,语言层面本身维护着一个全局的锁机制,用来保证线程安全)。
不能完全保证线程安全,为什么还要设计GIL锁?
这和 CPython 的底层内存管理有关。
CPython 使用引用计数来管理内容,所有 Python 脚本中创建的实例,都会配备一个引用计数,来记录有多少个指针来指向它。当实例的引用计数的值为 0 时,会自动释放其所占的内存。
假设有两个 Python 线程同时引用 a,那么双方就都会尝试操作该数据,很有可能造成引用计数的条件竞争,导致引用计数只增加 1(实际应增加 2),这造成的后果是,当第一个线程结束时,会把引用计数减少 1,此时可能已经达到释放内存的条件(引用计数为 0),当第 2 个线程再次视图访问 a 时,就无法找到有效的内存了。
所以,CPython 引进 GIL,可以最大程度上规避类似内存管理这样复杂的竞争风险问题。
__repr__
__str__
__iter__
__call__
__new__
__init__
__getattr__
__setattr__
__dir__
__lt__
__le__
在python中,isinstance的意思是“判断类型”;isinstance()是一个内置函数,用于判断一个对象是否是一个已知的类型,类似type()。
isinstance() 与 type() 区别:
如果要判断两个类型是否相同推荐使用 isinstance()。
1.实现enter和exist方法
class File:
def __init__(self, filename, mode):
self.filename = filename
self.mode = mode
def __enter__(self):
print("进入")
self.f = open(self.filename, self.mode)
return self.f
def __exit__(self, exc_type=None, exc_val=None, exc_tbs=None):
print("退出")
self.f.close()
2.使用contextmanager装饰器
该装饰器将生成器中的代码通过yield语句分成两部分,yield之前的代码为__enter__
方法,yield之后的代码为__exit__
方法,yield的返回值即__enter__
方法的返回值,用于赋给as后的变量。
from contextlib import contextmanager
@contextmanager
def open_file(filename, mode):
print('进入')
f = open(filename, mode)
try:
yield f
finally:
print('退出')
f.close()
https://blog.csdn.net/YZL40514131/article/details/125349175
python字典的底层实现的是哈希表。调用python内置的哈希函数,将键(key)作为参数进行转换(哈希运算+取余运算),得到一个唯一的地址(地址的索引),然后将值(value)存放到对应的地址中(给相同的键赋值会直接覆盖原值,因为相同的键转换后的地址时一样的)。
哈希表使用顺序表存储数据:存储键值对时,通过哈希函数计算出键对应的索引,将值存到索引对应的数据区中
获取数据时,通过哈希函数计算出键对应的索引,将该索引对应的数据取出来。
所以键(Key)必须是可哈希的,即通过哈希函数可为此键计算出唯一地址。
对于 Python 来说,变量,列表、字典、集合这些都是可变的,所以都不能做为键(Key)来使用。因为元组里边可以存放列表这类可变元素,所以如果实在想拿元组当字典的键(Key),那必须对元组做限制:元组中只包括像数字和字符串这样的不可变元素时,才可以作为字典中有效的键(Key)。另外还需要注意的一点是,Python 的哈希算法对相同的值计算得到的结果是一样的,也就是说 12315 和 12315.0 的值相同,他们被认为是相同的键(Key)。
第一种方法:使用装饰器
def singleton(cls):
instances = {}
def wrapper(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return wrapper
@singleton
class Foo(object):
pass
if __name__ == "__main__":
foo1 = Foo()
foo2 = Foo()
print(foo1 is foo2)
第二种方法:使用基类
class Singleton(object):
_instance = None
def __new__(cls, *args, **kwargs):
if not hasattr(cls, '_instance'):
cls._instance = super(Singleton, cls).__new__(cls, *args, **kwargs)
return cls._instance
class Foo(Singleton):
pass
if __name__ == "__main__":
foo1 = Foo()
foo2 = Foo()
print(foo1 is foo2)
第三种方法:使用元类
元类,元类是用于创建类对象的类,类对象创建实例对象时一定要调用call方法,因此在调用call时候保证始终只创建一个实例即可,type是python的元类。
class Singleton(type):
# 元类必须继承type
def __call__(cls, *args, **kwargs):
if not hasattr(cls, '_instance'):
cls._instance = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instance
class Foo(metaclass=Singleton):
pass
if __name__ == "__main__":
foo1 = Foo()
foo2 = Foo()
print(foo1 is foo2)
单例模式应用的场景一般发现在以下条件下:
资源共享的情况下,避免由于资源操作时导致的性能或损耗等,如日志文件,应用配置。
控制资源的情况下,方便资源之间的互相通信。如线程池等,1,网站的计数器 2,应用配置 3.多线程池 4.数据库配置 数据库连接池 5.应用程序的日志应用…
反射的定义:主要是应用于类的对象上,在运行时,将对象中的属性和方法反射出来。
使用场景:可以动态的向对象中添加属性和方法。也可以动态的调用对象中的方法或者属性。
反射的常用方法:
1.hasaattr(obj,str)
判断输入的str字符串在对象obj中是否存在(属性或方法),存在返回True,否则返回False
2.getattr(obj,str)
将按照输入的str字符串在对象obj中查找。如找到同名属性,则返回该属性;如找到同名方法,则返回方法的引用,想要调用此方法得使用 getattr(obj,str)()进行调用。 如果未能找到同名的属性或者方法,则抛出异常:AttributeError。
3.setattr(obj,name,value)
name为属性名或者方法名,value为属性值或者方法的引用动态添加属性。如上,首先定义一个方法。再使用setattr(对象名,想要定义的方法名,所定义方法的方法名)
4.delattr(obj,str)
将你输入的字符串str在对象obj中查找,如找到同名属性或者方法就进行删除
python 里一些皆对象,包括类也是一个对象。元类及创建类的类。
一般自己写元类主要作用是对创建类或类的对象做一些限制和验证等。
新式类:以 object 为基类(父类)的类;经典类:不以 object 为基类的类。
新式类和经典类在多继承中,会影响 MRO(方法搜索顺序),经典类会按照深度优先的方法去搜索 ;新式类会按照广度优先的方法去搜索。
在 Python 2.x 中,如果没有指定父类,也不会把 object 作为基类。
而在 Python 3.x 中,如果未指定父类,则默认将 object 作为该类的基类,所以 Python 3.x 中定义的都是新式类。
主要是为了兼容经典类,所以都统一默认为新式类了。(不理解。。。)
交集:
s1 = {2, 3.6, True, 2 + 3j}
s2 = {1, 3.6, False, 2 + 3j}
print(s1 & s2)
# {1, 3.6, (2+3j)}
print(s2 & s1)
# {True, 3.6, (2+3j)}
交集时,布尔类型和0、1比较时,返回结果参考的是后一个数据;
但是把s2的False去掉,又不一样了,还不知道原因:
s1 = {2, 3.6, True, 2 + 3j}
s2 = {1, 3.6, 2 + 3j}
print(s1 & s2)
# {1, 3.6, (2+3j)}
print(s2 & s1)
# {1, 3.6, (2+3j)}
并集:
s1 = {2, 3.6, True, 2 + 3j}
s2 = {1, 3.6, False, 2 + 3j}
print(s1 | s2)
# {False, True, 2, 3.6, (2+3j)}
print(s2 | s1)
# {False, 1, 2, 3.6, (2+3j)}
并集时,布尔类型和0、1比较时,返回结果参考的是第一个数据;
把s2的False去掉,求并集时没有出现上面那种现象。
d1 = {
"a": 2,
"b": 3,
"c": 1
}
print(max(d1, key=lambda x: d1[x]))
max默认比较的是字典的key,可以指定key来制定比较规则。
sorted(d.items(),key=lambda x:x[1])
s = "aStr"[::-1]
sorted函数的使用
alist = [{'name':'a','age':20},{'name':'b','age':30},{'name':'c','age':25}]
def sort_by_age(list1):
return sorted(alist,key=lambda x:x['age'], reverse=True)
print(sort_by_age(alist))
print([x*11 for x in range(10)])
list1 = []
dict1 = {}
for i in range(3):
dict1["num"] = i
list1.append(dict1)
print(list1)
# [{'num': 2}, {'num': 2}, {'num': 2}]
因为dict1是可变变量,所以每次把dict1加到列表里,其实列表里存的都是dict1的引用,当dict1倍改变,列表里引用的dict1都一起跟着改变。
1.使用一个新列表,把不需要删除的元素拷贝到新列表;
2.如果不使用新列表,可以使用倒序遍历,这样遍历时,删除元素不会导致前面的索引变化
a=[1,2,3,4,5,6,7,8]
print(id(a))
for i in range(len(a)-1,-1,-1):
if a[i]>5:
pass
else:
a.remove(a[i])
print(id(a)) print('-----------') print(a)
import re
from collections import Counter
def test2(filepath):
with open(filepath) as f:
return list(map(lambda c: c[0], Counter(re.sub("\W+", " ", f.read()).split()).most_common(10)))
考察datetime模块使用
import datetime
def dayofyear():
year = input("请输入年份: ")
month = input("请输入月份: ")
day = input("请输入天: ")
date1 = datetime.date(year=int(year),month=int(month),day=int(day))
date2 = datetime.date(year=int(year),month=1,day=1)
return (date1-date2).days+1
方法1
def func1(l):
if isinstance(l, str):
l = [int(i) for i in l]
l.sort(reverse=True)
for i in range(len(l)):
if l[i] % 2 > 0:
l.insert(0, l.pop(i))
print(''.join(str(e) for e in l))
方法2
def func2(l):
print("".join(sorted(l, key=lambda x: int(x) % 2 == 0 and 20 - int(x) or int(x))))
def multi():
return [lambda x : i*x for i in range(4)]
print([m(3) for m in multi()])
正确答案是[9,9,9,9],而不是[0,3,6,9]。产生的原因是Python的闭包的后期绑定导致的,这意味着在闭包中的变量是在内部函数被调用的时候被查找的,因为,最后函数被调用的时候,for循环已经完成, i 的值最后是3,因此每一个返回值的i都是3,所以最后的结果是[9,9,9,9]。
因为延迟绑定的特性,即直到函数调用时,函数内部的执行内容才被确定,而此时i值在产生序列时,最终被赋值为4了,因此每一个函数调用中的上下文的i都是4,而不是预期的递增变化的。
from collections import abc
def decorator(func):
print("a")
def wrapper(*args, **kwargs):
print("b")
return func(*args, **kwargs)
print("c")
return wrapper
@decorator
def worker(*args, **kwargs):
print("d")
if __name__ == "__main__":
print("开始")
worker()
# a
# 开始
# b
# d
# c没打印因为提前return了