Misalnya saya memiliki kelas dasar sebagai berikut:
class BaseClass(object):
def __init__(self, classtype):
self._type = classtype
Dari kelas ini saya mendapatkan beberapa kelas lain, misalnya
class TestClass(BaseClass):
def __init__(self):
super(TestClass, self).__init__('Test')
class SpecialClass(BaseClass):
def __init__(self):
super(TestClass, self).__init__('Special')
Apakah ada cara yang bagus dan pythonic untuk membuat kelas-kelas itu secara dinamis dengan panggilan fungsi yang menempatkan kelas baru ke dalam ruang lingkup saya saat ini, seperti:
foo(BaseClass, "My")
a = MyClass()
...
Karena akan ada komentar dan pertanyaan mengapa saya membutuhkan ini: Semua kelas turunan memiliki struktur internal yang sama persis dengan perbedaannya, bahwa konstruktor mengambil sejumlah argumen yang sebelumnya tidak ditentukan. Jadi, misalnya, MyClass
mengambil kata kunci a
sementara konstruktor kelas TestClass
mengambil b
dan c
.
inst1 = MyClass(a=4)
inst2 = MyClass(a=5)
inst3 = TestClass(b=False, c = "test")
Tetapi mereka TIDAK PERNAH menggunakan tipe kelas sebagai argumen input seperti
inst1 = BaseClass(classtype = "My", a=4)
Saya mendapatkan ini untuk bekerja tetapi lebih suka cara lain, yaitu objek kelas yang dibuat secara dinamis.
sumber
a
itu akan selaluMyClass
danTestClass
tidak akan pernah mengambila
? Mengapa tidak mendeklarasikan semua 3 argumenBaseClass.__init__()
tetapi default semuanyaNone
?def __init__(self, a=None, b=None, C=None)
?Jawaban:
Sedikit kode ini memungkinkan Anda membuat kelas baru dengan nama dinamis dan nama parameter. Verifikasi parameter
__init__
tidak mengizinkan parameter yang tidak diketahui, jika Anda memerlukan verifikasi lain, seperti tipe, atau wajib, cukup tambahkan logika di sana:class BaseClass(object): def __init__(self, classtype): self._type = classtype def ClassFactory(name, argnames, BaseClass=BaseClass): def __init__(self, **kwargs): for key, value in kwargs.items(): # here, the argnames variable is the one passed to the # ClassFactory call if key not in argnames: raise TypeError("Argument %s not valid for %s" % (key, self.__class__.__name__)) setattr(self, key, value) BaseClass.__init__(self, name[:-len("Class")]) newclass = type(name, (BaseClass,),{"__init__": __init__}) return newclass
Dan ini berfungsi seperti ini, misalnya:
>>> SpecialClass = ClassFactory("SpecialClass", "a b c".split()) >>> s = SpecialClass(a=2) >>> s.a 2 >>> s2 = SpecialClass(d=3) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 8, in __init__ TypeError: Argument d not valid for SpecialClass
Saya melihat Anda meminta untuk memasukkan nama dinamis dalam lingkup penamaan - sekarang, itu tidak dianggap sebagai praktik yang baik dengan Python - Anda memiliki nama variabel, yang dikenal pada waktu pengkodean, atau data - dan nama yang dipelajari dalam runtime lebih banyak " data "daripada" variabel "-
Jadi, Anda bisa menambahkan kelas Anda ke kamus dan menggunakannya dari sana:
name = "SpecialClass" classes = {} classes[name] = ClassFactory(name, params) instance = classes[name](...)
Dan jika desain Anda benar-benar membutuhkan nama yang akan dimasukkan dalam cakupan, lakukan hal yang sama, tetapi gunakan kamus yang ditampilkan oleh
globals()
panggilan alih-alih kamus acak:name = "SpecialClass" globals()[name] = ClassFactory(name, params) instance = SpecialClass(...)
(Memang mungkin bagi fungsi pabrik kelas untuk memasukkan nama secara dinamis pada cakupan global pemanggil - tetapi itu praktik yang lebih buruk lagi, dan tidak kompatibel di seluruh implementasi Python. Cara melakukannya adalah dengan mendapatkan pemanggil bingkai eksekusi, melalui sys._getframe (1) dan menetapkan nama kelas dalam kamus global bingkai dalam
f_globals
atributnya).update, tl; dr: Jawaban ini telah menjadi populer, masih sangat spesifik untuk badan pertanyaan. Jawaban umum tentang bagaimana "secara dinamis membuat kelas turunan dari kelas dasar" dengan Python adalah panggilan sederhana untuk
type
meneruskan nama kelas baru, tupel dengan baseclass (es) dan__dict__
tubuh untuk kelas baru -seperti ini:>>> new_class = type("NewClassName", (BaseClass,), {"new_method": lambda self: ...})
memperbarui
Siapa pun yang membutuhkan ini juga harus memeriksa proyek dill - ia mengklaim dapat membuat kelas acar dan membongkar seperti yang dilakukan acar pada objek biasa, dan telah hidup untuk itu dalam beberapa pengujian saya.
sumber
BaseClass.__init__()
akan lebih baik sebagai yang lebih umumsuper(self.__class__).__init__()
, yang bermain lebih baik ketika kelas-kelas baru disubkelas. (Referensi: rhettinger.wordpress.com/2011/05/26/super-considered-super )super
atas dan buat subclass dari kelas yang dibuat secara dinamis untuk memahaminya; Dan, di sisi lain, dalam hal ini Anda dapat memiliki baseclass sebagai objek umum yang akan dipanggil__init__
.__init__
dariBaseClass
dipanggil dengan satu argumen, tetapi pada kenyataannyaBaseClass.__init__
selalu mengambil daftar argumen kata kunci yang berubah-ubah. Kedua, solusi di atas menetapkan semua nama parameter yang diizinkan sebagai atribut, yang bukan yang saya inginkan. Setiap argumen HARUS pergi keBaseClass
, tapi yang mana yang saya tahu saat membuat kelas turunan. Saya mungkin akan memperbarui pertanyaan atau mengajukan pertanyaan yang lebih tepat untuk membuatnya lebih jelas.super()
saya sebutkan memberiTypeError: must be type, not SubSubClass
. Jika saya mengerti benar, ini berasal dari argumen pertamaself
dari__init__()
, yang merupakanSubSubClass
di manatype
objek diharapkan: ini tampaknya berkaitan dengan faktasuper(self.__class__)
adalah objek yang super terikat. Apa__init__()
metodenya? Saya tidak yakin metode mana yang membutuhkan tipe argumen pertamatype
. Bisakah Anda menjelaskan? (Catatan tambahan:super()
pendekatan saya memang tidak masuk akal, di sini, karena__init__()
memiliki tanda tangan variabel.)type()
adalah fungsi yang membuat kelas (dan di sub-kelas tertentu):def set_x(self, value): self.x = value SubClass = type('SubClass', (BaseClass,), {'set_x': set_x}) # (More methods can be put in SubClass, including __init__().) obj = SubClass() obj.set_x(42) print obj.x # Prints 42 print isinstance(obj, BaseClass) # True
sumber
TypeError
yang mengatakan__init__() takes exactly 2 arguments (1 given)
. Saya menemukan bahwa menambahkan sesuatu (apa saja?) Untuk mengisi kekosongan sudah cukup. Misalnya,obj = SubClass('foo')
berjalan tanpa kesalahan.SubClass
merupakan sub-kelas dariBaseClass
dalam pertanyaan danBaseClass
mengambil parameter (classtype
, yang ada'foo'
dalam contoh Anda).Untuk membuat kelas dengan nilai atribut dinamis, periksa kode di bawah ini. NB. Ini adalah potongan kode dalam bahasa pemrograman python
def create_class(attribute_data, **more_data): # define a function with required attributes class ClassCreated(optional extensions): # define class with optional inheritance attribute1 = adattribute_data # set class attributes with function parameter attribute2 = more_data.get("attribute2") return ClassCreated # return the created class # use class myclass1 = create_class("hello") # *generates a class*
sumber