সাজসজ্জারের সাথে পাইথন ফাংশন সংজ্ঞাটি কীভাবে বাইপাস করবেন?


66

আমি জানতে চাই যে বৈশ্বিক সেটিংসের (যেমন ওএস) উপর ভিত্তি করে পাইথন ফাংশন সংজ্ঞা নিয়ন্ত্রণ করা সম্ভব কিনা। উদাহরণ:

@linux
def my_callback(*args, **kwargs):
    print("Doing something @ Linux")
    return

@windows
def my_callback(*args, **kwargs):
    print("Doing something @ Windows")
    return

তারপরে, যদি কেউ লিনাক্স ব্যবহার করে থাকে তবে এর প্রথম সংজ্ঞাটি my_callbackব্যবহার করা হবে এবং দ্বিতীয়টি নীরবে উপেক্ষা করা হবে।

এটি ওএস নির্ধারণ সম্পর্কে নয়, এর কার্যকারিতা সংজ্ঞা / সজ্জকার সম্পর্কে।


10
দ্বিতীয় প্রসাধক সমতূল্য my_callback = windows(<actual function definition>)- তাই নাম my_callback হবে প্রসাধক কি হতে পারে কি নির্বিশেষে মুছে ফেলা হতে। ফাংশনের লিনাক্স সংস্করণটি সেই পরিবর্তনশীলটিতে শেষ হওয়ার একমাত্র উপায় যদি windows()এটি ফিরে আসে তবে ফাংশনটির লিনাক্স সংস্করণ সম্পর্কে জানার উপায় নেই। আমি মনে করি এটি সম্পাদন করার আরও সাধারণ উপায় হ'ল পৃথক ফাইলগুলিতে ওএস-নির্দিষ্ট ফাংশন সংজ্ঞা রাখা এবং শর্তসাপেক্ষে importকেবল তার মধ্যে একটি।
জেসনহার্পার

7
আপনি এর ইন্টারফেসটি একবার দেখে নিতে পারেন functools.singledispatch, যা আপনি যা চান তার অনুরূপ কিছু করে। সেখানে, registerসাজসজ্জাটি প্রেরণকারী সম্পর্কে জানেন (কারণ এটি প্রেরণের ক্রিয়াকলাপের একটি বৈশিষ্ট্য, এবং সেই নির্দিষ্ট প্রেরকের জন্য নির্দিষ্ট), সুতরাং এটি প্রেরণকারীকে ফিরিয়ে দিতে এবং আপনার পদ্ধতির সমস্যাগুলি এড়াতে পারে।
ব্যবহারকারী 2357112

5
আপনি এখানে যা করার চেষ্টা করছেন তা প্রশংসনীয় হলেও এটি উল্লেখ করার মতো যে, বেশিরভাগ সিপিথন একটি স্ট্যান্ডার্ড "যদি / এলিফ / অন্য" তে একটি প্ল্যাটফর্ম চেক করে; উদাহরণস্বরূপ uuid.getnode(),। (এটি বলেছিল, এখানে টডের উত্তরটি বেশ ভাল))
ব্র্যাড সলোমন

উত্তর:


58

যদি আপনার কোডটিতে #fdef উইন্ডোজ / # এন্ডিফের মতো একই ধরণের প্রভাব রাখার লক্ষ্য হয় .. এটি করার একটি উপায় এখানে রয়েছে (আমি একটি ম্যাক বিটিডব্লুতে আছি)।

সিম্পল কেস, নো চেইনিং

>>> def _ifdef_decorator_impl(plat, func, frame):
...     if platform.system() == plat:
...         return func
...     elif func.__name__ in frame.f_locals:
...         return frame.f_locals[func.__name__]
...     else:
...         def _not_implemented(*args, **kwargs):
...             raise NotImplementedError(
...                 f"Function {func.__name__} is not defined "
...                 f"for platform {platform.system()}.")
...         return _not_implemented
...             
...
>>> def windows(func):
...     return _ifdef_decorator_impl('Windows', func, sys._getframe().f_back)
...     
>>> def macos(func):
...     return _ifdef_decorator_impl('Darwin', func, sys._getframe().f_back)

সুতরাং এই প্রয়োগের মাধ্যমে আপনি আপনার প্রশ্নে একই সিনট্যাক্স পাবেন।

>>> @macos
... def zulu():
...     print("world")
...     
>>> @windows
... def zulu():
...     print("hello")
...     
>>> zulu()
world
>>> 

