Python基础

赋值、浅 copy、深 copy

  • 赋值:相当于多贴了一个标签(引用),指向同一个对象,引用计数 +1。

  • 浅拷贝 :会开辟新的内存地址存储 被拷贝对象的外层对象 ,但是 不拷贝内层的对象,不能算一个完整的拷贝副本。

  • 深拷贝 :会开辟新的内存地址存储被拷贝对象的外层对象,同时 对于内层对象也会递归拷贝,即是一个完整的拷贝副本。

赋值

不可变对象被重新赋值,重新分配了一块内存,ID 就变了

python 复制代码
a = 1
b = a
print(id(a), id(b))  # 140729223153328 140729223153328

a = 2
print(a, b)  # 2 1
print(id(a), id(b))  # 140729223153360 140729223153328

列表直接赋值给列表不属于拷贝, 只是内存地址的引用

python 复制代码
list1 = ["a", "b", "c"]
list2 = list1
list1.append("d")
print(list1, list2)  # ['a', 'b', 'c', 'd'] ['a', 'b', 'c', 'd']
print(id(list1), id(list2))  # 2212470388480 2212470388480

浅拷贝

浅拷贝, 只会拷贝第一层, 第二层的内容不会拷贝

list() 转换也是浅 copy

python 复制代码
list1 = ["a", "b", "c"]
list2 = list1.copy()
# 转换也是浅copy
list3 = list(list1)

list1.append("d")

print(list1, list2, list3)  # ['a', 'b', 'c', 'd'] ['a', 'b', 'c'] ['a', 'b', 'c']
print(id(list1), id(list2), id(list3)) # 2128034527104 2128034526144 2128033091072
python 复制代码
list1 = ["a", "b", "c", [1, 2, 3]]
list2 = list1.copy()
# 转换也是浅copy
list3 = list(list1)

list1[3].append(4)

print(list1, list2, list3)  # ['a', 'b', 'c', [1, 2, 3, 4]] ['a', 'b', 'c', [1, 2, 3, 4]] ['a', 'b', 'c', [1, 2, 3, 4]]
print(id(list1), id(list2), id(list3))  # 2111124370112 2111124370816 2111124369664
print(id(list1[3]), id(list2[3]), id(list3[3]))  # 2111124400320 2111124400320 2111124400320

深拷贝

python 复制代码
import copy

list1 = ["a", "b", "c", [1, 2, 3]]
list2 = copy.deepcopy(list1)
list1[3].append(4)

print(list1, list2)  # ['a', 'b', 'c', [1, 2, 3, 4]] ['a', 'b', 'c', [1, 2, 3]]
print(id(list1), id(list2))  # 1394256181440 1394256181696
print(id(list1[3]), id(list2[3]))  # 1394256180608 1394256115584

推导式生成对象

[item] * 3 的结果相当于 [item, item, item],因为 item 指向的是一个可变对象(list),所以我们用 * 做重复的时候,实际上得到的 items 的三个元素都是指向的同一个对象 ["hello"]

python 复制代码
item = ["hello"]
items = [item] * 3

print(items)  # [['hello'], ['hello'], ['hello']]

items[0][0] = "world"
print(items)  # [['world'], ['world'], ['world']]
python 复制代码
items = [['hello'] for _ in range(3)]
print(id(items[0]), id(items[1]), id(items[2]))  # 1618997298624 1618997299584 1618997299456

items[0][0] = "world"
print(items)  # [['world'], ['hello'], ['hello']]

lambda 测试

python 复制代码
def multipliers():
    return [lambda x: i * x for i in range(4)]

print([m(2) for m in multipliers()]) 

# [6, 6, 6, 6]
python 复制代码
def multipliers():
    # 添加了一个默认参数i=i
    return [lambda x, i=i: i * x for i in range(4)]

print([m(2) for m in multipliers()])

# [0, 2, 4, 6]
python 复制代码
def multipliers():
    return (lambda x: i * x for i in range(4))

print([m(2) for m in multipliers()])

# [0, 2, 4, 6]

变量及作用域

全局变量

python 复制代码
g1 = 1
g2 = []

def f():
    g1 = 2
    g2.append(1)

f()
print(g1)  # 1
print(g2)  # [1]
  • g1: 因为是数值,f 中直接就重新定义了;
  • g2: 因为是列表,f 中是修改,而不是重新定义,所以是对全局变量的修改;如果这里重新定义,再修改,就是对局部变量的修改了,不会影响全局变量;

在 python 的函数内,可以直接引用外部变量,但不能改写外部变量

可以使用 nonlocalglobal 来实现,nonlocalglobal 的区别在于 nonlocal 语句会去搜寻本地变量与全局变量之间的变量,其会优先寻找层级关系与闭包作用域最近的外部变量。

Example

  • a = 1 不在 globals 作用域,因此在 func2 中只能用 nonlocal 定义后才能修改。

    python 复制代码
    a = 0
    
    def func1():
        a = 1
    
        def func2():
            a = 2
            print('closure a: ', a)
    
        print(f'func1_a: {a}')
        func2()
        print(f'after func2, func1_a: {a}')
    
    
    func1()
      print(f'global a: {a}')
    python 复制代码
    # func1_a:1
    # closure a: 2
    # after func2, func1_a:1
    # global a:0
  • a = 1 不在 globals 作用域,因此在 func2 中只能用 nonlocal 定义后才能修改。

    python 复制代码
    a = 0
    
    def func1():
        a = 1
    
        def func2():
            nonlocal a
            a = 2
            print('closure a: ', a)
    
        print(f'func1_a: {a}')
        func2()
        print(f'after func2, func1_a: {a}')
    
    
    func1()
    print(f'global a: {a}')
    python 复制代码
    # func1_a: 1
    # closure a:  2
    # after func2, func1_a: 2
    # global a: 0
  • a = 1 不在 globals 作用域,因此在 func2 中只能用 nonlocal 定义后才能修改。

    python 复制代码
    a = 0
    
    def func1():
        a = 1
    
        def func2():
            global a
            a = 2
            print('closure a: ', a)
    
        print(f'func1_a: {a}')
        func2()
        print(f'after func2, func1_a: {a}')
    
    
    func1()
    print(f'global a: {a}')
    python 复制代码
    # func1_a: 1
    # closure a:  2
    # after func2, func1_a: 1
    # global a: 2

