Pontos de aprendizagem Python (2)

Meu blog: https://www.luozhiyun.com/archives/269

'==' VS 'é'

O operador '==' compara os valores entre objetos para igualdade.
O operador 'is' compara se a identidade dos objetos é igual, ou seja, se são o mesmo objeto, se apontam para o mesmo endereço de memória.

Tais como:

a = 10
b = 10
 
a == b
True
 
id(a)
4427562448
 
id(b)
4427562448
 
a is b
True

O Python abrirá uma memória para o valor de 10 e, em seguida, as variáveis ​​a e b apontam para essa área de memória ao mesmo tempo, ou seja, a e b apontam para a variável 10, para que os valores de a e b sejam iguais e os IDs também sejam iguais.

No entanto, para números inteiros, a conclusão de que a é b é True acima se aplica apenas a números no intervalo de -5 a 256. Isso é semelhante ao cache inteiro do java, cache java -127 a 128.

Quando comparamos uma variável a um singleton, geralmente usamos 'is'. Um exemplo típico é verificar se uma variável é None:

if a is None:
      ...
 
if a is not None:
      ...

A eficiência da velocidade do operador de comparação 'é' geralmente é melhor que '=='. Como o operador 'is' não pode ser sobrecarregado, executar a == b é equivalente a executar a. Eq (b), e a maioria dos tipos de dados do Python sobrecarrega a função __eq__.

Cópia rasa e cópia profunda

Cópia rasa

Cópia rasa refere-se à realocação de um pedaço de memória para criar um novo objeto, e os elementos nele são referências aos objetos filho no objeto original. Portanto, se os elementos no objeto original são imutáveis, isso não importa; mas se os elementos são variáveis, a cópia superficial geralmente traz alguns efeitos colaterais, como a seguir:

l1 = [[1, 2], (30, 40)]
l2 = list(l1)
l1.append(100)
l1[0].append(3)
 
l1
[[1, 2, 3], (30, 40), 100]
 
l2
[[1, 2, 3], (30, 40)]
 
l1[1] += (50, 60)
l1
[[1, 2, 3], (30, 40, 50, 60), 100]
 
l2
[[1, 2, 3], (30, 40)]

Neste exemplo, como o elemento na cópia superficial é uma referência ao elemento original do objeto, o elemento em l2 e l1 se refere à mesma lista e objeto de tupla.

l1 [0] .append (3), aqui significa adicionar o elemento 3 à primeira lista em l1. Como l2 é uma cópia superficial de l1, o primeiro elemento em l2 e o primeiro elemento em l1 apontam para a mesma lista; portanto, a primeira lista em l2 também corresponderá ao elemento recém-adicionado 3.

l1 [1] + = (50, 60), porque a tupla é imutável, o que significa emendar a segunda tupla em l1 e, em seguida, recriar uma nova tupla como o segundo elemento em l1, A nova tupla não é referenciada em l2, portanto, l2 não é afetado.

Cópia profunda

A chamada cópia profunda refere-se à realocação de um pedaço de memória, criação de um novo objeto e cópia recursiva dos elementos no objeto original para o novo objeto, criando novos subobjetos. Portanto, o novo objeto não está relacionado ao objeto original.

O Python usa copy.deepcopy () para implementar cópias profundas de objetos.

import copy
l1 = [[1, 2], (30, 40)]
l2 = copy.deepcopy(l1)
l1.append(100)
l1[0].append(3)
 
l1
[[1, 2, 3], (30, 40), 100]
 
l2 
[[1, 2], (30, 40)]

No entanto, a cópia profunda não é perfeita e muitas vezes traz uma série de problemas. Se houver uma referência a si próprio no objeto copiado, o programa poderá cair facilmente em um loop infinito:

import copy
x = [1]
x.append(x)
 
x
[1, [...]]
 
y = copy.deepcopy(x)
y
[1, [...]]