উপরের কোডটি যা করছে তা মূলত প্ল্যাটফর্মের সাথে মিলে জুলুকে জুলুকে বরাদ্দ করে। প্ল্যাটফর্মটি যদি মেলে না তবে এটি জুলুটিকে পূর্বে সংজ্ঞায়িত করা হলে ফিরে আসবে। যদি এটি সংজ্ঞায়িত না করা হয়, তবে এটি একটি স্থানধারক ফাংশন প্রদান করে যা একটি ব্যতিক্রম উত্থাপন করে।

আপনি যদি মনে রাখেন তবে সজ্জাবিদরা ধারণাটি সহজেই নির্ধারণ করতে পারবেন

@mydecorator
def foo():
    pass

এর সাথে সাদৃশ্যপূর্ণ:

foo = mydecorator(foo)

প্যারামিটারাইজড ডেকরেটার ব্যবহার করে একটি বাস্তবায়ন এখানে দেওয়া হয়েছে:

>>> def ifdef(plat):
...     frame = sys._getframe().f_back
...     def _ifdef(func):
...         return _ifdef_decorator_impl(plat, func, frame)
...     return _ifdef
...     
>>> @ifdef('Darwin')
... def ice9():
...     print("nonsense")

প্যারামিটারাইজড সজ্জাকরণগুলি সাদৃশ্যপূর্ণ foo = mydecorator(param)(foo)

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

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

>>> @macos
... class CallableClass:
...     
...     @macos
...     def __call__(self):
...         print("CallableClass.__call__() invoked.")
...     
...     @macos
...     def func_with_inner(self):
...         print("Defining inner function.")
...         
...         @macos
...         def inner():
...             print("Inner function defined for Darwin called.")
...             
...         @windows
...         def inner():
...             print("Inner function for Windows called.")
...         
...         inner()
...         
...     @macos
...     class InnerClass:
...         
...         @macos
...         def inner_class_function(self):
...             print("Called inner_class_function() Mac.")
...             
...         @windows
...         def inner_class_function(self):
...             print("Called inner_class_function() for windows.")

উপরের অংশটি সজ্জকারের মৌলিক প্রক্রিয়া, কীভাবে কলারের সুযোগ অ্যাক্সেস করতে পারে এবং সাধারণ অ্যালগরিদম সংজ্ঞায়িত একটি অভ্যন্তরীণ ফাংশন রাখার মাধ্যমে একই ধরণের আচরণকারী একাধিক সাজসজ্জারকে কীভাবে সরলীকরণ করা যায় তা প্রদর্শন করে।

চেইন সাপোর্ট

কোনও ফাংশন একাধিক প্ল্যাটফর্মের জন্য প্রযোজ্য কিনা তা নির্দেশ করে এই সাজসজ্জা শৃঙ্খলাবদ্ধদের সমর্থন করার জন্য, সাজসজ্জাটি এভাবে প্রয়োগ করা যেতে পারে:

>>> class IfDefDecoratorPlaceholder:
...     def __init__(self, func):
...         self.__name__ = func.__name__
...         self._func    = func
...         
...     def __call__(self, *args, **kwargs):
...         raise NotImplementedError(
...             f"Function {self._func.__name__} is not defined for "
...             f"platform {platform.system()}.")
...
>>> def _ifdef_decorator_impl(plat, func, frame):
...     if platform.system() == plat:
...         if type(func) == IfDefDecoratorPlaceholder:
...             func = func._func
...         frame.f_locals[func.__name__] = func
...         return func
...     elif func.__name__ in frame.f_locals:
...         return frame.f_locals[func.__name__]
...     elif type(func) == IfDefDecoratorPlaceholder:
...         return func
...     else:
...         return IfDefDecoratorPlaceholder(func)
...
>>> def linux(func):
...     return _ifdef_decorator_impl('Linux', func, sys._getframe().f_back)

এইভাবে আপনি শৃঙ্খলা সমর্থন করেন:

>>> @macos
... @linux
... def foo():
...     print("works!")
...     
>>> foo()
works!

4
মনে রাখবেন, এই শুধুমাত্র কাজ করে macosএবং windowsএকই মডিউলে সংজ্ঞায়িত করা হয় zulu। আমার বিশ্বাস এই অনুষ্ঠানে পরিণাম ডেকে আনবে যেমন বাম হচ্ছে Noneযদি ফাংশন বর্তমান প্ল্যাটফর্ম, যা কিছু হতে করবে সংজ্ঞায়িত করা হয় না খুব বিভ্রান্তিকর রানটাইম ত্রুটি।
ব্রায়ান

