কিভাবে একটি ক্লাস সাজাইয়া?


129

পাইথন 2.5 তে, কোনও শ্রেণীর সাজসজ্জা করে এমন কোনও শোভাকর তৈরির উপায় আছে কি? বিশেষত, আমি কোনও শ্রেণীর সদস্যকে যুক্ত করতে এবং সেই সদস্যের জন্য একটি মান নিতে কনস্ট্রাক্টর পরিবর্তন করতে একটি ডেকরেটর ব্যবহার করতে চাই।

নিম্নলিখিত শ্রেণীর মতো কিছু খুঁজছেন (যাতে 'ক্লাস ফু:' তে একটি সিনট্যাক্স ত্রুটি রয়েছে:

def getId(self): return self.__id

class addID(original_class):
    def __init__(self, id, *args, **kws):
        self.__id = id
        self.getId = getId
        original_class.__init__(self, *args, **kws)

@addID
class Foo:
    def __init__(self, value1):
        self.value1 = value1

if __name__ == '__main__':
    foo1 = Foo(5,1)
    print foo1.value1, foo1.getId()
    foo2 = Foo(15,2)
    print foo2.value1, foo2.getId()

আমি অনুমান করি যে আমি আসলে যা করছি তা হল পাইথনের সি # ইন্টারফেসের মতো কিছু করার একটি উপায়। আমি মনে করি আমার দৃষ্টান্তটি পরিবর্তন করতে হবে।

উত্তর:


80

আমি আপনি যে রূপরেখাটি বর্ণনা করেছেন তার পরিবর্তে সাবক্লাসটি বিবেচনা করতে চাইতে পারেন এই ধারণাটি আমি দ্বিতীয় করব। তবে, আপনার নির্দিষ্ট দৃশ্যটি না জেনে, ওয়াইএমএমভি :-)

আপনি যা ভাবছেন তা একটি মেটাক্লাস। __new__একটি ক্লাসের অধীনে একটি ক্লাস ফাংশন বর্গ, যার ফলে এটি তখন সামনে বর্গ তৈরি করা হয় পুনর্লিখন করতে পূর্ণ প্রস্তাবিত সংজ্ঞা পাস করা হয়েছে। আপনি, সেই সময়ে, নতুনটির জন্য নির্মাতাকে সাব-আউট করতে পারেন।

উদাহরণ:

def substitute_init(self, id, *args, **kwargs):
    pass

class FooMeta(type):

    def __new__(cls, name, bases, attrs):
        attrs['__init__'] = substitute_init
        return super(FooMeta, cls).__new__(cls, name, bases, attrs)

class Foo(object):

    __metaclass__ = FooMeta

    def __init__(self, value1):
        pass

কনস্ট্রাক্টরকে প্রতিস্থাপন করা সম্ভবত কিছুটা নাটকীয়, তবে ভাষা এই ধরণের গভীর অন্তঃকরণ এবং গতিশীল পরিবর্তনের জন্য সমর্থন সরবরাহ করে।


আপনাকে ধন্যবাদ, আমি এটি খুঁজছি এমন একটি শ্রেণি যা অন্যান্য যে কোনও শ্রেণীর সংশোধন করতে পারে যেমন তাদের সকলেরই একটি নির্দিষ্ট সদস্য রয়েছে। ক্লাসগুলি একটি সাধারণ আইডি ক্লাস থেকে উত্তরাধিকারী না হওয়ার জন্য আমার কারণগুলি হ'ল আমি ক্লাসগুলির নন-আইডি সংস্করণ এবং আইডি সংস্করণগুলি রাখতে চাই।
রবার্ট গোল্যান্ড

পাইথন ২.৫ বা তার চেয়ে বেশি বয়স্কগুলিতে এই জাতীয় জিনিসগুলি করার জন্য মেটাক্লাসগুলি যেতে যেতে যেতে ব্যবহৃত হত, তবে আজকাল আপনি খুব প্রায়ই ক্লাস সজ্জা ব্যবহার করতে পারেন (স্টিভেনের উত্তর দেখুন) যা অনেক সহজ।
জনাথন হার্টলি

203

শ্রেণি অলঙ্করণকারী আপনার সমস্যার সঠিক সমাধান কিনা তা ছাড়াও:

পাইথন ২.6 এবং উচ্চতর ক্ষেত্রে @ -syntax সহ শ্রেণি সজ্জা রয়েছে, সুতরাং আপনি লিখতে পারেন:

@addID
class Foo:
    pass

পুরানো সংস্করণগুলিতে, আপনি এটি অন্য উপায়ে করতে পারেন:

class Foo:
    pass

Foo = addID(Foo)

তবে খেয়াল করুন যে এটি ফাংশন সজ্জকারদের জন্য একই কাজ করে এবং ডেকোররেটরের নতুন (বা সংশোধিত মূল) শ্রেণিটি ফিরিয়ে দেওয়া উচিত, যা আপনি উদাহরণে করছেন না। অ্যাডআইডি সাজসজ্জারটি দেখতে এটির মতো হবে:

def addID(original_class):
    orig_init = original_class.__init__
    # Make copy of original __init__, so we can call it without recursion

    def __init__(self, id, *args, **kws):
        self.__id = id
        self.getId = getId
        orig_init(self, *args, **kws) # Call the original __init__

    original_class.__init__ = __init__ # Set the class' __init__ to the new one
    return original_class

তারপরে আপনি উপরে বর্ণিত হিসাবে পাইথন সংস্করণে উপযুক্ত সিনট্যাক্স ব্যবহার করতে পারেন।

তবে আমি অন্যদের সাথে একমত যে আপনি যদি ওভাররাইড করতে চান তবে উত্তরাধিকার আরও উপযুক্ত __init__


2
... যদিও প্রশ্নটিতে বিশেষত পাইথন 2.5 :-) উল্লেখ রয়েছে
জারেট হার্ডি

5
দুঃখিত linesep বিশৃঙ্খলার জন্য, কিন্তু কোড নমুনার না মন্তব্য কঠোরভাবে মহান ...: Def addID (original_class): original_class .__ orig__init__ = original_class .__ init__ Def Init __ (স্ব, * args, ** kws): মুদ্রণ "প্রসাধক" self.id = 9 original_class .__ orig__init __ (স্ব, * args, ** kws) original_class .__ Init = Init আগমন original_class @addID বর্গ foo: Def __init __ (স্ব): মুদ্রণ "foo" একটি = foo () মুদ্রণ a.id
জেরাল্ড সেনারাক্লেনস ডি গ্রানসি

2
@ স্টিভেন, @ জেরাল্ড ঠিক আছে, আপনি যদি নিজের উত্তরটি আপডেট করতে পারতেন তবে তার কোডটি পড়া খুব সহজ হবে;)
দিন

3
@ ডে: অনুস্মারকটির জন্য ধন্যবাদ, আমি এর আগে মন্তব্যগুলি লক্ষ্য করিনি। তবে: আমি জেরাল্ডস কোডের সাথেও সর্বাধিক পুনরাবৃত্তির গভীরতা পেয়েছি। আমি চেষ্টা করব এবং একটি কার্যক্ষম সংস্করণ তৈরি করব ;-)
স্টিভেন

1
@ ডে এবং @ জেরাল্ড: দুঃখিত, জেরাল্ডের কোডটি ইস্যুটি ছিল না, মন্তব্যে ম্যাঙ্গেল কোডের কারণে আমি গণ্ডগোল করেছিলাম ;-)
স্টিভেন

20

কেউই ব্যাখ্যা করেন নি যে আপনি গতিশীলভাবে ক্লাসগুলি সংজ্ঞায়িত করতে পারেন। সুতরাং আপনার কাছে এমন একটি সজ্জনকারী থাকতে পারে যা একটি উপক্লাস সংজ্ঞায়িত করে (এবং ফেরত দেয়):

def addId(cls):

    class AddId(cls):

        def __init__(self, id, *args, **kargs):
            super(AddId, self).__init__(*args, **kargs)
            self.__id = id

        def getId(self):
            return self.__id

    return AddId

যা পাইথন 2 এ ব্যবহার করা যেতে পারে (ব্ল্যাককিংহেটের মন্তব্য যা আপনাকে 2.6+ এ কেন চালিয়ে যেতে হবে তা ব্যাখ্যা করে):

class Foo:
    pass

FooId = addId(Foo)