闭包 nonclosure

一个闭包就是你调用了一个函数 A,这个函数 A 返回了一个函数 B 给你。这个返回的函数 B 就叫做闭包 。你在调用函数 A 的时候传递的参数就是 自由变量

python 复制代码
def func(name):
    def inner_func(age):
        print('name:', name, 'age:', age)
    return inner_func

bb = func('aa')
bb(26)
python 复制代码
# name: aa age: 26

这里面调用 func 的时候就产生了一个 闭包------inner_func , 并且该闭包持有 自由变量------name ,因此这也意味着,当函数 func 的生命周期结束之后,name 这个变量依然存在,因为它被闭包引用了,所以不会被回收。

闭包的作用

闭包的最大特点是 可以将父函数的变量与内部函数绑定并返回绑定变量后的函数(也即闭包),此时即便生成闭包的环境(父函数)已经释放,闭包仍然存在,这个过程很像类(父函数)生成实例(闭包),不同的是父函数只在调用时执行,执行完毕后其环境就会释放,而类则在文件执行时创建,一般程序执行完毕后作用域才释放。

因此对一些需要重用的功能且不足以定义为类的行为,使用闭包会比使用类占用更少的资源,且更轻巧灵活。

Example:

假设我们仅仅想打印出各类动物的叫声,分别以类和闭包来实现:

python 复制代码
def animal_voice(animal):
    def sound(voice):
        print(animal, ":", voice, "...")
    return sound


dog = animal_voice("dog")
dog("wangwang")  # dog : wangwang ...
dog("wowo")  # dog : wowo ...
python 复制代码
class Animal:
    def __init__(self, animal):
        self.animal = animal

    def sound(self, voice):
        print(self.animal, ":", voice, "...")


dog = Animal('dog')
dog.sound("wangwang")  # dog : wangwang ...
dog.sound("wowo")  # dog : wowo ... 
python 复制代码
print(id(Animal.sound))  # 2165033261520
print(id(dog.sound))  # 2165026975808

可以看到输出结果完全一样,但显然类的实现相对繁琐,且这里只是想输出一下动物的叫声,定义一个 Animal 类未免小题大做,而且 voice 函数在执行完毕后,其作用域就已经释放,但 Animal 类及其实例 dog 的相应属性却一直贮存在内存中。而这种占用对于实现该功能后,则是没有必要的。

除此之外,闭包还有很多其他功能,比如用于封装等,另外,闭包有效的减少了函数参数的数目这对并行计算非常有价值,比如可以让每台电脑负责一个函数,然后串起来,实现流水化的作业等。

Example

python 复制代码
def makebold(fn):
    def wrapped():
        return "<b>" + fn + "<b>"
    return wrapped

def makeitalic(fn):
    def wrapped():
        return "<i>" + fn + "<i>"
    return wrapped

def hello():
    return "hello world"


hello = makeitalic(hello())
hello = makebold(hello())
print(hello())  # <b><i>hello world<i><b>

装饰器

装饰器(decorators) 是 Python 中的一种高级功能,它允许你 动态地修改函数或类的行为

装饰器是一种函数,它接受 一个函数作为参数 ,并 返回一个新的函数或修改原来的函数

装饰器的语法使用 @decorator_name 来应用在函数或方法上

Python 装饰器允许在不修改原有函数代码的基础上,动态地增加或修改函数的功能,装饰器本质上是一个 接收函数作为输入返回一个新的包装过后的函数 的对象。

装饰器的应用场景:

  • 日志记录: 装饰器可用于记录函数的调用信息、参数和返回值。
  • 性能分析: 可以使用装饰器来测量函数的执行时间。
  • 权限控制: 装饰器可用于限制对某些函数的访问权限。
  • 缓存: 装饰器可用于实现函数结果的缓存,以提高性能。

Example 1

python 复制代码
def outer(f):
    def inner(*args, **kargs):
        inner.co += 1
        return f(*args, **kargs)

    inner.co = 0
    return inner


@outer
def f():
    pass


f()
f()
f()
print(f.co)
python 复制代码
# 3

Example 2

python 复制代码
def wrapFun(func):
    def inner(a, b):
        print('function name:', func.__name__)
        r = func(a, b)
        return r
    return inner

@wrapFun
def myadd(a, b):
    return a + b

print(myadd(2, 3))
python 复制代码
# function name: myadd
# 5

多个装饰器装饰一个函数类似 stack

多个装饰器装饰一个函数时,执行时的顺序是:最先装饰的装饰器,最后一个执行 。它遵循了 先进后出规则 类似于 stack

Example 3

python 复制代码
def set_fun1(func):
    # 打印用于验证在多个装饰器的情况下,多个装饰器之间的执行顺序
    print("set_fun1已被定义")  
    def call_fun1(*args, **kwargs):
        # 当被装饰函数执行时,会打印
        print("call_fun1执行了")
        return func()
    return call_fun1


def set_fun2(func):
    print("set_fun2已被定义")
    def call_fun2(*args, **kwargs):
        print("call_fun2执行了")
        return func()
    return call_fun2


# 装饰函数
@set_fun2
@set_fun1
def test():
    print("--------------")