1
এটি মডিউল-গ্লোবাল স্কোপে সংজ্ঞায়িত না হওয়া পদ্ধতি বা অন্যান্য ফাংশনগুলির জন্য কাজ করবে না।
ব্যবহারকারী 2357112

1
আপনাকে ধন্যবাদ মনিকা। হ্যাঁ, আমি এটি কোনও ক্লাসের সদস্য ফাংশনে ব্যবহার করার জন্য জবাবদিহি করিনি .. ঠিক আছে .. আমি আমার কোডটি আরও জেনেরিক করতে পারি কিনা তা আমি দেখতে পাচ্ছি।
টড

1
@ মনিকা ঠিক আছে .. আমি ক্লাস সদস্য ফাংশনগুলির জন্য অ্যাকাউন্টটি কোড আপডেট করেছিলাম। আপনি কি একবার চেষ্টা করে দেখতে পারেন?
টড

2
@ মনিকা, ঠিক আছে .. আমি ক্লাসের পদ্ধতিগুলি কভার করার জন্য কোডটি আপডেট করেছি এবং এটি কার্যকর কিনা তা নিশ্চিত করার জন্য কিছুটা পরীক্ষা-নিরীক্ষা করেছি - কোনও বিস্তৃত কিছুই না .. আপনি যদি এটিকে রান দিতে চান তবে আমাকে জানাতে এটি কীভাবে হয়।
টড

37

যদিও @decoratorসিনট্যাক্স সৌন্দর্য চমৎকার, আপনি পেতে সঠিক একই আচরণ যেমন একটি সহজ সঙ্গে আকাঙ্ক্ষিত if

linux = platform.system() == "Linux"
windows = platform.system() == "Windows"
macos = platform.system() == "Darwin"

if linux:
    def my_callback(*args, **kwargs):
        print("Doing something @ Linux")
        return

if windows:
    def my_callback(*args, **kwargs):
        print("Doing something @ Windows")
        return

যদি প্রয়োজন হয় তবে এটি সহজেই প্রয়োগ করতে পারে যে কোনও কোনও ক্ষেত্রে এটি মিলেছে।

if linux:
    def my_callback(*args, **kwargs):
        print("Doing something @ Linux")
        return

elif windows:
    def my_callback(*args, **kwargs):
        print("Doing something @ Windows")
        return

else:
     raise NotImplementedError("This platform is not supported")

8
+1, যদি আপনি যাইহোক দুটি পৃথক ফাংশন লিখতে যাচ্ছেন, তবে এটি যাওয়ার উপায়। আমি সম্ভবত ডিবাগিংয়ের জন্য মূল ফাংশনটির নামগুলি সংরক্ষণ করতে চাই (সুতরাং স্ট্যাকের চিহ্নগুলি সঠিক): def callback_windows(...)এবং def callback_linux(...)তারপর if windows: callback = callback_windows, ইত্যাদি But তবে কোনওভাবেই এটি পড়ার, ডিবাগ করার এবং বজায় রাখা সহজ।
শেঠ

আপনি সম্মত হন যে ব্যবহারের ক্ষেত্রে সন্তুষ্ট করার জন্য এটিই সহজতম কৌশল I তবে, আসল প্রশ্নটি ছিল সাজসজ্জার বিষয়ে এবং কীভাবে তাদের কার্য সম্পাদনের ঘোষণায় প্রয়োগ করা যেতে পারে। সুতরাং সুযোগটি কেবল শর্তযুক্ত প্ল্যাটফর্ম যুক্তি ছাড়িয়ে ic
টড

3
আমি একটি ব্যবহার করব elif, কারণ এটি কখনই প্রত্যাশিত ঘটনা হতে পারে না যে linux/ windows/ এর মধ্যে একের বেশি macOSসত্য হবে। আসলে, আমি সম্ভবত একাধিক বুলিয়ান পতাকাের পরিবর্তে কেবল একটি একক ভেরিয়েবলের সংজ্ঞা দিয়েছি p = platform.system(), তারপরে ব্যবহার করব if p == "Linux"। যে ভেরিয়েবলগুলি নেই তা সিঙ্কের বাইরে যেতে পারে না।
চ্যানার

