不可变与可变类型


184

我对什么是不可变类型感到困惑。我知道该float对象被认为是不可变的,在我的书中有这样的例子:

class RoundFloat(float):
    def __new__(cls, val):
        return float.__new__(cls, round(val, 2))

由于类的结构/层次结构,这是否被认为是不可变的?意思float是在类的顶部,是它自己的方法调用。类似于此类示例(即使我的书说的dict是可变的):

class SortedKeyDict(dict):
    def __new__(cls, val):
        return dict.__new__(cls, val.clear())

可变的东西在类内部具有方法,例如以下类型:

class SortedKeyDict_a(dict):
    def example(self):
        return self.keys()

另外,对于最后一个class(SortedKeyDict_a),如果我将这种类型的set传递给它:

d = (('zheng-cai', 67), ('hui-jun', 68),('xin-yi', 2))

不调用该example方法,它返回一个字典。在SortedKeyDict__new__其标记为错误。我尝试使用将整数传递给RoundFloat类,__new__并且它未标记任何错误。


您还可以在何时使用copy.copy时使用[:]python来检查列表分配,我也回答了有关可变性的更多信息。
2011年

Answers:


230

什么?浮游物是一成不变的吗?但是我不能

x = 5.0
x += 7.0
print x # 12.0

那不是“ mut” x吗?

好吧,您同意字符串是不可变的,对吗?但是您可以做同样的事情。

s = 'foo'
s += 'bar'
print s # foobar

变量的值会更改,但是会通过更改变量引用的内容来更改。一个可变的类型可以改变这种方式,它可 “到位”而改变。

这是区别。

x = something # immutable type
print x
func(x)
print x # prints the same thing

x = something # mutable type
print x
func(x)
print x # might print something different

x = something # immutable type
y = x
print x
# some statement that operates on y
print x # prints the same thing

x = something # mutable type
y = x
print x
# some statement that operates on y
print x # might print something different

具体例子

x = 'foo'
y = x
print x # foo
y += 'bar'
print x # foo

x = [1, 2, 3]
y = x
print x # [1, 2, 3]
y += [3, 2, 1]
print x # [1, 2, 3, 3, 2, 1]

def func(val):
    val += 'bar'

x = 'foo'
print x # foo
func(x)
print x # foo

def func(val):
    val += [3, 2, 1]

x = [1, 2, 3]
print x # [1, 2, 3]
func(x)
print x # [1, 2, 3, 3, 2, 1]

5
您的解释对我而言意味着:可变变量通过引用传递,不可变变量通过值传递。这样对吗 ?
劳伦斯·梅耶

16
几乎,但不完全是。从技术上讲,所有变量都是在Python中通过引用传递的,但其语义更类似于C中的按值传递def f(my_list): my_list = [1, 2, 3]。使用C中的按引用传递时,可以通过调用该函数来更改参数的值。在Python中,该功能不执行任何操作。def f(my_list): my_list[:] = [1, 2, 3]会做某事。
Morningstar 2014年

6
可变类型可以在适当位置更改。不变类型不能在原地改变。这就是python看世界的方式。不管如何将变量传递给函数。
ychaouche 2014年

13
Python的语义和C ++的按引用传递语义之间的主要区别在于,赋值在Python中不是突变,而在C ++中是不变的。(但是,当然,由于增强的分配(a += b有时有时突变)这一事实使情况变得复杂。而且,分配给较大对象的一部分的事实有时意味着该较大对象的突变,而从未意味着该部分的突变,例如,a[0] = b不会突变a[0],但是它可能确实会发生变化a……这就是为什么最好不要尝试用C ++来表示,而只是以自己的方式描述Python所做的事情……)
abarnert 2015年

2
我发现此答案具有误导性,因为它不使用id(),这对于理解不可变的含义至关重要。
pawel_winzig,2016年

183

您必须了解Python将其所有数据表示为对象。其中一些对象(例如列表和字典)是可变的,这意味着您可以更改其内容而无需更改其标识。其他对象(例如整数,浮点数,字符串和元组)是无法更改的对象。一种简单的理解方法是查看对象ID。

在下面,您将看到一个不可变的字符串。您无法更改其内容。TypeError如果您尝试更改它,它将引发一个。同样,如果我们分配新内容,则会创建一个新对象,而不是修改内容。