test()
python 复制代码
# set_fun1已被定义
# set_fun2已被定义
# call_fun2执行了
# call_fun1执行了
# --------------

迭代对象,迭代器,生成器

  • 若对象中实现了 __getitem__ 或者 __iter__ 方法 ,那么这个对象就是 可迭代对象

  • 若对象中实现了 __next____iter__ 方法 ,那么这个对象就是 迭代器

  • 可迭代对象的背后其实是迭代器在起作用

使用了 yield 的函数被称为 生成器(generator)

yield 是一个关键字,用于定义生成器函数,生成器函数是一种特殊的函数,可以在迭代过程中逐步产生值,而不是一次性返回所有结果。生成器是一个返回迭代器的函数,只能用于迭代操作,更简单点理解生成器就是一个迭代器。

当在生成器函数中使用 yield 语句时,函数的执行将会暂停,并将 yield 后面的表达式作为当前迭代的值返回。

然后,每次调用生成器的 next() 方法或使用 for 循环进行迭代时,函数会从上次暂停的地方继续执行,直到再次遇到 yield 语句。这样,生成器函数可以逐步产生值,而不需要一次性计算并返回所有结果。

调用一个生成器函数 ,返回的是一个 迭代器对象。可以说所有生成器对象都是迭代器对象,有一点细微的区别:

  • 生成器对象更倾向于在无限中集合中惰性的输出需要的数据
  • 迭代器更倾向于在实现已知道所有数据的情况下惰性输出需要的数据

恰当的例子就是斐波那契数列,可以用生成器实现一个斐波那契数列,但因为该数列的元素是无限多个,所以说其是迭代器实现的就没有说由生成器实现的说法恰当。

判断对象是否是可迭代对象的方法

方法一:

python 复制代码
print('__iter__' in dir([1, 2, 3]))  # True

方法二:

python 复制代码
from collections.abc import Iterable

print(isinstance('123', Iterable))  # True
python 复制代码
arr = [1, 2, 3]
arr_iter = arr.__iter__()
print(arr_iter.__next__())  # 1

# arr是可迭代对象, 不是迭代器 
print('__next__' in dir(arr))  # False

# arr_iter是迭代器
print('__next__' in dir(arr_iter))  # True

1. __iter__()

该方法返回的是当前对象的 迭代器类的实例。因为可迭代对象与迭代器都要实现这个方法,因此有以下两种写法。

  1. 用于可迭代对象类的写法,返回该可迭代对象的迭代器类的实例。
  2. 用于迭代器类的写法,直接返回 self(即自己本身),表示自身即是自己的迭代器。

2. __next__ ()

返回迭代的每一步,实现该方法时注意要最后超出边界要抛出 StopIteration 异常。

python 复制代码
    def __init__(self, start, end):
        self.start = start
        self.end = end

    def __iter__(self):
        return self

    def __next__(self):
        if self.start < self.end:
            self.start += 1
            return self.start - 1
        else:
            raise StopIteration


for i in MyRange(1, 3):
    print(i)  # 1 2
for i in range(1, 3):
    print(i)  # 1 2

3. iter(source, sentinel=None)

python 复制代码
def iter(source, sentinel=None): # known special case of iter
    """
    iter(iterable) -> iterator
    iter(callable, sentinel) -> iterator
    
    Get an iterator from an object.  In the first form, the argument must
    supply its own iterator, or be a sequence.
    In the second form, the callable is called until it returns the sentinel.
    """
    pass

Example 1:

python 复制代码
it = iter([1, 2, 3, 4, 5])

def func():
    return next(it)

for j in iter(func, 4):
    print(j)
python 复制代码
# 1
# 2
# 3

Example 2:

python 复制代码
class Next:
    def __init__(self):
        self.data = [0, 1, 2, 3, 4]
        self._iter = iter(self.data)

    def get_len(self):
        return len(self.data)

    def __iter__(self):
        print('iter')
        return self

    def __call__(self):
        print("call")
        return next(self._iter)

    def __next__(self):
        print('i am next')
        return next(self._iter)

这里 Next() 是初始化创建一个实例,该实例可以被调用,也就是 Next()() 时会调用 Next().__call__()

iter 对象的第一个参数如果是 可调用对象 时,会一直调用该对象 直到与第二个参数相同 或者 raise StopIteration

而每次执行 __call__ 时,会对 self._iter 迭代一次。这里 __next____iter__ 没有执行。

python 复制代码
for it in iter(Next(), 6):
    print(it)

# call
# 0
# call
# 1
# call
# 2
# call
# 3
# call
# 4
# call
python 复制代码
iter_next = iter(Next())
print("===================")
for it in iter_next:
    print(it)

# iter
# ===================
# iter
# i am next
# 0
# i am next
# 1
# i am next
# 2
# i am next
# 3
# i am next
# 4
# i am next

异常捕获

try/except

try 语句按照如下方式工作:

  • 执行 try 子句(在关键字 try 和关键字 except 之间的语句)。

  • 如果没有异常发生,忽略 except 子句,try 子句执行后结束。

  • 如果在执行 try 子句的过程中发生了异常,那么 try 子句余下的部分将被忽略

  • 如果异常的类型和 except 之后的名称相符,那么对应的 except 子句将被执行。一个 try 语句可能包含多个 except 子句,分别来处理不同的特定的异常。最多只有一个分支会被执行。

    except 只有第一个捕获的会执行

    一个 except 子句可以同时处理多个异常,这些异常将被放在一个括号里成为一个元组:

    except (ValueError,IndexError) as e:

  • 如果一个异常没有与任何的 except 匹配,那么这个异常将会传递给上层的 try 中。

Except 多个异常

python 复制代码
class AException(Exception):
    def __str__(self):
        return "A Exception"