Não há fenômeno de estouro de pilha aqui, porque a função de cópia profunda deepcopy mantém um dicionário para registrar os objetos copiados e seus IDs. Durante o processo de cópia, se o objeto a ser copiado já estiver armazenado no dicionário, ele será retornado diretamente do dicionário.

def deepcopy(x, memo=None, _nil=[]):
    """Deep copy operation on arbitrary Python objects.
    	
	See the module's __doc__ string for more info.
	"""
	
    if memo is None:
        memo = {}
    d = id(x) # 查询被拷贝对象 x 的 id
	y = memo.get(d, _nil) # 查询字典里是否已经存储了该对象
	if y is not _nil:
	    return y # 如果字典里已经存储了将要拷贝的对象,则直接返回
        ...    

Passagem de parâmetro Python

A transferência de parâmetros no Python é transferência de atribuição ou transferência de referência chamada objeto. A atribuição ou passagem de referência do objeto aqui não aponta para um endereço de memória específico, mas aponta para um objeto específico.

  • Se o objeto for mutável, quando ele mudar, todas as variáveis ​​que apontam para o objeto serão alteradas.
  • Se o objeto for imutável, a atribuição simples poderá alterar apenas o valor de uma das variáveis, as demais variáveis ​​não serão afetadas.

Por exemplo:

def my_func1(b):
	b = 2
 
a = 1
my_func1(a)
a
1

O parâmetro que passa aqui faz as variáveis ​​aeb apontarem para o objeto 1 ao mesmo tempo. Mas quando executamos para b = 2, o sistema recriará um novo objeto com o valor 2 e permitirá que b aponte para ele; enquanto a ainda aponta para o objeto 1. Portanto, o valor de a permanece inalterado e permanece 1.

def my_func3(l2):
	l2.append(4)
 
l1 = [1, 2, 3]
my_func3(l1)
l1
[1, 2, 3, 4]

Aqui, l1 e l2 apontam primeiro para uma lista com valores de [1, 2, 3] ao mesmo tempo. No entanto, devido à lista de variáveis, quando a função append () é executada e um novo elemento 4 é adicionado ao final, os valores das variáveis ​​l1 e l2 também mudam de acordo.

def my_func4(l2):
	l2 = l2 + [4]
 
l1 = [1, 2, 3]
my_func4(l1)
l1
[1, 2, 3]

Aqui l2 = l2 + [4], o que significa que uma nova lista de "adição do elemento 4 no final" é criada e deixe l2 apontar para esse novo objeto. Esse processo não tem nada a ver com l1, portanto o valor de l1 permanece inalterado.

Decorador

Primeiro, examinamos um exemplo simples de um decorador:

def my_decorator(func):
    def wrapper():
        print('wrapper of decorator')
        func()
    return wrapper
 
def greet():
    print('hello world')
 
greet = my_decorator(greet)
greet()
 
# 输出
wrapper of decorator
hello world

Nesse código, a variável greet aponta para o invólucro da função interna () e o invólucro da função interna () chama a função original greet (); portanto, quando a última chamada para greet (), primeiro imprime 'invólucro do decorador', Em seguida, imprima 'olá mundo'.

my_decorator () é um decorador que agrupa a função greet () que realmente precisa ser executada e altera seu comportamento.

Em python, você pode usar uma maneira mais elegante:

def my_decorator(func):
    def wrapper():
        print('wrapper of decorator')
        func()
    return wrapper
 
@my_decorator
def greet():
    print('hello world')
 
greet()

@my_decorator é equivalente à instrução greet = my_decorator (greet) anterior

Normalmente, usaremos args e ** kwargs como argumentos para a função wrapper () do decorador. args e ** kwargs indicam que qualquer número e tipo de parâmetros são aceitos; portanto, o decorador pode ser escrito da seguinte forma:

def my_decorator(func):
    def wrapper(*args, **kwargs):
        print('wrapper of decorator')
        func(*args, **kwargs)
    return wrapper

Isso permite que o decorador aceite parâmetros arbitrários.

Decorador para parâmetros personalizados

Por exemplo, quero definir um parâmetro para indicar o número de vezes que a função interna do decorador é executada