>>> s = "abc"
>>>id(s)
4702124
>>> s[0] 
'a'
>>> s[0] = "o"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment
>>> s = "xyz"
>>>id(s)
4800100
>>> s += "uvw"
>>>id(s)
4800500

您可以使用列表来执行此操作,并且不会更改对象标识

>>> i = [1,2,3]
>>>id(i)
2146718700
>>> i[0] 
1
>>> i[0] = 7
>>> id(i)
2146718700

要阅读有关Python数据模型的更多信息,可以查看Python语言参考:


4
+1对于指向Python文档的链接。但是,我花了一些时间才意识到今天您需要区分Python 2和3-我更新了答案以强调这一点。
本杰明2015年

106

常见的不可变类型:

  1. 数字:int()float()complex()
  2. 不可变的序列:str()tuple()frozenset()bytes()

常见的可变类型(几乎所有其他类型):

  1. 可变序列:list()bytearray()
  2. 设置类型: set()
  3. 映射类型: dict()
  4. 类,类实例
  5. 等等

快速测试类型是否可变的一种技巧是使用id()内置函数。

例如,在整数上使用

>>> i = 1
>>> id(i)
***704
>>> i += 1
>>> i
2
>>> id(i)
***736 (different from ***704)

使用清单上的

>>> a = [1]
>>> id(a)
***416
>>> a.append(2)
>>> a
[1, 2]
>>> id(a)
***416 (same with the above id)

11
好解释。喜欢“通过...检查”的概念id()。+1。
Parag Tyagi 2014年

4
实际上,id()此处的使用会产生误导。给定对象在其生存期内始终具有相同的ID,但是由于垃圾回收,在不同时间存在的不同对象可能具有相同的ID。
augurar

36

首先,一个类是否具有方法或它的类结构与可变性无关。

ints和floats是不可变的。如果我做

a = 1
a += 5

它在第一行中将名称指向内存a中的1某个位置。在第二行,它查找的是1,添加5,获取6,然后点a那个6在记忆-它并没有改变1一个6以任何方式。使用其他不可变类型的相同逻辑适用于以下示例:

b = 'some string'
b += 'some other string'
c = ('some', 'tuple')
c += ('some', 'other', 'tuple')

对于可变类型,我可以做一些改变其存储在内存中的值的事情。带有:

d = [1, 2, 3]

我创建的位置的列表12以及3在内存中。如果我那么做

e = d

我只是点e相同list d的点。然后,我可以这样做:

e += [4, 5]

并且指向ed指向的列表也将更新为也具有45在内存中的位置。

如果我返回一个不可变的类型,并使用tuple

f = (1, 2, 3)
g = f
g += (4, 5)

然后f仍然只指向原始的tuple -您指向g了一个全新的tuple

现在,以您的示例

class SortedKeyDict(dict):
    def __new__(cls, val):
        return dict.__new__(cls, val.clear())

你通过的地方

d = (('zheng-cai', 67), ('hui-jun', 68),('xin-yi', 2))

(这是一个tupletuples)的val,你得到一个错误,因为tuple都不具备的一个.clear()方法-你必须通过dict(d)val它工作,在这种情况下,你会得到一个空的SortedKeyDict结果。


2
这是很好的解释。喜欢这个问题,并以很多有趣的(新的)观点对此进行了解释。
失败的科学家,

24

如果您是从另一种语言来学习Python(除了非常像Python的一种语言,例如Ruby),并且坚持要用另一种语言来理解它,那么人们通常会感到困惑:

>>> a = 1
>>> a = 2 # I thought int was immutable, but I just changed it?!

在Python中,分配不是Python中的变异。

在C ++中,如果您编写a = 2,则在调用a.operator=(2),这将使存储在中的对象发生突变a。(如果有没有存储在对象a,这是一个错误。)

在Python中,a = 2对存储在其中的任何内容均不执行任何操作a;它只是意味着2现在存储在其中a。(如果有没有存储在对象a,这很好。)


最终,这是更深层次区别的一部分。

像C ++这样的语言中的变量是内存中的键入位置。如果aint,则表示编译器知道应将其解释为的4个字节int。因此,执行此操作后a = 2,它将存储在这4个字节的内存中的内容从0, 0, 0, 1更改为0, 0, 0, 2。如果其他地方有另一个int变量,则它有自己的4个字节。