class BException(AException):
    def __str__(self):
        return "B Exception"
    
class CException(AException, BException):
    pass

Example 1

python 复制代码
try:
    try:
        raise BException
    except AException:
        raise
except Exception as exc:
    print("Raise exception")
    print(str(exc))
python 复制代码
# Raise exception
# B Exception

Example 2

python 复制代码
try:
    try:
        raise BException
    except AException:
        raise AException
except Exception as exc:
    print("Raise exception")
    print(str(exc))
python 复制代码
# Raise exception
# A Exception

Example 3

python 复制代码
try:
    exc_massage = []
    try:
        raise CException
    except AException as exc:
        exc_massage.append("AException")
        raise exc
    except BException as exc:
        exc_massage.append("BException")
        raise exc
    except CException as exc:
        exc_massage.append("CException")
        raise exc
except CException:
    print(exc_massage)
    print("C Exception")
except AException as e:
    print("A Exception")
except BException:
    print("B Exception")
python 复制代码
# ['AException']
# C Exception

try/except...else

try/except 语句还有一个可选的 else 子句,如果使用这个子句,那么必须放在所有的 except 子句之后。

else 子句将在 try 子句没有发生任何异常的时候执行。

使用 else 子句比把所有的语句都放在 try 子句里面要好,这样可以避免一些意想不到,而 except 又无法捕获的异常。

python 复制代码
try:
    try:
        x = 1 / 0
    except TypeError:
        print("TypeError")
    else:
        print("Else")
except Exception as e:
    print(e)
python 复制代码
# division by zero

try/except...else...finally

finally 语句无论异常是否发生都会执行

Example 1

python 复制代码
def test():
    try:
        print('try')
        a = 1 / 0
        print('try')
        return 0
    except:
        print('except')
        return 1
    else:
        print("else")
        return 2
    finally:
        print('finally')

print(test())

# try
# except
# finally
# 1

Example 2

python 复制代码
def test():
    try:
        print('try')
        a = 5.0 / 0.0
        print('try')
        return 0
    except:
        print('except')
        return 1
    else:
        print("else")
        return 2
    finally:
        print('finally')
        return 3


print(test())

# try
# except
# finally
# 3

Example 3

python 复制代码
def test():
    try:
        print('try')
        a = 1 / 1
        print('try')
        return 0
    except:
        print('except')
        return 1
    else:
        print("else")
        return 2
    finally:
        print('finally')
        return 3


print(test())

# try
# try
# finally
# 3

Example 4

python 复制代码
def test():
    try:
        print('try')
        a = 1 / 1
        print('try')
    except:
        print('except')
        return 1
    else:
        print("else")
        return 2
    finally:
        print('finally')
        return 3


print(test())

# try
# try
# else
# finally
# 3

python 的类变量和 C++的静态变量不同,并不是由类的所有对象共享。

类本身拥有自己的类变量(保存在内存),当一个 TestClass 类的对象被构造时,会将当前类变量拷贝一份给这个对象,当前类变量的值是多少,这个对象拷贝得到的类变量的值就是多少;而且,通过对象来修改类变量,并不会影响其他对象的类变量的值 ,因为大家都有各自的副本,更不会影响类本身所拥有的那个类变量的值;只有类自己才能改变类本身拥有的类变量的值。

对于 类数据属性实例数据属性,可以总结为:

  • 类数据属性 属于类本身,可以通过 类名 进行访问/修改
  • 类数据属性也可以被类的所有实例访问/修改
  • 在类定义之后,可以通过类名动态添加类数据属性,新增的类属性也被类和所有实例共有
  • 实例数据属性 只能通过实例访问
  • 在实例生成后,还可以动态添加实例数据属性,但是这些实例数据属性只属于该实例
python 复制代码
class TestClass(object):
    # 类变量
    val1 = 100

    def __init__(self):
        # 成员变量
        self.val2 = 200

    def fcn(self, val=400):
        val3 = 300
        self.val4 = val
        self.val5 = 500
python 复制代码
inst = TestClass()

print(TestClass.val1)  # 100
print(inst.val1)  # 100
print(inst.val2)  # 200

print(inst.val3)  # 'TestClass' object has no attribute 'val3'
print(inst.val4)  # 'TestClass' object has no attribute 'val4'
print(inst.val5)  # 'TestClass' object has no attribute 'val5'
python 复制代码
inst1 = TestClass()
inst2 = TestClass()

print(inst1.val1)  # 100

inst1.val1 = 1000
print(inst1.val1)  # 1000

print(TestClass.val1)  # 100
print(inst2.val1)  # 100

TestClass.val1 = 2000
print(TestClass.val1)  # 2000
print(inst1.val1)  # 1000     被重新赋值后,就跟类变量没关系了
print(inst2.val1)  # 2000     没有重新赋值,跟类变量保存一致

inst3 = TestClass()
print(inst3.val1)  # 2000

不可变对象与可变对象

python 内置的一些类型中

  • 可变对象:list dict set

  • 不可变对象:tuple string int float bool

Python 中万物皆对象,每个对象包含 3 个属性,id,type,value

  • id :对象地址,可以通过内置函数 id() 查看对象引用的地址。

  • type :对象类型,可以通过内置函数 type() 查看对象的类型。

  • value :对象的值


is==

  • is 比较的是 id 是不是一样
  • == 比较的是值是不是一样。
python 复制代码
a = 1
b = a
c = 1
d = 1.0
print(id(a))  # 140729241962160
print(id(b))  # 140729241962160
print(id(c))  # 140729241962160
print(id(d))  # 2070028299024
print(a is d)  # False
print(a == d)  # True
  • 判断 a is d 的时候,实际上比较的是 id(d)==id(d),结果为 False。
  • 判断 a==d 的时候,实际上比较的是 id(a) 这个地址指向的值是不是和 id(d) 这个地址指向值一样。结果为 True。