এবং পাইথন 3 তে এটি পছন্দ করুন (তবে super()আপনার ক্লাসে ব্যবহার করতে সাবধান হন ):

@addId
class Foo:
    pass

সুতরাং আপনি আপনার কেক রাখতে পারেন এবং এটি খেতে পারেন - উত্তরাধিকার এবং সাজসজ্জা!


4
পাইকন ২.6++-তে ডেকরেটারে সাবক্লাসিং বিপজ্জনক, কারণ এটি superআসল ক্লাসে কলগুলিকে ব্রেক করে। যদি Fooএকটি পদ্ধতি নামে ছিল fooযে বলা super(Foo, self).foo(), এটা অসীম recurse কারণ নাম Fooউপশ্রেণী প্রসাধক দ্বারা ফিরে আবদ্ধ হয়, মূল শ্রেণী (যা কোন নামে অ্যাক্সেসযোগ্য নয়)। পাইথন 3 এর super()যুক্তিহীন সমস্যাটি এড়িয়ে চলে (আমি একই সংকলক যাদুতে ধরে নিলাম যা এটিকে কিছুতেই কাজ করতে দেয়)। ক্লাসটি বিভিন্ন নামে ম্যানুয়ালি সাজিয়ে আপনি ইস্যুটি নিয়ে কাজ করতে পারেন (যেমনটি আপনি পাইথনের ২.২ উদাহরণে করেছেন)।
ব্ল্যাকঙ্কহ্ট

1
হাহ। ধন্যবাদ, আমার কোনও ধারণা ছিল না (আমি পাইথন 3 ব্যবহার করি)। একটি মন্তব্য যোগ করা হবে।
অ্যান্ড্রু কুক

13

এটি একটি ভাল অনুশীলন নয় এবং সেই কারণে এটি করার কোনও ব্যবস্থা নেই। আপনি যা চান তা পূরণ করার সঠিক উপায় হ'ল উত্তরাধিকার।

মধ্যে একবার দেখে নিন বর্গ ডকুমেন্টেশন

একটি সামান্য উদাহরণ:

class Employee(object):

    def __init__(self, age, sex, siblings=0):
        self.age = age
        self.sex = sex    
        self.siblings = siblings

    def born_on(self):    
        today = datetime.date.today()

        return today - datetime.timedelta(days=self.age*365)


class Boss(Employee):    
    def __init__(self, age, sex, siblings=0, bonus=0):
        self.bonus = bonus
        Employee.__init__(self, age, sex, siblings)

এইভাবে বসের Employeeনিজস্ব __init__পদ্ধতি এবং নিজস্ব সদস্য সহ সবকিছু রয়েছে ।


3
আমি অনুমান করি যে আমি যা চেয়েছিলাম তা ছিল শ্রেণীর যে ক্লাসে রয়েছে তার বস অজ্ঞাব্যক্তি ছিল। এটি হ'ল, কয়েক ডজন বিভিন্ন ক্লাস থাকতে পারে যা আমি বসের বৈশিষ্ট্যগুলিতে প্রয়োগ করতে চাই। আমি কি এই ডজন ক্লাস বসের উত্তরাধিকার সূত্রে রেখেছি?
রবার্ট গাউল্যান্ড

5
@ রবার্ট গাউল্যান্ড: এজন্য পাইথনের একাধিক উত্তরাধিকার রয়েছে। হ্যাঁ, আপনার বিভিন্ন অভিভাবক শ্রেণীর বিভিন্ন দিক থেকে উত্তরাধিকারী হওয়া উচিত।
এসলট

7
@ এস.লোট: সাধারণভাবে একাধিক উত্তরাধিকার একটি খারাপ ধারণা, এমনকি উত্তরাধিকারের অত্যধিক মাত্রাও খারাপ। আমি আপনাকে একাধিক উত্তরাধিকার থেকে দূরে থাকার পরামর্শ দেব।
এমপিটারসন

5
মিটারসন: পাইথনের একাধিক উত্তরাধিকার কি এই পদ্ধতির চেয়ে খারাপ? অজগরটির একাধিক উত্তরাধিকার কী হয়েছে?
আরাফ্যাজিওন