像Python这样的语言中的变量是具有自己生命的对象的名称。有一个数字对象1,另一个有数字对象2。而且a不是4个字节的内存以表示int,它只是指向1对象的名称。a = 2将数字1转换为数字2 是没有意义的(这会给任何Python程序员太多的力量来改变宇宙的基本工作原理);相反,它所做的只是a忘了1对象,2而是指向对象。


那么,如果分配不是突变,什么突变?

  • 调用已记录为变异的方法,例如a.append(b)。(请注意,这些方法几乎总是会返回None)。不可变类型没有任何此类方法,可变类型通常具有。
  • 分配给对象的一部分,例如a.spam = ba[0] = b。不可变类型不允许分配属性或元素,可变类型通常允许一个或另一个。
  • 有时使用增强分配,例如a += b,有时不使用。可变类型通常会改变值;不可变的类型永远不会做,而是给您一个副本(它们是计算a + b,然后将结果分配给a)。

但是,如果分配不是突变,那么如何分配给对象突变的一部分呢?那就是棘手的地方。a[0] = b确实发生变异a[0](再次,与C ++),但它确实发生变异a(不像C ++,除了间接地)。

所有这些就是为什么最好不要尝试将Python的语义放在您惯用的语言上,而是按照自己的术语学习Python的语义。


2
说一个=“嗨”。a [0] ='f'将具有'print a'打印输出'fi'(我到目前为止是对的吗?),所以当您说它不会使a [0]而不是a突变时,这意味着什么? ?a [n]现在也有它自己的位置,并且更改其值会将其指向另一个值吗?
丹尼尔·史普林格

19

对象是否可变取决于其类型。这不取决于它是否具有某些方法,也不取决于类层次结构的结构。

用户定义的类型(即类)通常是可变的。有一些例外,例如不可变类型的简单子类。其他不变类型包括一些内置的类型,如intfloattuplestr,如C实现以及一些Python类

来自《 Python语言参考》中“数据模型”一章的一般说明:

某些对象的值可以更改。价值可以改变的对象被认为是可变的。创建后其值不可更改的对象称为不可变的。

(当更改可变对象的值时,包含对可变对象的引用的不可变容器对象的值可以更改;但是该容器仍然被认为是不可变的,因为它所包含的对象的集合无法更改。因此,不可变性并不是严格意义上的与具有不变的值一样,它更加微妙。)

对象的可变性取决于其类型。例如,数字,字符串和元组是不可变的,而字典和列表则是可变的。


+1请注意,尽管只有某些扩展类型(您可能要检查一下其定义,所有Python的内置类型都在C中实现)是不可变的。其他人(大多数我敢说)是完全可变的。

@delnan您怎么称呼“扩展类型”
eyquem

@eyquem:我在回答中错误地使用了“扩展类型”一词,而delnan指的是它。在他发表评论后,我修改了答案,避免使用这个词。
taleinat 2011年

19

可变对象与不可变对象之间的区别

定义

可变对象:创建后可以更改的对象。
不可变的对象:创建后无法更改的对象。

在python中,它将尝试更改不可变对象的值,它将赋予新对象。

可变对象

这是python中可变类型的列表对象:

  1. list
  2. Dictionary
  3. Set
  4. bytearray
  5. user defined classes

不变的对象

这是python中不可变类型的列表对象:

  1. int
  2. float
  3. decimal
  4. complex
  5. bool
  6. string
  7. tuple
  8. range
  9. frozenset
  10. bytes

一些未回答的问题

问题字符串是不可变类型吗?
的,但是您可以解释一下: 证明1

a = "Hello"
a +=" World"
print a

输出量

"Hello World"

在上面的示例中,一次创建为“ Hello”的字符串最终更改为“ Hello World”。这意味着字符串是可变类型。但是我们不能检查它的身份并检查它是否是可变类型。

a = "Hello"
identity_a = id(a)
a += " World"
new_identity_a = id(a)
if identity_a != new_identity_a:
    print "String is Immutable"

输出量

String is Immutable

证明2

a = "Hello World"
a[0] = "M"

输出量

TypeError 'str' object does not support item assignment