python 为了实现对内存的有效利用,对小整数 [-5,256] 内的整数会进行缓存,不在该范围内的则不会缓存。

注意在 python 交互式界面有内存池缓存机制,只适用于-5~256,在 python 脚本编程中则没有这个限制:

python 复制代码
a = 255
b = 255
print(a is b)  # True

c = 257
d = 257
print(c is d)  # True

Python console

python 复制代码
a = 255
b = 255
id(a)
Out[10]: 2922684620976
id(b)
Out[11]: 2922684620976
a is b
Out[4]: True
python 复制代码
c = 257
d = 257
id(c)
Out[8]: 2920614082448
id(d)
Out[9]: 2920614081904    
c is d
Out[7]: False

类属性

python 复制代码
class Person(object):
    tall = 180
    hobbies = []

    def __init__(self, name, age, weight):
        self.name = name
        self.age = age
        self.weight = weight

    def inform(self):
        print('%s is %s weights %s' % (self.name, self.age, self.weight))

特殊的类属性:对于所有的类,都有一组特殊的属性

__name__:类的名字(字符串)

__doc__ :类的文档字符串

__bases__:类的所有父类组成的元组

__dict__:类的属性组成的字典

__module__:类所属的模块

__class__:类对象的类型

python 复制代码
print(Person.__name__)  # Person
print(Person.__doc__)  # None
print(Person.__bases__)  # (<class 'object'>,)
print(Person.__dir__)  # <method '__dir__' of 'object' objects>
print(Person.__module__)  # __main__
print(Person.__class__)  # <class 'type'>

__dict__dir() 区别

  • __dict__ 属性:
    • 类的 __dict__ 存储 所有实例共享的变量和函数 (类属性,方法等),类的 __dict__不包含其父类的属性
    • 实例的 __dict__ 属性 仅仅是那个实例的 实例属性的集合 ,并不包含该实例的所有有效属性,正是因为实例的 __dict__ 属性,每个实例的实例属性才会互不影响。
    • python 一切皆对象,并不是所有对象都拥有 __dict__ 属性 。许多内建类型就没有 __dict__ 属性,如 list,此时就需要用 dir() 来列出对象的所有属性。
  • dir() 函数: dir() 是 Python 提供的一个 API 函数,dir() 函数会自动寻找一个对象的所有属性(包括从父类中继承的属性 )。 所以如果想获取一个对象所有有效属性,应使用 dir()

python 复制代码
# 类数据属性属于类本身,可以通过类名进行访问/修改,此处添加"football"、"woman"两个
Person.hobbies.extend(["football", "woman"])
print(Person.hobbies)  # ['football', 'woman']

# 在类定义之后,可以通过类名动态添加类数据属性,新增的类属性也被类和所有实例共有
Person.hobbies2 = ["reading", "jogging", "swimming"]
print(Person.hobbies2)  # ['reading', 'jogging', 'swimming']

# 实例数据属性只能通过实例访问
Bruce = Person("Bruce", 25, 60)
print(f"{Bruce.name} is {Bruce.age} years old")  # Bruce is 25 years old

# 在实例生成后,还可以动态添加实例数据属性,但是这些实例数据属性只属于该实例
Bruce.gender = "male"
print("{Bruce.name} is {Bruce.gender}")  # Bruce is male

# class instance can access class attribute
Bruce.hobbies.append("C#")
print(Bruce.hobbies)  # ['football', 'woman', 'C#']
print(Bruce.hobbies2)  # ['reading', 'jogging', 'swimming']

类数据属性属于类本身,被所有该类的实例共享并且,通过实例可以去访问/修改类属性。但是,在通过实例中访问类属性的时候一定要谨慎,因为可能出现 属性 "隐藏" 的情况

对于 不可变类型 的类属性,隐藏属性 可以总结为:

  • 对于 不可变类型 的类属性 person.tall,可以通过实例 Bruce 进行访问,并且 "person.tall is Bruce.tall"
  • 当通过实例赋值/修改 tall 属性的时候,将为实例 Bruce 新建一个 tall 实例属性,这时,"person.tall is not Bruce.tall"
  • 当通过 "del Bruce.tall" 语句删除实例的 tall 属性后,再次成为 "person.tall is Bruce.tall"
python 复制代码
# 对于不可变类型的类属性person.tall,可以通过实例Bruce进行访问
print(Person.tall is Bruce.tall)  # True

# 重新赋值或者修改
Bruce.tall = 185
print(Bruce.tall)  # 185
print(Person.tall is Bruce.tall)  # False

# 再次删除实例的赋值
del Bruce.tall
print(Bruce.tall)  # 180
print(Person.tall is Bruce.tall)  # True

对于 可变类型 的类属性,隐藏属性 可以总结为:

  • 同样对于 可变类型 的类属性 person.hobbies,可以通过实例 Bruce 进行访问,并且 "person.hobbies is Bruce hobbies"
  • 当通过实例赋值 hobbies 属性的时候,都将为实例 Bruce 新建一个 hobbies 实例属性,这时,"person.hobbies is not Bruce hobbies"
  • 当通过 "del Bruce. hobbies" 语句删除实例的 hobbies 属性后,再次成为 "person. hobbies is Bruce hobbies"
  • 当通过实例修改 hobbies 属性的时候,将修改 Bruce. hobbies 指向的内存地址(即 person.hobbies),此时,"person.hobbies is Bruce. hobbies"
python 复制代码
# 对于可变类型的类属性person.hobbies,可以通过实例Bruce进行访问
print(Person.hobbies is Bruce.hobbies)  # True