@chepner যদি পরিষ্কার ক্ষেত্রে পারস্পরিক একচেটিয়া আছে, elifঅবশ্যই এর সুবিধাগুলো আছে - বিশেষত একটি trailing else+ + raiseযে অন্তত একটি ক্ষেত্রে তা নিশ্চিত করার জন্য করেনি ম্যাচ। ভবিষ্যদ্বাণীটি মূল্যায়ন করার জন্য, আমি তাদের প্রাক-মূল্যায়ন করা পছন্দ করি - এটি সদৃশতা এবং সংজ্ঞা সংজ্ঞা এবং ব্যবহারকে এড়িয়ে চলে। এমনকি যদি ফলাফলটি ভেরিয়েবলগুলিতে সঞ্চয় না হয় তবে এখন হার্ডকোডযুক্ত মান রয়েছে যা সিঙ্কের বাইরে চলে যেতে পারে। আমি বিভিন্ন উপায়ে যেমন ম্যাজিক স্ট্রিংগুলি কখনও মনে করতে পারি না , যেমন platform.system() == "Windows"বনাম sys.platform == "win32", ...
মিস্টারমিয়াগি

আপনি স্ট্রিংগুলি গণনা করতে পারেন, একটি সাবক্লাস Enumবা কেবল ধ্রুবকগুলির সেট সহ।
চ্যানার

8

নীচে এই যান্ত্রিকটির সম্ভাব্য বাস্তবায়ন দেওয়া হল। মন্তব্যে উল্লিখিত হিসাবে, "মাষ্টার প্রেরণকারী" ইন্টারফেস প্রয়োগ করা আরও ভাল হতে পারে যেমন দেখা যায়functools.singledispatch একাধিক ওভারলোডেড সংজ্ঞার সাথে যুক্ত রাষ্ট্রের উপর নজর রাখতে, । আমার আশা এই বাস্তবায়ন কমপক্ষে কোনও বৃহত্তর কোডবেসের জন্য এই কার্যকারিতাটি বিকাশ করার সময় আপনার যে সমস্যার মুখোমুখি হতে পারে সে সম্পর্কে অন্তত অন্তর্দৃষ্টি দেবে hope

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

import platform
from functools import wraps
from typing import Callable, Optional


def implement_for_os(os_name: str):
    """
    Produce a decorator that defines a provided function only if the
    platform returned by `platform.system` matches the given `os_name`.
    Otherwise, replace the function with one that raises `NotImplementedError`.
    """
    def decorator(previous_definition: Optional[Callable]):
        def _decorator(func: Callable):
            if previous_definition and hasattr(previous_definition, '_implemented_for_os'):
                # This function was already implemented for this platform. Leave it unchanged.
                return previous_definition
            elif platform.system() == os_name:
                # The current function is the correct impementation for this platform.
                # Mark it as such, and return it unchanged.
                func._implemented_for_os = True
                return func
            else:
                # This function has not yet been implemented for the current platform
                @wraps(func)
                def _not_implemented(*args, **kwargs):
                    raise NotImplementedError(
                        f"The function {func.__name__} is not defined"
                        f" for the platform {platform.system()}"
                    )

                return _not_implemented
        return _decorator

    return decorator


implement_linux = implement_for_os('Linux')

implement_windows = implement_for_os('Windows')

এই সাজসজ্জার ব্যবহার করার জন্য, আমাদের অবশ্যই দুটি স্তরের ইন্ডিরিয়ারেশন দিয়ে কাজ করতে হবে। প্রথমত, আমরা অবশ্যই অবশ্যই অবশ্যই প্ল্যাটফর্মটি নির্দিষ্ট করব যা আমরা ডেকরেটরকে সাড়া দিতে চাই। এটি implement_linux = implement_for_os('Linux')উপরের লাইন এবং এর উইন্ডো সমকক্ষ দ্বারা সম্পন্ন হয় । এরপরে, আমাদের ভার্য লোড হওয়ার ফাংশনটির বিদ্যমান সংজ্ঞাটি পাশ করতে হবে। এই পদক্ষেপটি অবশ্যই নীচে প্রদর্শিত হিসাবে সংজ্ঞা স্থানে সম্পন্ন করতে হবে।

প্ল্যাটফর্ম-বিশিষ্ট ফাংশনটি সংজ্ঞায়িত করতে আপনি এখন নিম্নলিখিতটি লিখতে পারেন:

@implement_linux(None)
def some_function():
    ...