问题元组是不可变的类型吗?
是的, 这是 证明1

tuple_a = (1,)
tuple_a[0] = (2,)
print a

输出量

'tuple' object does not support item assignment

在[46]中:=“ Hello”在[47]中:id(a)Out [47]:140071263880128在[48]中:a = a.replace(“ H”,“ g”)在[49]中:a Out [49]:'gello'In [50]:id(a)Out [50]:140071263881040
Argus Malware

您是否愿意证明我上面给出的示例的项目分配问题
Argus Malware

不变类型中不会出现项目分配问题。在您的情况下,您正在更改字符串a,但在内存中将其分配给新变量。在我的情况下,项目分配不会像在列表或字典的情况下那样更改变量的内存。如果要执行替换操作,则是在创建新变量而不修改现有变量
anand tripathi

在您的情况下,@ ArgusMalware的两个ID相等,因为第一个ID由GC回​​收,因此第二个ID重用了内存。
科隆

11

可变对象必须至少具有一种能够使该对象变异的方法。例如,list对象具有append方法,该方法实际上将使对象变异:

>>> a = [1,2,3]
>>> a.append('hello') # `a` has mutated but is still the same object
>>> a
[1, 2, 3, 'hello']

但是该类float没有方法来更改float对象。你可以做:

>>> b = 5.0 
>>> b = b + 0.1
>>> b
5.1

但是=操作数不是方法。它只是在变量和变量右边之间建立了绑定,没有别的。它永远不会改变或创建对象。从现在开始,它是变量将指向的内容的声明。

当您执行b = b + 0.1=操作数时,将变量绑定到新的float时,将创建,结果为 5 + 0.1

将变量分配给存在的对象(可变对象或不可变对象)时,=操作数会将变量绑定到该对象。再也没有发生

无论哪种情况,=都只需要进行绑定即可。它不会更改或创建对象。

当您这样做时a = 1.0=操作数不是创建浮点数,而是创建1.0行的一部分。实际上,在编写时,1.0这是float(1.0)返回浮点对象的构造函数调用的简写形式。(这就是为什么您键入1.0并按Enter时会在1.0下面显示“ echo”的原因;这是您调用的构造函数的返回值)

现在,如果b是float和分配a = b,这两个变量都指向同一个对象,但实际上变量不能comunicate betweem自己,因为对象是inmutable,如果你这样做b += 1,现在b指向一个新的对象,a是仍指向老人,不知道要b指向什么。

但是,如果c是的话,假设是a list,并且您a = c现在分配,a并且c可以“通信”,因为它list是可变的,如果这样做c.append('msg'),则只需检查a您是否收到消息即可。

(顺便说一句,每个对象都有一个唯一的ID号,可以与之关联id(x)。因此,您可以检查一个对象是否相同或不检查其唯一ID是否已更改。)


6

如果该类的每个对象在实例化时具有固定值而不能随后更改,则该类是不可变的

换句话说,更改该变量的整个值(name)或不理会它。

例:

my_string = "Hello world" 
my_string[0] = "h"
print my_string 

您希望它可以正常工作并打印问候世界,但这将引发以下错误:

Traceback (most recent call last):
File "test.py", line 4, in <module>
my_string[0] = "h"
TypeError: 'str' object does not support item assignment

口译员说:我无法更改此字符串的第一个字符

您必须更改整体string以使其起作用:

my_string = "Hello World" 
my_string = "hello world"
print my_string #hello world

检查此表:

在此处输入图片说明

资源


如何比上面显示的方式更简洁地修改python字符串的组件?
卢克·戴维斯

@LukeDavis你可以的my_string = 'h' + my_string[1:]。这将生成一个名为my_string的新字符串,并且原来的my_string消失了(请打印id(my_string)以查看此内容)。当然,这不是很灵活,对于更一般的情况,您可以转换为列表并返回:l = list(my_string) l[0] = 'h' my_string = ''.join(l)
danio

5

在我看来,您正在与可变/不可变实际上意味着什么的问题作斗争。所以这是一个简单的例子:

首先,我们需要一个基础来进行解释。

因此,请考虑将您编程为虚拟对象的任何事物,以二进制序列形式存储在计算机内存中的事物。(不过,不要试图想像的太难。^^)现在,在大多数计算机语言中,您不会直接使用这些二进制数,而是更多地使用二进制数的解释。