def repeat(num):
    def my_decorator(func):
        def wrapper(*args, **kwargs):
            for i in range(num):
                print('wrapper of decorator')
                func(*args, **kwargs)
        return wrapper
    return my_decorator
 
 
@repeat(4)
def greet(message):
    print(message)
 
greet('hello world')
 
# 输出:
wrapper of decorator
hello world
wrapper of decorator
hello world
wrapper of decorator
hello world
wrapper of decorator
hello world

Reter as metainformações da função original

Como segue:

greet.__name__
## 输出
'wrapper'
 
help(greet)
# 输出
Help on function wrapper in module __main__:
 
wrapper(*args, **kwargs)

Depois que a função greet () é decorada, suas meta informações são alteradas. As informações meta nos dizem que "não é mais a função greet () anterior, mas substituída pela função wrapper ()".

Portanto, você pode adicionar o decorador interno @ functools.wrap, que ajudará a reter as meta informações da função original.
Como segue:

import functools
 
def my_decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print('wrapper of decorator')
        func(*args, **kwargs)
    return wrapper
    
@my_decorator
def greet(message):
    print(message)
 
greet.__name__
 
# 输出
'greet'

Decorador de classe

O decorador da classe depende principalmente da função __call_ (). Sempre que você chamar um exemplo de classe, a função __call __ () será executada uma vez.

class Count:
    def __init__(self, func):
        self.func = func
        self.num_calls = 0
 
    def __call__(self, *args, **kwargs):
        self.num_calls += 1
        print('num of calls is: {}'.format(self.num_calls))
        return self.func(*args, **kwargs)
 
@Count
def example():
    print("hello world")
 
example()
 
# 输出
num of calls is: 1
hello world
 
example()
 
# 输出
num of calls is: 2
hello world
  

Aninhamento do decorador

Tais como:

@decorator1
@decorator2
@decorator3
def func():
    ...

É equivalente a:

decorator1(decorator2(decorator3(func)))

Exemplos:

import functools
 