Bruce.hobbies.append("CSS")
print(Person.hobbies is Bruce.hobbies)  # True
print(Person.hobbies)  # ['football', 'woman', 'C#', 'CSS']

Will = Person("Will", 27, 60)
print(f"{Will.name} is {Will.age} years old") # Will is 27 years old

# Will shares the same class attribute with wilber
# Will don't have the "gender" attribute that belongs to wilber
print(Will.hobbies)  # ['football', 'woman', 'C#', 'CSS']
print(Will.gender)  # AttributeError: 'Person' object has no attribute 'gender'

注意,虽然通过实例可以访问类属性,但是,不建议这么做,最好还是通过类名来访问类属性,从而避免属性隐藏带来的不必要麻烦


__del__

python 复制代码
class Test:
    # 当内存不需要的时候调用这个删除方法,python解释器自动调用
    def __del__(self):
        print("Over")


t1 = Test()
t2 = t1
del t1
del t2
print("==========")

# Over
# ==========
python 复制代码
class Test:
    # 当内存不需要的时候调用这个删除方法,python解释器自动调用
    def __del__(self):
        print("Over")

t1 = Test()
t2 = t1
del dog1
print("==========")

# ==========
# Over

当删除了 t1,内存空间还没有结束,还不会调用 __del__ 方法,当调用完最后一条语句时,内存空间被释放,调用 __del__ 方法

类 class 的访问控制

"_" 和 "__" 的使用 更多的是一种规范/约定,并没有真正达到限制的目的

单下划线 "_"

以单下划线开头的表示的是 protected 类型的变量,即只能 允许其本身与子类 进行访问;同时表示弱内部变量标示,如 "from moduleName import * " 将不会引入以单下划线 "_" 开头的函数

双下划线 "__"

双下划线的表示的是 private 类型的变量。只能是 允许这个类本身 进行访问了,连子类也不可以,这类属性在运行时属性名会加上单下划线和类名。

  • 对于 Python 中的类属性,可以通过双下划线 "__" 来实现 一定程度的私有化,因为双下划线开头的属性在运行时会被 "混淆"(mangling)

  • 双下划线的另一个重要的目地是,避免子类对父类同名属性的冲突

  • 单下划线,可被重写 ,调用 子类方法

  • 双下划线,不能被重写 ,调用的还是 父类方法

python 复制代码
class A(object):
    def __init__(self):
        self.__private()
        self.public()

    def __private(self):
        print('A.__private()')

    def public(self):
        print('A.public()')


class B(A):
    def __private(self):
        print('B.__private()')

    def public(self):
        print('B.public()')


b = B()

# A.__private()
# B.public()

类 class 的继承

在 Python 中,同时支持 单继承多继承

实现继承之后,子类将继承父类的属性(除了文档字符串),也可以使用内建函数 insubclass() 来判断一个类是不是另一个类的子孙类


类型比较

  • type() 不会认为子类是一种父类类型
  • isinstance() 会认为子类是一种父类类型。
python 复制代码
class Foo(object):
 pass

class Bar(Foo):
 pass

print(type(Foo()) == Foo)  # True
print(type(Bar()) == Foo)  # False
print(isinstance(Foo(), Foo))  # True
print(isinstance(Bar(), Foo))  # True

Example 1

python 复制代码
class A:
    def __init__(self):
        self.__j = 1
        self.number = 5

class B(A):
    def __init__(self):
        self.__j = 2
        self.number = 7

    def show(self):
        print(self.__j, self.number)

b = B()
b.show() # 2 7

Example 2

python 复制代码
class A(object):
    def __method(self):
        print("I'm a method in A")

    def method(self):
        self.__method()

class B(A):
    def __method(self):
        print("I'm a method in B")


B().method()  # I'm a method in A
A().method()  # I'm a method in A

Example 3

python 复制代码
class Parent(object):
    """
    parent class
    """
    pass


class Child(Parent):
    pass


# doc属性不会被继承
print(Parent.__doc__)  # parent class
print(Child.__doc__)  # None

super 的使用

super 主要显式调用父类,在子类中,一般会定义与父类相同的属性(数据属性,方法),从而来实现子类特有的行为。也就是说,子类会继承父类的所有的属性和方法,子类也可以覆盖父类同名的属性和方法

调用父类的方法:

  1. 将"self"显式的传递进去:
python 复制代码
class Parent(object):
    Value = "Hi, Parent value"
    def fun(self):
        print("This is from Parent")

class Child(Parent):
    Value = "Hi, Child  value"
    def fun(self):
        print("This is from Child")
        # 调用父类Parent的fun函数方法
        Parent.fun(self)  

c = Child()
c.fun()

# This is from Child
# This is from Parent
  1. 使用 Python 中的 super 关键字
python 复制代码
class Parent(object):
    Value = "Hi, Parent value"
    def fun(self):
        print("This is from Parent")

class Child(Parent):
    Value = "Hi, Child  value"
    def fun(self):
        print("This is from Child")
        # 相当于用super的方法与上一调用父类的语句置换
        super(Child, self).fun()

c = Child()
c.fun()

# This is from Child
# This is from Parent

super()

对于你定义的每一个类,Python 会计算出一个所谓的 方法解析顺序(MRO)列表 。 这个 MRO 列表就是一个简单的所有基类的线性顺序表。为了实现继承,Python 会在 MRO 列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。

而这个 MRO 列表的构造是通过一个 C3 线性化算法来实现的。 实际上就是 合并所有父类的 MRO 列表并遵循如下三条准则

  • 子类会先于父类被检查
  • 多个父类会根据它们在列表中的顺序被检查
  • 如果对下一个类存在两个合法的选择,选择第一个父类

虽然名义上来说 super 是用来调用父类中的方法,但是 super 实际上是在 MRO 表中找到下一个匹配的类。super 原型如下:

python 复制代码
def super(cls, inst):
    mro = inst.__class__.mro()
    return mro[mro.index(cls) + 1]

两个参数 clsinst 分别做了两件事:

  1. inst 负责生成 MRO 的 list
  2. 通过 cls 定位当前 MRO 中的 index, 并返回 mro[index + 1]

但是根据我们上面说的 super 本质知道 super 和父类其实没有实质关联,我们就不难理解为什么 enter B 下一句是 enter C 而不是 enter A 了(如果认为 super 代表"调用父类的方法",会想当然的认为下一句应该是 enter A)。

可以用 self.__class__.__mro__ 方法来 查询当前 MRO

一个多继承中的 MRO 是固定的(只要每个类之间都有继承关系)

Example:

python 复制代码
class A(object):
    def __init__(self):
        print("Enter A")

class B(A):
    def __init__(self):
        print('Enter B')
        super(B, self).__init__()
        print('Leave B')

class C(A):
    def __init__(self):
        print('Enter C')
        super(C, self).__init__()
        print('Leave C')

class D(B, C):
    def __init__(self):
        print('Enter D')
        super(D, self).__init__()
        print("Leave D")
        print(self.__class__.__mro__)
        
d = D()

# Enter D
# Enter B
# Enter C
# Enter A
# Leave C
# Leave B
# Leave D
# (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)

上方例子的中流程:

  1. class D 中,输出"Enter D " , 然后就会调用 super 方法,super() 方法,第一个参数是 D,在 MRO 列表中的下标(index)为 0,那么调用的下一个类就是下标为(index+1)的类,即 class B,

  2. 进入 class B,输出 "Enter B" , 再次调用 super(),此时的 index 为 1,那么调用的下一个类的 index 为 2,即 class C,输出"Enter C" .

  3. class C 中,调用 super(),进入 class A,输出"Enter A",

  4. 回到 class C ,输出 "Leave C" , 再回到 class B ,输出"Leave B", 然后回到 class D,输出"Leave D"。结束

当使用 super() 函数时,Python 会在 MRO 列表上继续搜索下一个类。 只要每个重定义的方法统一使用 super() 并只调用它一次, 那么控制流最终会遍历完整个 MRO 列表,每个方法也只会被调用一次。

__new__

__new__ 方法:类级别的方法

  1. 是在类准备将自身实例化时调用,并且至少需要传递一个参数 cls,此参数在实例化时由 python 解释器自动提供
  2. 始终是 类的类方法,即使没有被加上类方法装饰器;
  3. 必须要有返回值,返回实例化出来的实例 ;在自己实现 __new__() 时需要注意:可以 return 父类(通过 super(当前类名,cls)).__new__ 出来的实例,或者直接是 object 的 __new__ 出来的实例
python 复制代码
class A(object):
    pass

# 默认调用父类object的__new__()方法来构造该类的实例
a = A()
print(a)  # <__main__.A object at 0x000001BE6F8CD520>

class A(object):
    def __new__(cls):
        "重写__new__方法"
        return "abc"

a = A()
print(a)  # 'abc'
print(type(a))  # <class 'str'>

通过 __new__() 方法实现单例

python 复制代码
class Singleton(object):
    def __new__(cls, *args, **kwargs):
        if not hasattr(cls, "_instance"):
            cls._instance = super(Singleton, cls).__new__(cls)
        return cls._instance


a = Singleton()
b = Singleton()
c = Singleton()
print(a)  # <__main__.Singleton object at 0x000001F0B57D86D0>
print(b)  # <__main__.Singleton object at 0x000001F0B57D86D0>
print(c)  # <__main__.Singleton object at 0x000001F0B57D86D0>

__init__

__init__ 方法:实例级别的方法

  1. 有一个参数 self, 该 self 参数就是 __new__() 返回的实例;

  2. __init__()__new()__ 的基础上完成初始化动作,不需要返回值

__new__() 没有正确返回当前类 cls 的实例,那 __init__() 将不会被调用

创建的每个实例都有自己的属性,方便类中的实例方法调用;

python 复制代码
class A:
    def __new__(cls, *args, **kwargs):
        print("A' __new__")
        # return super(A,cls).__new__(cls)
        return object.__new__(cls)

    def __init__(self):
        print("A' __init__")


class B(A):
    def __new__(cls, *args, **kwargs):
        print("B' __new__")

    def __init__(self):
        print("B' __init__")

a = A()
# B' __new__  没有创建真正的类B,所以类B的构造函数没有调用
b = B()


# A' __new__
# A' __init__
# B' __new__

当在 Python 中出现继承的情况时,一定要注意初始化函数 __init__ 的行为:

  • 如果子类没有定义自己的初始化函数 ,父类的初始化函数会被 默认调用;但是如果要实例化子类的对象,则只能传入父类的初始化函数对应的参数,否则会出错

  • 如果子类定义了自己的初始化函数 ,而在子类中 没有显式调用 父类的初始化函数,则父类的属性 不会 被初始化

  • 如果子类定义了自己的初始化函数 ,在子类中 显式调用 父类,子类和父类的属性都 被初始化

元类

类是能够创建出类实例的对象。类本身也是实例,它们是元类的实例

Python 中的一切都是对象,它们要么是类的实例,要么是元类的实例,除了 typetype 实际上是它自己的元类,在纯 Python 环境中这可不是你能够做到的,这是通过在实现层面耍一些小手段做到的。

其次,元类是很复杂的。对于非常简单的类,你可能不希望通过使用元类来对类做修改。你可以通过其他两种技术来修改类:

python 复制代码
class A:
    pass

print(type(1))  # <class 'int'>
print(type(type(1)))  # <class 'type'>
print(type(int))  # <class 'type'>
print(type(A))  # <class 'type'>

