Menggunakan properti () pada metodemetode


174

Saya punya kelas dengan dua metode kelas (menggunakan fungsi classmethod ()) untuk mendapatkan dan mengatur apa yang pada dasarnya adalah variabel statis. Saya mencoba menggunakan fungsi properti () dengan ini, tetapi menghasilkan kesalahan. Saya dapat mereproduksi kesalahan dengan yang berikut dalam juru bahasa:

class Foo(object):
    _var = 5
    @classmethod
    def getvar(cls):
        return cls._var
    @classmethod
    def setvar(cls, value):
        cls._var = value
    var = property(getvar, setvar)

Saya bisa menunjukkan metode kelas, tetapi mereka tidak berfungsi sebagai properti:

>>> f = Foo()
>>> f.getvar()
5
>>> f.setvar(4)
>>> f.getvar()
4
>>> f.var
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: 'classmethod' object is not callable
>>> f.var=5
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: 'classmethod' object is not callable

Apakah mungkin untuk menggunakan fungsi properti () dengan fungsi yang didekorasi dengan metode kelas?

Jawaban:


90

Properti dibuat di kelas tetapi memengaruhi instance. Jadi, jika Anda menginginkan properti classmethod, buat properti di metaclass.

>>> class foo(object):
...     _var = 5
...     class __metaclass__(type):  # Python 2 syntax for metaclasses
...         pass
...     @classmethod
...     def getvar(cls):
...         return cls._var
...     @classmethod
...     def setvar(cls, value):
...         cls._var = value
...     
>>> foo.__metaclass__.var = property(foo.getvar.im_func, foo.setvar.im_func)
>>> foo.var
5
>>> foo.var = 3
>>> foo.var
3

Tetapi karena Anda menggunakan metaclass, itu akan lebih baik jika Anda hanya memindahkan metode metodenya ke sana.

>>> class foo(object):
...     _var = 5
...     class __metaclass__(type):  # Python 2 syntax for metaclasses
...         @property
...         def var(cls):
...             return cls._var
...         @var.setter
...         def var(cls, value):
...             cls._var = value
... 
>>> foo.var
5
>>> foo.var = 3
>>> foo.var
3

atau, menggunakan metaclass=...sintaks Python 3 , dan metaclass yang didefinisikan di luar footubuh kelas, dan metaclass yang bertanggung jawab untuk menetapkan nilai awal dari _var:

>>> class foo_meta(type):
...     def __init__(cls, *args, **kwargs):
...         cls._var = 5
...     @property
...     def var(cls):
...         return cls._var
...     @var.setter
...     def var(cls, value):
...         cls._var = value
...
>>> class foo(metaclass=foo_meta):
...     pass
...
>>> foo.var
5
>>> foo.var = 3
>>> foo.var
3

1
Ini sepertinya tidak berfungsi untuk saya dalam Python 3.2. Jika saya mengubah foo .__ metaclass __. Var = properti (foo.getvar.im_func, foo.setvar.im_func) menjadi foo .__ metaclass __. Var = properti (foo.getvar .__ func__, foo.setvar .__ func__) Saya mendapatkan "AttributeError: type objek 'foo' tidak memiliki atribut 'var' "saat menjalankan" foo.var ".
Michael Kelley

SIGH koreksi ganda: ini bekerja di Python 2.7, tetapi tidak Python 3.2.
Michael Kelley

@MichaelKelley - Itu karena sintaks untuk metaclasses telah berubah dalam Python 3.x
mac

1
Saya tidak yakin untuk mengerti, apa yang akan menjadi cara Python 3.x untuk menulis ini?
SylvainD

8
@Josay: Anda harus mendefinisikan metaclass terlebih dahulu, lalu mendefinisikan kelas menggunakan class Foo(metaclass=...)sintaks baru .
Kevin

69

Membaca catatan rilis Python 2.2 , saya menemukan yang berikut ini.

Metode get [properti] tidak akan dipanggil ketika properti diakses sebagai atribut kelas (Cx) alih-alih sebagai atribut instance (C (). X). Jika Anda ingin mengesampingkan operasi __get__ untuk properti ketika digunakan sebagai atribut kelas, Anda dapat mensubklasifikasikan properti - ini adalah tipe gaya baru itu sendiri - untuk memperluas metode __get__-nya, atau Anda dapat menentukan tipe deskriptor dari awal dengan membuat yang baru kelas-gaya yang mendefinisikan metode __get__, __set__ dan __delete__.