例如,您不考虑数字0x110、0xaf0278297319或类似数字,而是考虑诸如6的数字或诸如“ Hello,world”之类的字符串。这些数字或字符串永远都不能解释计算机内存中的二进制数。对于变量的任何值也是如此。

简而言之:我们使用实际值编程,而是使用实际二进制值的解释。

现在,我们确实有一些为了逻辑和其他“精巧的东西”而不能更改的解释,而有些解释很可能会更改。例如,考虑一个城市的模拟,换句话说就是一个程序,其中有许多虚拟对象,其中一些是房屋。现在可以更改这些虚拟对象(房屋),并且仍然可以将它们视为相同的房屋吗?当然可以。因此它们是可变的:可以对其进行更改而不会成为“完全”不同的对象。

现在考虑整数:它们也是虚拟对象(计算机内存中的二进制数的序列)。因此,如果我们更改其中之一,就像将值六增加一,它仍然是六吗?当然不是。因此,任何整数都是不可变的。

因此:如果虚拟对象中的任何更改意味着它实际上变成了另一个虚拟对象,则称为不可变。

最后说明:

(1)切勿将使用可变语言和不变语言的真实世界经验与某种语言的编程混为一谈:

每种编程语言都有自己的定义,哪些对象可以被静音,哪些对象不能被静音。

因此,尽管您现在可能已经理解了含义上的差异,但是您仍然必须学习每种编程语言的实际实现。...确实,某种语言的目的可能是将6静音而变成7。那么这又将是相当疯狂或有趣的事情,例如并行宇宙的模拟。^^

(2)这种解释当然不是科学的,它是为了帮助您掌握可变和不变之间的区别。


5

这个答案的目标是创建一个地方,以找到所有好的主意,这些主意是关于如何分辨是否正在处理变异/非变异(不可变/可变),以及在可能的情况下该怎么做?有时候,突变是不受欢迎的,而python在这方面的行为可能与其他语言的编码人员的直觉相反。

根据@ mina-gabriel的有用文章:

分析以上内容并结合@arrakëën的帖子:

什么不能意外改变?

  • 标量(存储单个值的变量类型)不会意外更改
    • 数值示例:int(),float(),complex()
  • 有一些“可变序列”:
    • str(),tuple(),frozenset(),bytes()

什么可以?

  • 列出类似对象(列表,字典,集合,bytearray())
  • 此处的帖子还说了类和类实例,但这可能取决于类继承自什么和/或其构建方式。

“意外”是指其他语言的程序员可能不会期望这种行为(Ruby或Ruby,以及其他一些“ Python一样”的语言除外)。

添加到此讨论中:

当它防止您不小心用占用内存的大型数据结构的多个副本填充代码时,此行为是一个优点。但是,当这种情况不受欢迎时,我们如何解决呢?

使用列表,简单的解决方案是像这样构建一个新的列表:

list2 =列表(list1)

与其他结构一起使用...解决方案可能会更加棘手。一种方法是遍历元素并将其添加到新的(相同类型的)空数据结构中。

当您传入可变结构时,函数会改变原始函数。怎么说呢?

  • 在此线程的其他注释上进行了一些测试,但随后有注释指示这些测试不是完全证据
  • object.function()是原始对象的方法,但其中只有一部分会发生变化。如果他们什么都不返回,那么他们可能会这样做。有人希望.append()能够在不测试给定名称的情况下进行更改。.union()返回set1.union(set2)的并集,并且不会突变。不确定时,可以检查该函数的返回值。如果return = None,它不会突变。
  • 在某些情况下,sorted()可能是一种解决方法。由于它返回了原始文档的排序版本,因此它可以让您在开始以其他方式处理原始文档之前存储未变异的副本。但是,此选项假定您不关心原始元素的顺序(如果这样做,则需要寻找其他方法)。相比之下,.sort()会改变原始的(正如人们所期望的那样)。

非标准方法(在有帮助的情况下):在MIT许可下发布的github上找到了此方法:

  • github资料库位于:tobgu,名称:pyrsistent
  • 含义:编写Python持久数据结构代码,以在不希望发生突变时代替核心数据结构使用

对于自定义类,@ semicolon建议检查是否存在__hash__函数,因为可变对象通常不应具有__hash__()函数。