def my_decorator1(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print('execute decorator1')
        func(*args, **kwargs)
    return wrapper
 
 
def my_decorator2(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print('execute decorator2')
        func(*args, **kwargs)
    return wrapper
 
 
@my_decorator1
@my_decorator2
def greet(message):
    print(message)
 
 
greet('hello world')
 
# 输出
execute decorator1
execute decorator2
hello world

Coroutine

A diferença entre corotinas e multithreading reside principalmente em dois pontos: um é que as corotinas são de rosca única; o outro é que as corotinas são determinadas pelos usuários, onde entregar o controle e passar para a próxima tarefa.

Vamos primeiro ver um exemplo:

import asyncio
 
async def crawl_page(url):
    print('crawling {}'.format(url))
    sleep_time = int(url.split('_')[-1])
    await asyncio.sleep(sleep_time)
    print('OK {}'.format(url))
 
async def main(urls):
    tasks = [asyncio.create_task(crawl_page(url)) for url in urls]
    for task in tasks:
        await task
 
%time asyncio.run(main(['url_1', 'url_2', 'url_3', 'url_4']))
 
########## 输出 ##########
 
crawling url_1
crawling url_2
crawling url_3
crawling url_4
OK url_1
OK url_2
OK url_3
OK url_4
Wall time: 3.99 s

Existem muitas maneiras de executar corotinas, aqui apresento três comumente usadas:

Primeiro, podemos chamá-lo através de aguardar. O efeito de aguardar execução é o mesmo que a execução normal do Python, o que significa que o programa será bloqueado aqui, entrará na função chamada coroutine e continuará após a conclusão da execução.Este também é o significado literal de aguardar.

Segundo, podemos criar uma tarefa através de asyncio.create_task (). Para aguardar o término de todas as tarefas, basta usar a tarefa nas tarefas: aguardar tarefa.

Finalmente, precisamos de asyncio.run para disparar a corrida. A função asyncio.run é um recurso disponível apenas após o Python 3.7. Uma especificação de programação muito boa é que asyncio.run (main ()), como função de entrada do programa principal, chama apenas asyncio.run uma vez no ciclo de execução do programa.

No exemplo acima, você também pode usar waitit asyncio.gather (* tarefas), o que significa aguardar todas as tarefas.

import asyncio
 
async def crawl_page(url):
    print('crawling {}'.format(url))
    sleep_time = int(url.split('_')[-1])
    await asyncio.sleep(sleep_time)
    print('OK {}'.format(url))
 
async def main(urls):
    tasks = [asyncio.create_task(crawl_page(url)) for url in urls]
    await asyncio.gather(*tasks)
 
%time asyncio.run(main(['url_1', 'url_2', 'url_3', 'url_4']))
 
########## 输出 ##########
 
crawling url_1
crawling url_2
crawling url_3
crawling url_4
OK url_1
OK url_2
OK url_3
OK url_4
Wall time: 4.01 s

Manipulação de interrupção e exceção de corotina

import asyncio
 
async def worker_1():
    await asyncio.sleep(1)
    return 1
 
async def worker_2():
    await asyncio.sleep(2)
    return 2 / 0
 
async def worker_3():
    await asyncio.sleep(3)
    return 3
 
async def main():
    task_1 = asyncio.create_task(worker_1())
    task_2 = asyncio.create_task(worker_2())
    task_3 = asyncio.create_task(worker_3())
 
    await asyncio.sleep(2)
    task_3.cancel()
 
    res = await asyncio.gather(task_1, task_2, task_3, return_exceptions=True)
    print(res)
 
%time asyncio.run(main())
 
########## 输出 ##########
 
[1, ZeroDivisionError('division by zero'), CancelledError()]
Wall time: 2 s

Neste exemplo, task_3.cancel () é usado para interromper o código e return_exceptions = True é usado para controlar a exceção de saída. Se não estiver definido, o erro será totalmente lançado em nossa camada de execução; portanto, tente, exceto o necessário, Isso significa que todas as outras tarefas que ainda não foram executadas serão canceladas.

Mecanismo de coleta de lixo em Python

O Python adota uma estratégia principalmente de mecanismo de contagem de referência, complementada por dois mecanismos de varredura de marcas e coleção geracional (coleção intergeracional).

Contagem de referência

O princípio do método de contagem de referência é que cada objeto mantenha um campo ob_ref, que é usado para registrar o número de vezes que o objeto é referenciado no momento. Sempre que uma nova referência aponta para o objeto, sua contagem de referência ob_ref ​​aumenta em 1 e sempre que o objeto A contagem ob_ref ​​é decrementada por 1. Quando a referência é inválida.Uma vez que a contagem de referência do objeto é 0, o objeto é imediatamente reciclado e o espaço de memória ocupado pelo objeto é liberado.

Sua desvantagem é que ele não pode resolver a "referência circular" do objeto.

Algoritmo de remoção de marcas

Para um gráfico direcionado, se começarmos a partir de um nó, atravessarmos e marcarmos todos os nós pelos quais ele passa, depois do final da travessia, todos os nós que não foram marcados serão chamados de nós inacessíveis. Obviamente, a existência desses nós não tem sentido.Naturalmente, precisamos reciclá-los.

Na implementação da coleta de lixo do Python, o mark-sweep mantém uma estrutura de dados usando uma lista duplamente vinculada e considera apenas objetos da classe container (somente objetos container podem produzir referências circulares).

Algoritmo de coleção geracional

Python divide todos os objetos em três gerações. O objeto recém-criado é a 0ª geração; após uma coleta de lixo, os objetos que ainda existem serão movidos da geração anterior para a próxima geração. O limite para iniciar a coleta de lixo automática para cada geração pode ser especificado separadamente. Quando novos objetos menos objetos excluídos no coletor de lixo atingem o limite correspondente, a coleta de lixo será iniciada para essa geração de objetos.

Acho que você gosta

Origin www.cnblogs.com/luozhiyun/p/12685722.html
Recomendado
Clasificación