2
@ আরাফ্যাঙ্গিয়ন: একাধিক উত্তরাধিকারকে সাধারণত সমস্যার একটি সতর্কতা চিহ্ন হিসাবে বিবেচনা করা হয়। এটি জটিল শ্রেণিবিন্যাস এবং কঠোরভাবে অনুসরণীয় সম্পর্কের জন্য তৈরি করে। যদি আপনার সমস্যা ডোমেন নিজেকে একাধিক উত্তরাধিকার হিসাবে ভাল ndsণ দেয়, (এটি কি শ্রেণিবদ্ধভাবে মডেল করা যায়?) এটি অনেকের পক্ষে একটি প্রাকৃতিক পছন্দ। এটি এমন সমস্ত ভাষার ক্ষেত্রে প্রযোজ্য যা বহু-উত্তরাধিকারের অনুমতি দেয়।
মর্টেন জেনসেন

6

আমি সম্মত হই যে উত্তোলিত সমস্যার জন্য উত্তরাধিকার উত্তম ফিট।

আমি এই প্রশ্নটি সার্থকভাবে দেখতে পেলাম যদিও সাজসজ্জা শ্রেণিতে, সমস্ত ধন্যবাদ।

পাইথন ২.7-এ উত্তরাধিকার কীভাবে জিনিসগুলিকে প্রভাবিত করে (এবং @ র্যাপস , যা মূল ফাংশনের ডক্ট্রিং ইত্যাদি রক্ষা করে) সহ অন্যান্য উত্তরের উপর ভিত্তি করে এখানে আরও কয়েকটি উদাহরণ রয়েছে :

def dec(klass):
    old_foo = klass.foo
    @wraps(klass.foo)
    def decorated_foo(self, *args ,**kwargs):
        print('@decorator pre %s' % msg)
        old_foo(self, *args, **kwargs)
        print('@decorator post %s' % msg)
    klass.foo = decorated_foo
    return klass

@dec  # No parentheses
class Foo...

প্রায়শই আপনি আপনার ডেকরেটারে প্যারামিটার যুক্ত করতে চান:

from functools import wraps

def dec(msg='default'):
    def decorator(klass):
        old_foo = klass.foo
        @wraps(klass.foo)
        def decorated_foo(self, *args ,**kwargs):
            print('@decorator pre %s' % msg)
            old_foo(self, *args, **kwargs)
            print('@decorator post %s' % msg)
        klass.foo = decorated_foo
        return klass
    return decorator

@dec('foo decorator')  # You must add parentheses now, even if they're empty
class Foo(object):
    def foo(self, *args, **kwargs):
        print('foo.foo()')

@dec('subfoo decorator')
class SubFoo(Foo):
    def foo(self, *args, **kwargs):
        print('subfoo.foo() pre')
        super(SubFoo, self).foo(*args, **kwargs)
        print('subfoo.foo() post')

@dec('subsubfoo decorator')
class SubSubFoo(SubFoo):
    def foo(self, *args, **kwargs):
        print('subsubfoo.foo() pre')
        super(SubSubFoo, self).foo(*args, **kwargs)
        print('subsubfoo.foo() post')

SubSubFoo().foo()

আউটপুট:

@decorator pre subsubfoo decorator
subsubfoo.foo() pre
@decorator pre subfoo decorator
subfoo.foo() pre
@decorator pre foo decorator
foo.foo()
@decorator post foo decorator
subfoo.foo() post
@decorator post subfoo decorator
subsubfoo.foo() post
@decorator post subsubfoo decorator

আমি আরও একটি সংক্ষিপ্ত দেখতে আমি একটি ফাংশন সজ্জা ব্যবহার করেছি। শ্রেণি সাজানোর জন্য এখানে একটি ক্লাস রয়েছে:

class Dec(object):

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

    def __call__(self, klass):
        old_foo = klass.foo
        msg = self.msg
        def decorated_foo(self, *args, **kwargs):
            print('@decorator pre %s' % msg)
            old_foo(self, *args, **kwargs)
            print('@decorator post %s' % msg)
        klass.foo = decorated_foo
        return klass

আরও শক্তিশালী সংস্করণ যা এই বন্ধনীগুলির জন্য যাচাই করে এবং যদি পদ্ধতিগুলি সজ্জিত শ্রেণিতে উপস্থিত না থাকে তবে তা কাজ করে:

from inspect import isclass

def decorate_if(condition, decorator):
    return decorator if condition else lambda x: x

def dec(msg):
    # Only use if your decorator's first parameter is never a class
    assert not isclass(msg)

    def decorator(klass):
        old_foo = getattr(klass, 'foo', None)

        @decorate_if(old_foo, wraps(klass.foo))
        def decorated_foo(self, *args ,**kwargs):
            print('@decorator pre %s' % msg)
            if callable(old_foo):
                old_foo(self, *args, **kwargs)
            print('@decorator post %s' % msg)

        klass.foo = decorated_foo
        return klass

    return decorator

assertচেক যে প্রসাধক প্রথম বন্ধনী ছাড়া ব্যবহার করা হয়েছে। যদি এটি থাকে, তবে সাজানো শ্রেণিটি সাজসজ্জারের msgপ্যারামিটারে দেওয়া হবে, যা একটি উত্থাপন করে AssertionError

@decorate_ifdecoratorযদি কেবল conditionমূল্যায়ন করে তবেই প্রযোজ্য True

getattr, callableপরীক্ষা এবং @decorate_ifতাই ব্যবহার করা হয় যে প্রসাধক যদি ভাঙে না foo()পদ্ধতি বর্গ সজ্জিত হচ্ছে অস্তিত্ব নেই।


4

এখানে একটি বর্গ সাজসজ্জার একটি বাস্তব ভাল বাস্তবায়ন আছে:

https://github.com/agiliq/Django-parsley/blob/master/parsley/decorators.py

আমি আসলে এটি একটি আকর্ষণীয় বাস্তবায়ন মনে করি। যেহেতু এটি এটি সজ্জিত শ্রেণীর সাবক্লাস করে, এটি isinstanceচেকের মতো জিনিসগুলিতে এই শ্রেণীর মতো ঠিক আচরণ করবে ।

এটা তোলে একটা অতিরিক্ত লাভ আছে: এটা জন্য বিরল নয় __init__একটি কাস্টম বিবৃতি জ্যাঙ্গো ফরম পরিবর্তন বা সংযোজন করতে self.fieldsতাই এটি ভালো পরিবর্তনের জন্য self.fieldsঘটতে পরে সব __init__প্রশ্নে বর্গ জন্য চালানো হয়েছে।

খুব চালাক.

যাইহোক, আপনার শ্রেণিতে আপনি চান সজ্জাটি কনস্ট্রাক্টরকে পরিবর্তন করতে হবে, যা আমি মনে করি না যে এটি কোনও শ্রেণি সাজসজ্জার জন্য একটি ভাল ব্যবহারের কেস।


0

এখানে একটি উদাহরণ যা কোনও শ্রেণীর পরামিতিগুলি ফেরত দেওয়ার প্রশ্নের উত্তর দেয়। তদুপরি, এটি এখনও উত্তরাধিকারের শৃঙ্খলাটিকে সম্মান করে, যেমন কেবলমাত্র শ্রেণীর প্যারামিটারগুলিই ফিরে আসে। ফাংশনটি get_paramsএকটি সাধারণ উদাহরণ হিসাবে যুক্ত করা হয়েছে তবে পরিদর্শন মডিউলটির জন্য অন্যান্য কার্যকারিতা যুক্ত করা যেতে পারে।

import inspect 

class Parent:
    @classmethod
    def get_params(my_class):
        return list(inspect.signature(my_class).parameters.keys())

class OtherParent:
    def __init__(self, a, b, c):
        pass

class Child(Parent, OtherParent):
    def __init__(self, x, y, z):
        pass

print(Child.get_params())
>>['x', 'y', 'z']

0

জ্যাঙ্গোতে রয়েছে method_decoratorএকটি সজ্জা যা কোনও সাজসজ্জারকে একটি পদ্ধতি সাজসজ্জারে রূপান্তরিত করে, আপনি দেখতে পাবেন এটি কীভাবে কার্যকর হয় django.utils.decorators:

https://github.com/django/django/blob/50cf183d219face91822c75fa0a15fe2fe3cb32d/django/utils/decorators.py#L53

https://docs.djangoproject.com/en/3.0/topics/class-based-views/intro/#decorating-the-class

আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.