CATATAN: Metode di bawah ini sebenarnya tidak berfungsi untuk setter, hanya getter.

Oleh karena itu, saya percaya solusi yang ditentukan adalah membuat ClassProperty sebagai subkelas properti.

class ClassProperty(property):
    def __get__(self, cls, owner):
        return self.fget.__get__(None, owner)()

class foo(object):
    _var=5
    def getvar(cls):
        return cls._var
    getvar=classmethod(getvar)
    def setvar(cls,value):
        cls._var=value
    setvar=classmethod(setvar)
    var=ClassProperty(getvar,setvar)

assert foo.getvar() == 5
foo.setvar(4)
assert foo.getvar() == 4
assert foo.var == 4
foo.var = 3
assert foo.var == 3

Namun, setter tidak benar-benar berfungsi:

foo.var = 4
assert foo.var == foo._var # raises AssertionError

foo._var tidak berubah, Anda cukup menimpa properti dengan nilai baru.

Anda juga dapat menggunakan ClassPropertysebagai dekorator:

class foo(object):
    _var = 5

    @ClassProperty
    @classmethod
    def var(cls):
        return cls._var

    @var.setter
    @classmethod
    def var(cls, value):
        cls._var = value

assert foo.var == 5

20
Saya tidak berpikir bagian setter dari ClassProperty benar-benar berfungsi seperti yang dijelaskan: sementara pernyataan contoh semuanya lulus, pada akhirnya foo._var == 4 (bukan 3, seperti tersirat). Pengaturan properti akan membuat properti itu sendiri. Ketika properti-kelas dibahas pada python-dev itu menunjukkan bahwa, sementara getter sepele, setter sulit (tidak mungkin?) Tanpa metaclass
Gabriel Grant

4
@ Gabriel Benar sekali. Saya tidak percaya tidak ada yang menunjukkan hal itu selama dua tahun.
agf

Saya juga tidak yakin mengapa Anda tidak hanya menggunakan self.fget(owner)dan menghapus keharusan harus menggunakan @classmethodsama sekali di sini? (itulah yang classmethod dilakukan , terjemahkan .__get__(instance, owner)(*args, **kwargs)ke function(owner, *args, **kwargs)panggilan, melalui perantara; properti tidak membutuhkan perantara).
Martijn Pieters

Demonstrasi Anda kurang memiliki transformasi aktual baik dalam pengambil atau penyetel yang akan menunjukkan dengan rapi bahwa foo.var = 3tugas Anda tidak benar-benar melewati properti , dan sebagai gantinya hanya mengganti objek properti pada foodengan integer. Jika Anda menambahkan assert isinstance(foo.__dict__['var'], ClassProperty)panggilan di antara pernyataan Anda, Anda akan melihat bahwa kegagalan setelah foo.var = 3dieksekusi.
Martijn Pieters

1
Kelas Python tidak descriptor dukungan mengikat pengaturan pada kelas itu sendiri , hanya untuk mendapatkan (jadi instance.attr, instance.attr = valuedan del instance.attrsemua mengikat descriptor akan ditemukan pada type(instance), tapi sementara classobj.attrmengikat, classobj.attr = valuedan del classobj.attrjangan tidak dan bukannya mengganti atau menghapus objek deskriptor itu sendiri). Anda memerlukan metaclass untuk mendukung pengaturan dan menghapus (membuat objek kelas sebagai instance, dan metaclass tipe).
Martijn Pieters

56

Saya berharap @classpropertydekorator read-only mati-sederhana ini akan membantu seseorang mencari properti class.

class classproperty(object):

    def __init__(self, fget):
        self.fget = fget

    def __get__(self, owner_self, owner_cls):
        return self.fget(owner_cls)

class C(object):

    @classproperty
    def x(cls):
        return 1

assert C.x == 1
assert C().x == 1

2
Apakah ini berfungsi dengan subclass? (dapatkah subclass menimpa classproperties?)
zakdances