@implement_windows(some_function)
def some_function():
   ...

implement_other_platform = implement_for_os('OtherPlatform')

@implement_other_platform(some_function)
def some_function():
   ...

কল some_function() সরবরাহ করা প্ল্যাটফর্ম-নির্দিষ্ট সংজ্ঞাতে যথাযথভাবে প্রেরণ হবে।

ব্যক্তিগতভাবে, আমি উত্পাদন কোডে এই কৌশলটি ব্যবহার করার পরামর্শ দেব না। আমার মতে, এই পার্থক্যগুলি যেখানে ঘটে সেখানে প্রতিটি স্থানে প্ল্যাটফর্ম নির্ভর নির্ভর আচরণ সম্পর্কে স্পষ্ট হওয়া ভাল।


এটি কি @ ইমপ্লিমেন্ট_ফর্ম_স ("লিনাক্স") ইত্যাদি হবে না ...
th0nk-

@ th0nk নং - ফাংশনটি implement_for_osনিজেই কোনও সজ্জা প্রদানকারীকে ফিরিয়ে দেয় না, বরং এমন একটি ফাংশন দেয় যা একবারে প্রশ্নযুক্ত ফাংশনের পূর্ববর্তী সংজ্ঞা সহ সরবরাহকারীকে তৈরি করবে।
ব্রায়ান

5

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

from collections import defaultdict
import inspect
import os


class PlatformFunction(object):
    mod_funcs = defaultdict(dict)

    @classmethod
    def get_function(cls, mod, func_name):
        return cls.mod_funcs[mod][func_name]

    @classmethod
    def set_function(cls, mod, func_name, func):
        cls.mod_funcs[mod][func_name] = func


def linux(func):
    frame_info = inspect.stack()[1]
    mod = inspect.getmodule(frame_info.frame)
    if os.environ['OS'] == 'linux':
        PlatformFunction.set_function(mod, func.__name__, func)

    def call(*args, **kwargs):
        return PlatformFunction.get_function(mod, func.__name__)(*args,
                                                                 **kwargs)

    return call


def windows(func):
    frame_info = inspect.stack()[1]
    mod = inspect.getmodule(frame_info.frame)
    if os.environ['OS'] == 'windows':
        PlatformFunction.set_function(mod, func.__name__, func)

    def call(*args, **kwargs):
        return PlatformFunction.get_function(mod, func.__name__)(*args,
                                                                 **kwargs)

    return call


@linux
def myfunc(a, b):
    print('linux', a, b)


@windows
def myfunc(a, b):
    print('windows', a, b)


if __name__ == '__main__':
    myfunc(1, 2)

0

একটি পরিষ্কার সমাধান হ'ল একটি ডেডিকেটেড ফাংশন রেজিস্ট্রি তৈরি করা যা প্রেরণ করা হয় sys.platform। এটি খুব অনুরূপ functools.singledispatch। এই ফাংশনের উত্স কোডটি একটি কাস্টম সংস্করণ প্রয়োগের জন্য একটি ভাল সূচনা পয়েন্ট সরবরাহ করে:

import functools
import sys
import types


def os_dispatch(func):
    registry = {}

    def dispatch(platform):
        try:
            return registry[platform]
        except KeyError:
            return registry[None]

    def register(platform, func=None):
        if func is None:
            if isinstance(platform, str):
                return lambda f: register(platform, f)
            platform, func = platform.__name__, platform  # it is a function
        registry[platform] = func
        return func

    def wrapper(*args, **kw):
        return dispatch(sys.platform)(*args, **kw)

    registry[None] = func
    wrapper.register = register
    wrapper.dispatch = dispatch
    wrapper.registry = types.MappingProxyType(registry)
    functools.update_wrapper(wrapper, func)
    return wrapper

এখন এটি এর মতো ব্যবহার করা যেতে পারে singledispatch:

@os_dispatch  # fallback in case OS is not supported
def my_callback():
    print('OS not supported')

@my_callback.register('linux')
def _():
    print('Doing something @ Linux')

@my_callback.register('windows')
def _():
    print('Doing something @ Windows')

my_callback()  # dispatches on sys.platform

নিবন্ধকরণটি সরাসরি ফাংশনগুলির নামগুলিতে কাজ করে:

@os_dispatch
def my_callback():
    print('OS not supported')

@my_callback.register
def linux():
    print('Doing something @ Linux')

@my_callback.register
def windows():
    print('Doing something @ Windows')
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.