Обратите внимание, что есть случаи, когда вы определили свой собственный пользовательский класс и хотите сохранить атрибуты, тогда вы должны использовать copy.copy()
или copy.deepcopy()
, а не альтернативы, например, в Python 3:
import copy
class MyList(list):
pass
lst = MyList([1,2,3])
lst.name = 'custom list'
d = {
'original': lst,
'slicecopy' : lst[:],
'lstcopy' : lst.copy(),
'copycopy': copy.copy(lst),
'deepcopy': copy.deepcopy(lst)
}
for k,v in d.items():
print('lst: {}'.format(k), end=', ')
try:
name = v.name
except AttributeError:
name = 'NA'
print('name: {}'.format(name))
Выходы:
lst: original, name: custom list
lst: slicecopy, name: NA
lst: lstcopy, name: NA
lst: copycopy, name: custom list
lst: deepcopy, name: custom list
Рассмотрим следующий код:
class Foo:
def bar(self) -> Foo:
return Foo()
Эта программа фактически завершится сбоем во время выполнения, если вы попытаетесь запустить ее с Python: когда интерпретатор увидит определение bar
, определение Foo
будет еще не закончен. Итак, поскольку Foo
еще не добавлено в глобальное пространство имен, мы пока не можем использовать его в качестве подсказки типа.
Точно так же рассмотрим эту программу:
class Foo:
def bar(self) -> Bar:
return Bar()
class Bar:
def foo(self) -> Foo:
return Foo()
Это взаимозависимое определение страдает от той же проблемы: в то время как мы оцениваем Foo
, Bar
еще не была оценена, поэтому интерпретатор бросает исключение.
Есть три решения этой проблемы. Во-первых, вы можете создать некоторые из ваших строк подсказок типа, эффективно «объявив их вперед»:
class Foo:
def bar(self) -> "Foo":
return Foo()
Это удовлетворяет интерпретатору Python и не нарушает сторонние инструменты, такие как mypy: они могут просто удалить кавычки перед разбором типа. Основным недостатком является то, что этот синтаксис выглядит некрасиво и неуклюже.
Второе решение заключается в использовании синтаксиса комментариев типа:
class Foo:
def bar(self):
# type: () -> Foo
return Foo()
Это имеет те же преимущества и недостатки, что и первое решение: оно удовлетворяет интерпретатору и инструментам, но выглядит хакерским и уродливым. Он также имеет дополнительное преимущество, заключающееся в том, что он поддерживает ваш код обратно совместимым с Python 2.7.
Третье решение - только Python 3.7+ - используйте директиву from __future__ import annotations
:
from __future__ import annotations
class Foo:
def bar(self) -> Foo:
return Foo()
Это автоматически сделает все аннотации представленными в виде строк. Таким образом, мы получаем преимущество первого решения, но без уродства.
Это поведение в конечном итоге станет стандартным в будущих версиях Python.
Также оказывается, что автоматическое создание всех строк аннотаций может привести к некоторым улучшениям производительности. Создание типов, таких как List[Dict[str, int]]
, может быть удивительно дорогим: они просто регулярные выражения во время выполнения и оцениваются так, как если бы они были написаны как List.__getitem__(Dict.__getitem__((str, int))
.
Оценить это выражение довольно дорого: в итоге мы выполняем два вызова метода, создаем кортеж и создаем два объекта. Конечно, это не считая дополнительной работы, которая происходит в самих методах __getitem__
- и работа, которая происходит в этих методах, оказывается нетривиальной из-за необходимости.
(Короче говоря, им нужно создавать специальные объекты, которые гарантируют, что типы, подобные List[int]
, не могут быть использованы ненадлежащим образом во время выполнения - например, в isinstance
проверках и т. П.)