1
Umm iya? class D(C): x = 2; assert D.x == 2
dtheodor

Saya berharap ini berhasil ketika saya menggunakannya .formatseperti "{x}".format(**dict(self.__class__.__dict__, **self.__dict__)):(
2rs2ts

@Nathan Tidak hanya ... ketika Anda mengaturnya, Anda mengabaikan semua xakses oleh 10. Saya suka pendekatan ini karena rapi dan sederhana tetapi kedengarannya seperti antipattern
Michele d'Amico

Mudah diperbaiki: tambahkan tanda __set__yang menaikkan ValueErroruntuk mencegah penggantian.
Kiran Jonnalagadda

30

Apakah mungkin untuk menggunakan fungsi properti () dengan fungsi yang didekorasi dengan metode kelas?

Tidak.

Namun, metode kelas hanyalah metode terikat (fungsi parsial) pada kelas yang dapat diakses dari instance kelas itu.

Karena instance adalah fungsi dari kelas dan Anda bisa mendapatkan kelas dari instance, Anda bisa mendapatkan perilaku apa pun yang diinginkan yang mungkin Anda inginkan dari properti kelas dengan property:

class Example(object):
    _class_property = None
    @property
    def class_property(self):
        return self._class_property
    @class_property.setter
    def class_property(self, value):
        type(self)._class_property = value
    @class_property.deleter
    def class_property(self):
        del type(self)._class_property

Kode ini dapat digunakan untuk menguji - kode harus lulus tanpa menimbulkan kesalahan:

ex1 = Example()
ex2 = Example()
ex1.class_property = None
ex2.class_property = 'Example'
assert ex1.class_property is ex2.class_property
del ex2.class_property
assert not hasattr(ex1, 'class_property')

Dan perhatikan bahwa kita sama sekali tidak membutuhkan metaclasses - dan Anda tidak secara langsung mengakses metaclass melalui instance kelasnya.

menulis @classpropertydekorator

Anda sebenarnya dapat membuat classpropertydekorator hanya dalam beberapa baris kode dengan mensubclassing property(ini diterapkan dalam C, tetapi Anda dapat melihat Python yang setara di sini ):

class classproperty(property):
    def __get__(self, obj, objtype=None):
        return super(classproperty, self).__get__(objtype)
    def __set__(self, obj, value):
        super(classproperty, self).__set__(type(obj), value)
    def __delete__(self, obj):
        super(classproperty, self).__delete__(type(obj))

Kemudian perlakukan dekorator seolah-olah itu adalah metode kelas yang dikombinasikan dengan properti:

class Foo(object):
    _bar = 5
    @classproperty
    def bar(cls):
        """this is the bar attribute - each subclass of Foo gets its own.
        Lookups should follow the method resolution order.
        """
        return cls._bar
    @bar.setter
    def bar(cls, value):
        cls._bar = value
    @bar.deleter
    def bar(cls):
        del cls._bar

Dan kode ini seharusnya bekerja tanpa kesalahan:

def main():
    f = Foo()
    print(f.bar)
    f.bar = 4
    print(f.bar)
    del f.bar
    try:
        f.bar
    except AttributeError:
        pass
    else:
        raise RuntimeError('f.bar must have worked - inconceivable!')
    help(f)  # includes the Foo.bar help.
    f.bar = 5

    class Bar(Foo):
        "a subclass of Foo, nothing more"
    help(Bar) # includes the Foo.bar help!
    b = Bar()
    b.bar = 'baz'
    print(b.bar) # prints baz
    del b.bar
    print(b.bar) # prints 5 - looked up from Foo!

    
if __name__ == '__main__':
    main()

Tapi saya tidak yakin seberapa baik ini disarankan. Artikel milis lama menyarankan itu tidak akan berfungsi.

Membuat properti bekerja di kelas:

Kelemahan dari di atas adalah bahwa "properti kelas" tidak dapat diakses dari kelas, karena itu hanya akan menimpa deskriptor data dari kelas __dict__.

Namun, kita bisa menimpanya dengan properti yang didefinisikan dalam metaclass __dict__. Sebagai contoh:

class MetaWithFooClassProperty(type):
    @property
    def foo(cls):
        """The foo property is a function of the class -
        in this case, the trivial case of the identity function.
        """
        return cls

Dan kemudian instance kelas dari metaclass dapat memiliki properti yang mengakses properti kelas menggunakan prinsip yang sudah ditunjukkan di bagian sebelumnya:

class FooClassProperty(metaclass=MetaWithFooClassProperty):
    @property
    def foo(self):
        """access the class's property"""
        return type(self).foo

Dan sekarang kita melihat keduanya

>>> FooClassProperty().foo
<class '__main__.FooClassProperty'>

dan kelas

>>> FooClassProperty.foo
<class '__main__.FooClassProperty'>

memiliki akses ke properti kelas.


3
Jelas, ringkas, teliti: ini harus menjadi jawaban yang diterima.
pfabri

25

Python 3!

Pertanyaan lama, banyak pandangan, sangat membutuhkan cara Python 3 yang benar.

Untungnya, mudah dengan metaclasskwarg:

class FooProperties(type):

    @property
    def var(cls):
        return cls._var

class Foo(object, metaclass=FooProperties):
    _var = 'FOO!'

Kemudian, >>> Foo.var

'FOO!'


1
artinya tidak ada jalan keluar yang sederhana
mehmet

@mehmet Apakah ini tidak sederhana? Fooadalah instance dari metaclass-nya, dan @propertydapat digunakan untuk metode-metodenya seperti halnya untuk contoh-contohnya Foo.
OJFord

2
Anda harus mendefinisikan kelas lain untuk suatu kelas, yaitu dua kali lipat kompleksitas dengan asumsi metaclass tidak dapat digunakan kembali.
mehmet

Metode class bekerja dari class dan instance. Properti ini hanya berfungsi dari kelas. Saya tidak berpikir inilah yang diminta.
Aaron Hall

1
@ AaronHall Jika itu penting, mudah ditambahkan Foo.__new__. Meskipun pada titik itu mungkin layak menggunakan getattribute sebagai gantinya, atau mempertanyakan apakah berpura-pura memiliki fitur bahasa benar-benar pendekatan yang ingin Anda ambil sama sekali.
OJFord

16

Tidak ada cara yang masuk akal untuk membuat sistem "properti kelas" ini bekerja dengan Python.

Ini adalah salah satu cara yang tidak masuk akal untuk membuatnya bekerja. Anda tentu bisa membuatnya lebih mulus dengan meningkatnya jumlah metaclass magic.

class ClassProperty(object):
    def __init__(self, getter, setter):
        self.getter = getter
        self.setter = setter
    def __get__(self, cls, owner):
        return getattr(cls, self.getter)()
    def __set__(self, cls, value):
        getattr(cls, self.setter)(value)

class MetaFoo(type):
    var = ClassProperty('getvar', 'setvar')

class Foo(object):
    __metaclass__ = MetaFoo
    _var = 5
    @classmethod
    def getvar(cls):
        print "Getting var =", cls._var
        return cls._var
    @classmethod
    def setvar(cls, value):
        print "Setting var =", value
        cls._var = value

x = Foo.var
print "Foo.var = ", x
Foo.var = 42
x = Foo.var
print "Foo.var = ", x

Simpul dari masalah ini adalah bahwa properti adalah apa yang Python sebut "deskriptor". Tidak ada cara yang singkat dan mudah untuk menjelaskan cara kerja pemrograman seperti ini, jadi saya harus mengarahkan Anda ke deskriptor howto .

Anda hanya perlu memahami hal-hal semacam ini jika Anda menerapkan kerangka kerja yang cukup maju. Seperti ketekunan objek transparan atau sistem RPC, atau sejenis bahasa khusus domain.

Namun, dalam komentar untuk jawaban sebelumnya, Anda mengatakan itu

perlu memodifikasi atribut yang sedemikian rupa sehingga dapat dilihat oleh semua instance kelas, dan dalam ruang lingkup dari mana metode kelas ini dipanggil tidak memiliki referensi ke semua instance kelas.

Menurut saya, yang Anda inginkan adalah pola desain Observer .


Saya suka ide contoh kode, tetapi sepertinya akan sedikit kikuk dalam praktik.
Mark Roddy

Apa yang saya coba selesaikan adalah pengaturan langsung dan mendapatkan satu atribut yang digunakan sebagai bendera untuk memodifikasi perilaku semua contoh jadi saya pikir Observer akan terlalu berlebihan untuk apa yang saya coba lakukan. Jika ada beberapa atribut yang dipertanyakan maka saya akan lebih condong.
Mark Roddy

Tampaknya hanya membuat fungsi publik dan memanggil mereka secara langsung adalah solusi sederhana. Saya ingin tahu apakah saya melakukan sesuatu yang salah atau jika saya berusaha melakukan itu tidak mungkin. Omong-omong tentang beberapa komentar. Batas 300 karakter menyebalkan.
Mark Roddy

Hal bagus tentang contoh kode adalah bahwa Anda dapat mengimplementasikan semua bit kikuk sekali dan kemudian mewarisinya. Cukup pindahkan _var ke kelas turunan. kelas D1 (Foo): _var = 21 kelas D2 (Foo) _var = "Halo" D1.var 21 D2.var Halo
Thomas L Holaday

6

Mengaturnya hanya pada kelas meta tidak membantu jika Anda ingin mengakses properti kelas melalui objek instantiated, dalam hal ini Anda perlu menginstal properti normal pada objek juga (yang dikirim ke properti kelas). Saya pikir yang berikut ini sedikit lebih jelas:

#!/usr/bin/python

class classproperty(property):
    def __get__(self, obj, type_):
        return self.fget.__get__(None, type_)()

    def __set__(self, obj, value):
        cls = type(obj)
        return self.fset.__get__(None, cls)(value)

class A (object):

    _foo = 1

    @classproperty
    @classmethod
    def foo(cls):
        return cls._foo

    @foo.setter
    @classmethod
    def foo(cls, value):
        cls.foo = value

a = A()

print a.foo

b = A()

print b.foo

b.foo = 5

print a.foo

A.foo = 10

print b.foo

print A.foo

3

Setengah solusi, __set__ di kelas tidak bekerja, masih. Solusinya adalah kelas properti kustom yang mengimplementasikan properti dan metode statis

class ClassProperty(object):
    def __init__(self, fget, fset):
        self.fget = fget
        self.fset = fset

    def __get__(self, instance, owner):
        return self.fget()

    def __set__(self, instance, value):
        self.fset(value)

class Foo(object):
    _bar = 1
    def get_bar():
        print 'getting'
        return Foo._bar

    def set_bar(value):
        print 'setting'
        Foo._bar = value

    bar = ClassProperty(get_bar, set_bar)

f = Foo()
#__get__ works
f.bar
Foo.bar

f.bar = 2
Foo.bar = 3 #__set__ does not

3

Karena saya perlu memodifikasi atribut yang sedemikian rupa sehingga dapat dilihat oleh semua instance kelas, dan dalam ruang lingkup dari mana metode kelas ini dipanggil tidak memiliki referensi ke semua instance kelas.

Apakah Anda memiliki akses ke setidaknya satu instance kelas? Saya bisa memikirkan cara untuk melakukannya:

class MyClass (object):
    __var = None

    def _set_var (self, value):
        type (self).__var = value

    def _get_var (self):
        return self.__var

    var = property (_get_var, _set_var)

a = MyClass ()
b = MyClass ()
a.var = "foo"
print b.var

2

Cobalah ini, ini menyelesaikan pekerjaan tanpa harus mengubah / menambahkan banyak kode yang ada.

>>> class foo(object):
...     _var = 5
...     def getvar(cls):
...         return cls._var
...     getvar = classmethod(getvar)
...     def setvar(cls, value):
...         cls._var = value
...     setvar = classmethod(setvar)
...     var = property(lambda self: self.getvar(), lambda self, val: self.setvar(val))
...
>>> f = foo()
>>> f.var
5
>>> f.var = 3
>>> f.var
3

The propertyFungsi membutuhkan dua callableargumen. memberi mereka pembungkus lambda (yang melewati contoh sebagai argumen pertama) dan semuanya baik-baik saja.


Seperti yang ditunjukkan Florian Bösch, sintaks yang diperlukan (oleh perpustakaan pihak ketiga atau kode lawas) adalah foo.var.
Thomas L Holaday

2

Inilah solusi yang harus bekerja untuk akses baik melalui kelas dan akses melalui contoh yang menggunakan metaclass.

In [1]: class ClassPropertyMeta(type):
   ...:     @property
   ...:     def prop(cls):
   ...:         return cls._prop
   ...:     def __new__(cls, name, parents, dct):
   ...:         # This makes overriding __getattr__ and __setattr__ in the class impossible, but should be fixable
   ...:         dct['__getattr__'] = classmethod(lambda cls, attr: getattr(cls, attr))
   ...:         dct['__setattr__'] = classmethod(lambda cls, attr, val: setattr(cls, attr, val))
   ...:         return super(ClassPropertyMeta, cls).__new__(cls, name, parents, dct)
   ...:

In [2]: class ClassProperty(object):
   ...:     __metaclass__ = ClassPropertyMeta
   ...:     _prop = 42
   ...:     def __getattr__(self, attr):
   ...:         raise Exception('Never gets called')
   ...:

In [3]: ClassProperty.prop
Out[3]: 42

In [4]: ClassProperty.prop = 1
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-4-e2e8b423818a> in <module>()
----> 1 ClassProperty.prop = 1

AttributeError: can't set attribute

In [5]: cp = ClassProperty()

In [6]: cp.prop
Out[6]: 42

In [7]: cp.prop = 1
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-7-e8284a3ee950> in <module>()
----> 1 cp.prop = 1

<ipython-input-1-16b7c320d521> in <lambda>(cls, attr, val)
      6         # This makes overriding __getattr__ and __setattr__ in the class impossible, but should be fixable
      7         dct['__getattr__'] = classmethod(lambda cls, attr: getattr(cls, attr))
----> 8         dct['__setattr__'] = classmethod(lambda cls, attr, val: setattr(cls, attr, val))
      9         return super(ClassPropertyMeta, cls).__new__(cls, name, parents, dct)

AttributeError: can't set attribute

Ini juga bekerja dengan setter yang didefinisikan dalam metaclass.


1

Setelah mencari tempat yang berbeda, saya menemukan metode untuk mendefinisikan classproperty yang valid dengan Python 2 dan 3.

from future.utils import with_metaclass

class BuilderMetaClass(type):
    @property
    def load_namespaces(self):
        return (self.__sourcepath__)

class BuilderMixin(with_metaclass(BuilderMetaClass, object)):
    __sourcepath__ = 'sp'        

print(BuilderMixin.load_namespaces)

Semoga ini bisa membantu seseorang :)