这是我目前在该主题上积累的全部内容。欢迎其他想法,更正等。谢谢。


3

思考差异的一种方法:

可以将对python中不可变对象的分配视为深拷贝,而对可变对象的分配则较浅


1
这是不正确的。Python中的所有分配均通过引用。不涉及复制。
augurar

3

最简单的答案:

可变变量是其值可能会在适当位置更改的变量,而在不可变变量中,值不会在适当的位置更改。修改不可变变量将重建相同的变量。

例:

>>>x = 5

将创建一个x引用的值5

x-> 5

>>>y = x

该语句使y引用x的5

x -------------> 5 <----------- y

>>>x = x + y

由于x是整数(不可变类型),因此已重建。

在该语句中,RHS上的表达式将得出值10,将其分配给LHS(x)时,x将重建为10。所以现在

x ---------> 10

y ---------> 5


-1

我还没有阅读所有答案,但是所选答案并不正确,我认为作者的想法是,能够重新分配变量意味着任何数据类型都是可变的。事实并非如此。可变性与按引用传递而不是按值传递有关。

假设您创建了一个列表

a = [1,2]

如果您要说:

b = a
b[1] = 3

即使您在B上重新分配了值,它也将在a上重新分配了值。这是因为当您分配“ b = a”时。您正在将“引用”传递给对象,而不是值的副本。字符串,浮点数等不是这种情况。这会使列表,字典和类似变量变得可变,但布尔值,浮点数等是不可变的。


-1

例如,对于不可变的对象,分配会创建值的新副本。

x=7
y=x
print(x,y)
x=10 # so for immutable objects this creates a new copy so that it doesnot 
#effect the value of y
print(x,y)

对于可变对象,分配不会创建值的另一个副本。例如,

x=[1,2,3,4]
print(x)
y=x #for immutable objects assignment doesn't create new copy 
x[2]=5
print(x,y) # both x&y holds the same list

1
绝对不正确。作业从不创建副本。请阅读nedbatchelder.com/text/names.html 在第一种情况下,x=10它只是另一个赋值,同时x[2] = 5调用了mutator方法。int对象只是缺少mutator方法,但是python赋值的语义并不取决于类型
juanpa.arrivillaga

-2

在Python中,有一种简单的方法可以知道:

不变的:

    >>> s='asd'
    >>> s is 'asd'
    True
    >>> s=None
    >>> s is None
    True
    >>> s=123
    >>> s is 123
    True

可变的:

>>> s={}
>>> s is {}
False
>>> {} is {}
Flase
>>> s=[1,2]
>>> s is [1,2]
False
>>> s=(1,2)
>>> s is (1,2)
False

和:

>>> s=abs
>>> s is abs
True

因此,我认为内置函数在Python中也是不可变的。

但是我真的不明白float是如何工作的:

>>> s=12.3
>>> s is 12.3
False
>>> 12.3 is 12.3
True
>>> s == 12.3
True
>>> id(12.3)
140241478380112
>>> id(s)
140241478380256
>>> s=12.3
>>> id(s)
140241478380112
>>> id(12.3)
140241478380256
>>> id(12.3)
140241478380256

好奇怪


但这显然是无效的。因为元组是不可变的。输入x = (1, 2),然后尝试进行mutate x,这是不可能的。我发现检查可变性的一种方法是hash,它至少适用于内置对象。hash(1) hash('a') hash((1, 2)) hash(True)所有的工作,hash([]) hash({}) hash({1, 2})都没有用。
分号

@semicolon对于用户定义的类,即使hash()对象定义的__hash__()类通常是可变的,如果对象定义了一个方法,它将起作用。
augurar

1
@augurar我的意思是,但是Python中没有任何东西可以保证任何东西,因为Python没有真正的静态类型或形式保证。但是该hash方法仍然是一种相当不错的方法,因为可变对象通常不应该具有__hash__()方法,因为在字典中将它们作为键只是危险。
分号

1
@augurar和分号(或其他知道的分号):__hash __()解决方案...自定义类的创建者是否必须添加它才能使它存在?如果是这样,那么规则是如果存在,则对象应该是不可变的;如果它不存在,我们就无法确定,因为创建者可能已经离开了。
TMWP
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.