元类是类的类,常可以用在类工厂中;

  • Python 中所有的类都是对象,可以通过 type() 来创建元类
  • 在定义类时,可用过 metaclass 参数来指定此类的元类
  • Python 类语句执行时,会先 查找其类本身的 metaclass 属性 ,如果没找到,会继续在 父类 中找,还没找到,则到 模块 中找,最后再用 内置的 type 来创建此类对象
  • 使用类、函数都可以当作元类,通常在 __new__ 方法中通过 type 来自定义自己的元类
  • 从设计的复杂度来讲,尽量少用元类,多用普通类或函数

并发与并行

并行

只有多个 CPU,才能实现并行;多个 CPU 同时执行任务(线程或者进程)

并发

CPU 轮流执行任务,每个任务执行 0.01s(举例),看起来是同时,实际上同一个时刻还是只执行一个任务;这种情况,称为并发;

线程 进程

线程

线程是 CPU 执行 的最基本单元

进程

进程是系统进行分配资源和调度的基本单位,是 操作系统执行 的基本单元;

多线程相比多进程的优势

  • 多线程无需重复申请资源,子线程和父线程共享资源;

  • 多线程间的通信速度快于进程通信,效率更高;

协程

协程 ,又称微线程。在 Python 语言中,单线程+异步 I/O 的编程模型称为协程。协程的特点是 只有一个线程 在执行,只有当子程序内部发生阻塞或者 IO 时,才会交出线程执行权给其他子程序,适当的时候再返回;

协程相比多线程的优势

  • 省去了大量线程切换的开销;
  • 由于是单线程执行,共享资源不需要加锁,执行效率更高;
python 复制代码
import asyncio
import time


async def say_after(delay, what):
    await asyncio.sleep(delay)
    print(f"{delay}: {what}")


async def main():
    task1 = asyncio.create_task(say_after(2, 'hello1'))
    task2 = asyncio.create_task(say_after(2, 'world1'))
    print(f"started at {time.strftime('%X')}")
    await say_after(1, 'hello2')
    await say_after(2, 'world2')
    await task1
    await task2
    print(f"finished at {time.strftime('%X')}")


asyncio.run(main())
python 复制代码
# started at 10:19:42
# 1: hello2
# 2: hello1
# 2: world1
# 2: world2
# finished at 10:19:45

要真正运行一个协程,asyncio 提供了三种主要机制:

  • asyncio.run() 函数用来运行最高层级的入口点 "main()" 函数

  • 等待一个协程await say_after(1, 'hello2') await say_after(2, 'world2') 会在等待 1 秒后打印 "hello2",然后 再次 等待 <math xmlns="http://www.w3.org/1998/Math/MathML"> 2 2 </math>2 秒后打印 "world2":

  • asyncio.create_task() 函数用来 并发运行 作为 asyncio 任务 的多个协程。

  • 总用时 <math xmlns="http://www.w3.org/1998/Math/MathML"> 3 3 </math>3 s

死锁

两个或两个以上的进程在执行过程中, 因争夺资源而造成的一种 互相等待 的现象, 若无 外力 作用, 它们都将无法推进下去。

产生死锁的原因:

  1. 因为系统资源不足。
  2. 进程运行推进顺序不合适。
  3. 资源分配不当等。

死锁的必要条件

  1. 互斥条件:一个资源每次只能被一个进程使用。
  2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
  3. 不剥夺条件: 进程已获得的资源,在末使用完之前,不能强行剥夺。
  4. 循环等待条件: 若干进程之间形成一种头尾相接的循环等待资源关系

死锁的避免:

死锁的预防是通过破坏产生条件来阻止死锁的产生,但这种方法破坏了系统的并行性和并发性。

死锁产生的前三个条件是死锁产生的必要条件,也就是说要产生死锁必须具备的条件,而不是存在这 3 个条件就一定产生死锁,那么只要在逻辑上回避了第四个条件就可以避免死锁。避免死锁采用的是允许前三个条件存在,但通过合理的资源分配算法来确保永远不会形成环形等待的封闭进程链,从而避免死锁。该方法支持多个进程的并行执行,为了避免死锁,系统动态的确定是否分配一个资源给请求的进程。

银行家算法:分配资源之前先看清楚,资源分配后是否会导致系统死锁。如果会死锁,则不分配,否则就分配。要求每个进程必须先知道资源的最大需求量,且在系统运行过程中,考察每个进程对各类资源的申请需要花费较多的时间。

进程间通信方式:

管道、共享存储器系统、消息传递系统、信号量

mutex 是互斥锁

相关推荐
秀儿还能再秀1 小时前
淘宝母婴购物数据可视化分析(基于脱敏公开数据集)
python·数据分析·学习笔记·数据可视化
计算机老学长1 小时前
基于Python的商品销量的数据分析及推荐系统
开发语言·python·数据分析
千益2 小时前
玩转python:系统设计模式在Python项目中的应用
python·设计模式
&白帝&2 小时前
Java @PathVariable获取路径参数
java·开发语言·python
Shepherdppz3 小时前
python量化交易——金融数据管理最佳实践——使用qteasy大批量自动拉取金融数据
python·金融·量化交易
互联网杂货铺3 小时前
python+pytest 接口自动化测试:参数关联
自动化测试·软件测试·python·测试工具·职场和发展·测试用例·pytest
筱涵哥3 小时前
Python默认参数详细教程:默认参数位置错误,动态默认值,__defaults__属性,动态默认值处理,从入门到实战的保姆级教程
开发语言·python
yzztin3 小时前
Python 导包和依赖路径问题
python
pk_xz1234563 小时前
介绍如何基于现有的可运行STGCN(Spatial-Temporal Graph Convolutional Network)模型代码进行交通流预测的改动
python