1
Jika metode ini adalah sesuatu yang Anda temukan di suatu tempat, akan lebih baik untuk memberikan tautan (lihat Cara referensi materi yang ditulis oleh orang lain )
Andrew Myers

-27

Ini saran saya. Jangan gunakan metode kelas.

Serius.

Apa alasan menggunakan metode kelas dalam kasus ini? Mengapa tidak memiliki objek biasa dari kelas biasa?


Jika Anda hanya ingin mengubah nilainya, properti tidak terlalu membantu bukan? Cukup atur nilai atribut dan lakukan dengan itu.

Properti hanya boleh digunakan jika ada sesuatu yang disembunyikan - sesuatu yang mungkin berubah dalam implementasi di masa depan.

Mungkin contoh Anda dilucuti, dan ada beberapa perhitungan jahat yang Anda tinggalkan. Tapi itu tidak terlihat seperti properti menambah nilai signifikan.

Teknik "privasi" yang dipengaruhi Java (dengan Python, nama atribut yang dimulai dengan _) tidak terlalu membantu. Pribadi dari siapa? Titik pribadi agak samar ketika Anda memiliki sumber (seperti yang Anda lakukan dengan Python.)

Getter dan setter gaya EJB yang dipengaruhi Java (sering kali dilakukan sebagai properti dengan Python) ada untuk memfasilitasi introspeksi primitif Java serta lulus dengan kompiler bahasa statis. Semua getter dan setter tidak membantu dalam Python.


14
Karena saya perlu memodifikasi atribut yang sedemikian rupa sehingga dapat dilihat oleh semua instance kelas, dan dalam ruang lingkup dari mana metode kelas ini dipanggil tidak memiliki referensi ke semua instance kelas.
Mark Roddy
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.