برنامه نویسی ۷۴۱ بازدید

آیا تاکنون پیش آمده که یک شیء پایتون داشته باشید که بخواهید به سرعت با جزییات آن آشنا شوید؟ شاید هم پیش آمده که شیء آشنایی داشته‌اید و به دنبال یک متد خاص می‌گشته‌اید، اما نمی‌دانسته‌اید آن را چگونه برای گوگل توصیف کنید. زمانی که در زمینه علوم داده روی اشیای پیچیده در کتابخانه‌هایی مانند TensorFlow کار می‌کنید، بارها با این موقعیت مواجه خواهید شد. در چنین مواردی آرزو می‌کنید که روش سریع‌تری برای شناخت جزییات اشیای پایتون در کتابخانه‌های جدید وجود داشته باشد، چون ممکن است مستندات در دسترس نباشد یا نادرست باشند و یا بررسی آن‌ها زمان‌بر باشد.

در این مقاله به روش بررسی عمیق اشیا پایتون می‌پردازیم و یک ابزار CLI قابل نصب از طریق pip معرفی می‌کنیم که pip dis (+) نام دارد و همه این کارها را برای شما انجام می‌دهد. اگر می‌خواهید مستقیماً با این ابزار آشنا شوید می‌توانید این بخش را رد کرده و یکراست به سراغ بخش «ابزار بازبینی شیء CLI به نام Peep Dis» بروید.

بررسی شیء

در این بخش به عنوان یک مثال ساده یک کلاس Rectangle یا چند متد و خصوصیت تعریف می‌کنیم:

class Rectangle:
    def __init__(self, a: float, b: float):
        self.a = a
        self.b = b

    def area(self) -> float:
        return self.a * self.b

    def scale(self, factor: float):
        """ scale the side lengths by `factor` """
        self.a = factor * self.a
        self.b = factor * self.b

    def bisect(self):
        """ reduce a by a factor of 2 to "cut in half" """
        self.a /= 2

    def __str__(self):
        return self.__class__.__name__ + str({'a': self.a, 'b': self.b})

تابع dir یک تابع داخلی ساده است که همه خصوصیت‌ها و متدهای یک شیء را به جز __dir__ که overload شده است، فهرست‌بندی می‌کند. این همان خصوصیت است که ادیتورها و IDE-ها برای «تکمیل خودکار» (Auto-complete) مورد استفاده قرار می‌دهند.

>>> rect = Rectangle(3., 4.)
>>> dir(rect)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'a', 'area', 'b', 'bisect', 'scale']

خروجی ما لیستی از رشته‌ها است که خصوصیت‌ها و متدهای شیء را نمایش می‌دهد که غالباً شامل موارد داخلی هستند. به طور معمول موارد درونی چندان مفید نیستند و فقط باعث افزایش شلوغی می‌شوند.

فیلتر کردن موارد داخلی

بسته به تعریف موارد درونی می‌توان یا از فیلترینگ رشته یا فیلترینگ نوع برای حذف آن‌ها استفاده کرد.

فیلترینگ رشته

def dir_string_filter(obj):
    is_magic = lambda x: (x.startswith('__') and x.endswith('__'))
    return [x for x in dir(obj) if not is_magic(x)]
>>> dir_string_filter(rect)
['a', 'area', 'b', 'bisect', 'scale']

فیلترینگ نوع

from types import BuiltinMethodType
def dir_type_filter(obj):
    is_builtin = lambda x: isinstance(getattr(obj, x), BuiltinMethodType) 
    return [x for x in dir(obj) if not is_builtin(x)]
>>> dir_type_filter(rect) 
['__class__', '__delattr__', '__dict__', '__doc__', '__eq__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__', '__repr__', '__setattr__', '__str__', '__weakref__', 'a', 'area', 'b', 'bisect', 'scale']

فیلترینگ رشته، همه متدها و خصوصیت‌های جادویی را حذف می‌کند، در حالی که فیلترینگ به وسیله BuiltinMethodType متدهای داخلی نوشته شده به زبان C را حذف می‌کند و خصوصیت‌های جادویی و بسیاری از متدهای غیر جادویی باقی می‌مانند. در اغلب موارد متدها و خصوصیت‌های جادویی مواردی هستند که می‌خواهیم حذف کنیم و از این رو از روش فیلترینگ رشته استفاده خواهیم کرد.

>>> dir_filtered = dir_string_filter(rect)

جداسازی متدها از خصوصیت‌ها

از میان آیتم‌هایی که پس از فیلترینگ به دست می‌آیند، ما همچنان نمی‌دانیم که کدام یک خصوصیت و کدام یک متد هستند. بدین منظور می‌توان از تابع داخلی callable (https://docs.python.org/3/library/functions.html#callable)‎ برای فیلتر کردن آن‌ها استفاده کرد.

خصوصیت‌ها

>>> attrs = [x for x in dir_filtered if not callable(getattr(rect, x))]
>>> attrs
['a', 'b']

متدها

>>> methods = [x for x in dir_filtered if callable(getattr(rect, x))]
>>> methods
  ['area', 'bisect', 'scale']

برای دیدن مقادیر خصوصیت‌ها

>>> attr_outputs = {x: getattr(rect, x) for x in attrs}
>>> attr_outputs
{'a': 3.0, 'b': 4.0}

فراخوانی متدها

در مورد متدها دیدن مقادیر خروجی چندان ساده نیست. یکی از ریسک‌های فراخوانی گاه‌به‌گاه متدهای تصادفی این است که ممکن است حالت شیء اصلی را تغییر دهند. برای نمونه Rectangle.bisect مقدار None بازگشت خواهد داد، اما اندازه مستطیل را با ضریبی از 2 کاهش می‌دهد.

 ...
    def bisect(self):
        """ reduce a by a factor of 2 to "cut in half" """
        self.a /= 2

می‌توان با ایجاد یک copy.deepcopy پیش از هر فراخوانی متد، از بروز تغییراتی در شیء اصلی جلوگیری کرد، گرچه این وضعیت می‌تواند در مورد اشیای بزرگ از نظر محاسباتی سنگین باشد. توجه داشته باشید که متدهایی که متغیرهای کلاس یا متغیرهای سراسری را تغییر می‌دهند و یا با محیط بیرونی‌شان تعامل دارند ممکن است تأثیرات پایداری داشته باشند.

تابع get_callable که در ادامه تعریف شده است، شیء اصلی را کپی کرده و متد متصل به آن کپی را بازگشت می‌دهد که می‌تواند به صورت مستقل از شیء والدش فراخوانی شود.

from copy import deepcopy
def get_callable(obj, name: str):
    return getattr(deepcopy(obj), name)

متدهایی مانند Rectangle.scale که نیازمند پارامترهای موقعیتی (positional) باشند، چالشی مضاعف ایجاد می‌کنند.

 ...
    def scale(self, factor: float):
        """ scale the side lengths by factor """
        self.a = factor * self.a self.b = factor * self.b

ما می‌توانیم خروجی‌های متدهایی که نیازمند موقعیت‌های مختلف هستند را با استفاده از «بررسی همه‌جانبه قبلی» یا با استفاده از gestfullargspec از ماژول داخلی insepct به دست آوریم تا مشخص شود که کدام اشیا نیازمند آرگومان‌های موقعیتی نیستند و تنها آن‌ها را ارزیابی کنیم.

تکنیک 1 فراخوانی متدها: بررسی همه‌جانبه

def attempt_method_call(func):
    try: 
        return str(func()) 
    except: 
        return '(failed to evaluate method)'
>>> outputs = {x: attempt_method_call(get_callable(rect, x)) for x in methods} 
>>> outputs 
{'area': 12.0, 'bisect': None, 'scale': '(failed to evaluate method)', }

چنان که انتظار می‌رود، area و bisect با موفقیت اجرا شده‌اند، در حالی که scale که نیازمند آرگومان‌های موقعیتی دیگری است چنین نبوده است.

تکنیک 2 فراخوانی متدها: بررسی جایگاه‌ها

ابتدا باید با getfullargspec آشنا شویم:

from inspect import getfullargspec
>>> getfullargspec(rect.scale)
FullArgSpec(args=['self', 'factor'], varargs=None, varkw=None, defaults=None, kwonlyargs=[], kwonlydefaults=None, annotations={'factor': <class 'float'>})

این متد یک شیء FullArgSpec بازگشت می‌دهد. args شامل نام‌های آرگومان‌ها است. vargs و varkw شامل نام‌های آرگومان‌های طول متغیر و آرگومان‌های کلیدواژه است که به ترتیب با استفاده از عملگرهای * و ** مشخص می‌شوند. defaults شامل مقادیر پیش‌فرض برای آرگومان‌های کلیدواژه است. kwonlyargs اسامی آرگومان‌های صرفا-کلیدواژه را لیست می‌کند. kwonlydefaults یک دیکشنری است که دارای مقادیر پیش‌فرض آرگومان صرفاً-کلیدواژه است. annotations یک دیکشنری است که همه «حاشیه‌نویسی‌های نوع» (type annotations) را تعیین می‌کند.

می‌توان از این اطلاعات برای بررسی این نکته که آیا یک متد، آرگومان‌های موقعیتی دارد یا نه، استفاده کرد و تنها در صورت عدم وجود چنین چیزی آن را ارزیابی کرد. در آغاز تلاش خواهیم کرد FullArgSpec متد را به دست آوریم گرچه همه انواع قابل فراخوانی پشتیبانی نمی‌شوند. سپس آرگومان‌ها را استخراج می‌کنیم و یک تابع کاربردی به نام remove_self‎‎_ برای حذف آرگومان selft تعریف می‌کنیم که در متدهای استاندارد تصریح شده است. با این که این کار در اینجا انجام نیافته، اما می‌توانیم با بررسی آرگومان cls از فراخوانی متدهای کلاس اجتناب کنیم. در نهایت اگر آرگومان‌ها مقادیر پیش‌فرضی داشته باشند، در این صورت هیچ آرگومان موقعیتی وجود ندارد و لذا متد می‌تواند فراخوانی شود.

def call_if_no_positionals(func):
    try:
        spec = getfullargspec(func)
    except TypeError:
        return '(unsupported callable)'
    args = spec.args
    _remove_self(args)
    n_defaults = len(spec.defaults) if spec.defaults else 0
    # check if all args are kwargs
    if len(args) == n_defaults:
        return func()
    else:
        return '(requires positional args)'

def _remove_self(arg_list):
    """ remove implicit `self` argument from list of arg names """
    if 'self' in arg_list:
        arg_list.remove('self')

با استفاده از این روش همان نتایجی که در روش بررسی همه جانبه به دست امد را کسب می‌کنیم:

>>> method_outputs = {x: call_if_no_positionals(get_callable(rect, x)) for x in methods}
>>> method_outputs
{'area': 12.0, 'bisect': None, 'scale': '(requires positional args)'}

استنباط انواع آرگومان

در ادامه تلاش می‌کنیم نوع هر آرگومان را از روی نوع حاشیه‌نویسی مقادیر پیش‌فرض به دست آوریم. ما infer_arg_types را تعریف می‌کنیم که به صورتی مشابه call_if_no_positionals آغاز می‌شود، اما به جای فراخوانی متد، یک OrderedDict با انواع استنباط شده مقداردهی می‌کند.

from inspect import getfullargspec
from typing import Dict

def infer_arg_types(func):
    try:
        spec = getfullargspec(func)
    except TypeError:
        return '(unsupported callable)'
    arg_types = OrderedDict()
    args = spec.args
    _remove_self(args)
    # infer types from type hints
    for arg in args:
        arg_types[arg] = spec.annotations.get(arg, None).__name__
    # infer types from default args
    if spec.defaults:
        for i, v in enumerate(spec.defaults):
            arg_i = - len(spec.defaults) + i
            arg = args[arg_i]
            arg_types[arg] = type(v).__name__
    if not arg_types:
        return None
    return arg_types

این را روی وهله‌ای از Rectangle خود فرامی‌خوانیم تا انواع همه متدهایی که نیازمند آرگومان هستند به دست آید چون همه آن‌ها دارای «سرنخ نوع» (type hint) هستند. توجه کنید که اگر دارای سرنخ نوع نبودند، این ترفند تنها برای آرگومان‌های کلیدواژه کار می‌کرد.

>>> method_arg_types = {x: get_arg_types(getattr(rect, x)) for x in methods}
>>> method_arg_types
{'area': None, 'scale': OrderedDict([('factor', 'float')]), 'take_half': None}

ساخت آرگومان‌ها

اگر بخواهیم خروجی‌های نمونه متدهایی که نیازمند آرگومان‌ها موقعیتی هستند را ببینیم، می‌توانیم از انواع آرگومان که در بخش قبل استنباط شده است استفاده کنیم تا آن‌ها را با بررسی مقادیر نمونه برای هر نوع بسازیم. حتی می‌توان در صورت وجود نوع محتوا در حاشیه‌نویسی حتی تلاش کنیم collection نیز بسازیم.

from typing import List
_sample_args = {
    'float': 1.5,
    'int': 2,
    'str': 'abc',
    'List[int]': [1, 2, 3],
}

ما یک ForgeError را طوری تعریف می‌کنیم که هر خطای ناشی از ساخت آرگومان را بتوان به صورت دقیق مدیریت کرد. بدین ترتیب می‌توانیم آرگومان‌ها را برای یک کلکسیون از متدها، حتی اگر برخی از آن‌ها کار نکنند بسازیم.

class ForgeError(ValueError):
    pass

برای ساخت تابع، یک متد برمی‌داریم و آرگومان‌های نمونه را از sample_args_ برحسب نوع خروجی infer_arg_types بررسی می‌کنیم. در صورتی که هر نوع آرگومانی فاقد نوع پیش‌فرض باشند و نوع‌ها را نتوان استنباط کرد، خطاهایی رخ می‌دهند یا اگر هر نوعی وجود داشته باشند، در sample_args_ وجود نخواهند داشت.

def forge_args(func, sample_dict):
    arg_types = infer_arg_types(func)
    # If no positional arguments
    if not arg_types:
        return {}
    # If not all types could be inferred
    if not all(arg_types.values()):
        raise ForgeError(f'Some arguments have unknown types')

    arg_dict = OrderedDict()
    for i, (arg, type_) in enumerate(arg_types.items()):
        # check for default values if keyword arg
        defaults = getfullargspec(func).defaults
        n_args_remaining = len(arg_types) - i
        if len(defaults) >= n_args_remaining:
            arg_dict[arg] = defaults[- n_args_remaining]
        # if no defaults, attempt to forge from _sample_dict
        elif type_ in _sample_args:
            arg_dict[arg] = sample_dict[type_]
        else:
            raise ForgeError(
                f'Unsupported argument type ({type_}) for argument: {arg}')
    return arg_dict

از آنجا که این تابع نسبتاً پیچیده‌ای است، این وضعیت می‌تواند جای خوبی برای برخی تست‌های unit باشد.

سپس می‌توانیم تابعی تعریف کنیم که یک شیء بگیرد و روی همه متدهای آن بچرخد. این تابع از تابع forge_args ما برای ساخت آرگومان‌ها در مواردی که از رویکرد «بررسی همه‌جانبه» استفاده می‌کنند بهره می‌گیرد و به بررسی دلایل هر گونه شکست می‌پردازد.

def forge_and_eval_methods(obj):
    dir_filtered = dir_string_filter(obj)
    method_names = filter_methods(obj, dir_filtered)
    output_dict = {}
    for name in method_names:
        method = get_callable(obj, name)
        try:
            arg_dict = forge_args(method)
            output_dict[name] = str(method(**arg_dict))
        except ForgeError:
            output_dict[name] = "(Failed to forge args)"
        except Exception:
            output_dict[name] = "(Failed to run method with forged args)"
    return output_dict

در ادامه آن را روی وهله Rectangle امتحان می‌کنیم:

>>> forged_outputs = forge_and_eval_methods(rect)
>>> forged_outputs
{'area': '12.0', 'bisect': None, 'scale': 'None'}

تفاوت بین این نتیجه و نتیجه قبلی ما چندان بزرگ نیست، اما توجه کنید که scale هم اینک به جای ‘requires positional args’ مقدار ‘None’ را در خروجی ارائه می‌دهد. دلیل این امر آن است که این متد با موفقیت با آرگومان‌های ساخته شده فراخوانی شده است، اما به جای بازگشت دادن هیچ چیز، حالت rect را با تغییر دادن خصوصیت a و b عوض می‌کند. ردگیری این تغییرات به طوری که بتوان کاری که متدها انجام می‌دهند را حتی در زمانی که هیچ چیزی بازگشت نمی‌دهند تشخیص داد بسیار مناسب خواهد بود.

ردگیری تغییرات حالت: تکنیک مقایسه

در این مثال نمونه، بُعد یعنی a و b را از Rectangle تغییر می‌دهیم، اما از آنجا که متد هیچ بازگشتی ندارد تعیین این که چه اتفاقی افتاده است کار دشواری است. ما می‌توانیم این تغییرات را با ذخیره کردن یک کپی از همه خصوصیات شیء پیش از فراخوانی متد و پس از آن مقایسه کردن دریابیم. بدین ترتیب شیء StateComparator را طوری تعریف می‌کنیم که امکان ذخیره خصوصیت‌های جاری را با استفاده از خصوصیت __dict__ به دست دهد، سپس موارد خصوصیت‌های حذف، اضافه و تغییر یافته را پس از فراخوانی متد بررسی می‌کنیم.

class StateComparator:
    def __init__(self, obj):
        self.state = deepcopy(obj.__dict__)

    def compare(self, other):
        state_1 = self.state
        state_2 = deepcopy(other.__dict__)
        new_attrs = {k: v for k, v in state_2.items() if k not in state_1}
        del_attrs = {k: v for k, v in state_1.items() if k not in state_2}
        mod_attrs = {k: (v, state_2[k]) for k, v in state_1.items()
                     if v != state_2[k]}
        change_dict = {}
        if new_attrs:
            change_dict['new'] = new_attrs
        if del_attrs:
            change_dict['deleted'] = del_attrs
        if mod_attrs:
            change_dict['modified'] = mod_attrs
        return change_dict

پیاده‌سازی واقعی کمی پیچیده‌تر است، زیرا اغلب موارد داخلی دارای خصوصیت __dict__ نیستند.

ما با استفاده از تابع forge_and_eval_methods به عنوان یک قالب می‌توانیم یک تابع جدید تعریف کنیم که شامل ردگیری حالت و گزینه‌ای برای فعال یا غیرفعال کردن ساخت آرگومان باشد.

def call_all_tracked(obj, sample_dict=_sample_args, forge=True):
    dir_filtered = dir_string_filter(obj)
    method_names = filter_methods(obj, dir_filtered)
    output_dict = {}
    for name in method_names:
        obj_2 = deepcopy(obj)
        # store initial state
        state = StateComparator(obj_2)
        method = getattr(obj_2, name)
        try:
            if forge is True:
                arg_dict = forge_args(method, sample_dict)
            else:
                arg_dict = {}
            output_dict[name] = str(method(**arg_dict))
        except ForgeError:
            output_dict[name] = "(Failed to forge args)"
        except Exception:
            output_dict[name] = "(Failed to run method with forged args)"
        # check for state changes
        Nchange_dict = state.compare(obj_2)
        if change_dict:
            output_dict[name] = {
                'output': output_dict[name],
                'state changes': change_dict
            }
    return output_dict

در ادامه این مورد را روی rect تست می‌کنیم:

>>> call_all_tracked(rect)
{'area': '12.0', 'bisect': {'state changes': {'modified': {'a': (3.0, 1.5)}}}, 'scale': {'output': {'state changes': {'modified': {'a': (3.0, 4.5), 'b': (4.0, 6.0)}}}}}

اکنون هر تغییر حالت در دیکشنری تعیین شده است و تغییرات به وسیله یک چندتایی که عدد اول نشان‌دهنده مقدار اولیه و عدد دوم نماینده مقدار نهایی است نمایش می‌یابند.

متأسفانه ساختن آرگومان از آرگومان‌های کلیدواژه و حاشیه‌نویسی کار دشواری است، زیرا اغلب کدهای پایتون دارای سرنخ نوع نیستند و بخش بزرگ آن از سوی getargspec پشتیبانی نمی‌شوند. در این موارد، ساخت آرگومان از طریق روش brute force یا استخراج از docstring-ها نیز ممکن است برای قابلیت‌های آتی peep dis برنامه‌ریزی‌شده است.

پرینت کردن صرف docstring-ها راه آسان‌تری برای درک متدهایی است که در اغلب موارد نیازمند آرگومان هستند. آن‌ها را می‌توان به طور سیستماتیک از خصوصیت __doc__ پرینت کرد.

>>> for x in dir_filtered: 
>>>     attr = getattr(x, __doc__, "No docstring") 
>>>     print(f'{x}: {attr}')

گنجاندن خروجی بسیار طولانی است و رمزگشایی از آن دشوار است، زیرا دارای کدهای رنگی نیست. خروجی می‌تواند با termcolor رنگ‌آمیزی شود که برای peep dis نیز استفاده شود.

بازبینی شیء CLI: Peep Dis

در این بخش به بررسی peep dis می‌پردازیم که در دو حالت مفید واقع می‌شوند.

شیء اسرارآمیز

ما یک شیء ساده به نام mystery_obj داریم که شامل آرایه‌ای از دماهای مکانی در سان‌فرانسیسکو است، اما نمی‌دانیم آنجا کجاست. می‌توانیم dir را فراخوانی کنیم و سپس به صورت تکراری هر متد یا خصوصیت را بررسی کنیم. همچنین می‌توانیم شیء را peep نیز بکنیم. ما می‌توانیم stdtemp را به عنوان خصوصیتی که نیاز داریم به سرعت شناسایی کنیم.

موارد داخلی فیلتر می‌شوند و خروجی‌ها برای بقیه خصوصیت‌ها و متدها بدون آرگومان موقعیتی پرینت می‌شوند. متدها به رنگ بنفش و خصوصیت‌ها فیروزه‌ای رنگ‌آمیزی شده‌اند. خروجی متدهای نیازمند آرگومان موقعیتی خاکستری می‌شوند تا امکان شناسایی راحت‌تر بقیه موارد را پیدا کنیم.

نام متد چیست؟

ما یک DataFrame با ستون‌های temp و humidity برای سان‌فرانسیسکو داریم که می‌خواهیم به یک مدل داده ظریف برای یک API که می‌سازیم تبدیل کنیم. یک روش خلاصه برای این کار وجود دارد، اما چیزی از dir مشخص نمی‌شود و از Stack Overflow نیز نتیجه‌ای عاید نمی‌شود. اگر DataFrame را peep کنیم، به سرعت مشخص می‌شود که melt آن متدی است که نیاز داریم.

در ادامه روش‌های قدیمی انجام این کارها را بررسی می‌کنیم:

شیء mystery

>>> dir(myster_obj)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'itemp', 'mtemp', 'stdtemp', 'temp']
>>> obj.mtemp
<bound method WeatherSeries.mtemp of <main.WeatherSeries object at 0x7fdb6ed32748>>
>>> mystery_obj.mtemp()
{'min': 67, 'max': 71, 'index min': 0, 'index max': 4, 'len': 6}
>>> mystery_obj.itemp
<bound method WeatherSeries.itemp of <__main__.WeatherSeries object at 0x7fdb6ed32780>>
>>> mystery_obj.itemp()
TypeError: itemp() missing 1 required positional argument: 'i'
>>> mystery_obj.itemp(0)
67
>>> mystery_obj.temp()
array([ 0, 67],
      [ 1, 69],
      [ 2, 70],
      [ 3, 70],
      [ 4, 71],
      [ 5, 70]])
>>> mystery_obj.stdtemp()
TypeError: 'numpy.ndarray' object is not callable
>>> mystery_obj.stdtemp
array([67, 69, 70, 70, 71, 70])

نام متد

>>> df
    humitidy  temp
 0        65    67
 1        65    68
 2        60    68
 3        60    69
 4        55    70 
>>> dir(df) 
['T', '_AXIS_ALIASES', '_AXIS_IALIASES', '_AXIS_LEN', '_AXIS_NAMES', '_AXIS_NUMBERS', '_AXIS_ORDERS', '_AXIS_REVERSED', '_AXIS_SLICEMAP', '__abs__', '__add__', '__and__', '__array__', '__array_wrap__', '__bool__', '__bytes__', '__class__', '__contains__', '__copy__', '__deepcopy__', '__delattr__', '__delitem__', '__dict__', '__dir__', '__div__', '__doc__', '__eq__', '__finalize__', '__floordiv__', '__format__', '__ge__', '__getattr__', '__getattribute__', '__getitem__', '__getstate__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__invert__', '__ipow__', '__isub__', '__iter__', '__itruediv__', '__le__', '__len__', '__lt__', '__mod__', '__module__', '__mul__', '__ne__', '__neg__', '__new__', '__nonzero__', '__or__', '__pow__', '__radd__', '__rand__', '__rdiv__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rmod__', '__rmul__', '__ror__', '__round__', '__rpow__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__', '__setitem__', '__setstate__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__unicode__', '__weakref__', '__xor__', '_accessors', '_add_numeric_operations', '_add_series_only_operations', '_add_series_or_dataframe_operations', '_agg_by_level', '_agg_doc', '_aggregate', '_aggregate_multiple_funcs', '_align_frame', '_align_series', '_apply_broadcast', '_apply_empty_result', '_apply_raw', '_apply_standard', '_at', '_box_col_values', '_box_item_values', '_builtin_table', '_check_inplace_setting', '_check_is_chained_assignment_possible', '_check_percentile', '_check_setitem_copy', '_clear_item_cache', '_combine_const', '_combine_frame', '_combine_match_columns', '_combine_match_index', '_combine_series', '_combine_series_infer', '_compare_frame', '_compare_frame_evaluate', '_consolidate', '_consolidate_inplace', '_construct_axes_dict', '_construct_axes_dict_for_slice', '_construct_axes_dict_from', '_construct_axes_from_arguments', '_constructor', '_constructor_expanddim', '_constructor_sliced', '_convert', '_count_level', '_create_indexer', '_cython_table', '_dir_additions', '_dir_deletions', '_ensure_valid_index', '_expand_axes', '_flex_compare_frame', '_from_arrays', '_from_axes', '_get_agg_axis', '_get_axis', '_get_axis_name', '_get_axis_number', '_get_axis_resolvers', '_get_block_manager_axis', '_get_bool_data', '_get_cacher', '_get_index_resolvers', '_get_item_cache', '_get_numeric_data', '_get_values', '_getitem_array', '_getitem_column', '_getitem_frame', '_getitem_multilevel', '_getitem_slice', '_gotitem', '_iat', '_iget_item_cache', '_iloc', '_indexed_same', '_info_axis', '_info_axis_name', '_info_axis_number', '_info_repr', '_init_dict', '_init_mgr', '_init_ndarray', '_internal_names', '_internal_names_set', '_is_builtin_func', '_is_cached', '_is_cython_func', '_is_datelike_mixed_type', '_is_mixed_type', '_is_numeric_mixed_type', '_is_view', '_ix', '_ixs', '_join_compat', '_loc', '_maybe_cache_changed', '_maybe_update_cacher', '_metadata', '_needs_reindex_multi', '_obj_with_exclusions', '_protect_consolidate', '_reduce', '_reindex_axes', '_reindex_axis', '_reindex_columns', '_reindex_index', '_reindex_multi', '_reindex_with_indexers', '_repr_data_resource_', '_repr_fits_horizontal_', '_repr_fits_vertical_', '_repr_html_', '_repr_latex_', '_reset_cache', '_reset_cacher', '_sanitize_column', '_selected_obj', '_selection', '_selection_list', '_selection_name', '_series', '_set_as_cached', '_set_axis', '_set_axis_name', '_set_is_copy', '_set_item', '_setitem_array', '_setitem_frame', '_setitem_slice', '_setup_axes', '_shallow_copy', '_slice', '_stat_axis', '_stat_axis_name', '_stat_axis_number', '_try_aggregate_string_function', '_typ', '_unpickle_frame_compat', '_unpickle_matrix_compat', '_update_inplace', '_validate_dtype', '_values', '_where', '_xs', 'a', 'abs', 'add', 'add_prefix', 'add_suffix', 'agg', 'aggregate', 'align', 'all', 'any', 'append', 'apply', 'applymap', 'as_blocks', 'as_matrix', 'asfreq', 'asof', 'assign', 'astype', 'at', 'at_time', 'axes', 'b', 'between_time', 'bfill', 'blocks', 'bool', 'boxplot', 'clip', 'clip_lower', 'clip_upper', 'columns', 'combine', 'combine_first', 'compound', 'consolidate', 'convert_objects', 'copy', 'corr', 'corrwith', 'count', 'cov', 'cummax', 'cummin', 'cumprod', 'cumsum', 'describe', 'diff', 'div', 'divide', 'dot', 'drop', 'drop_duplicates', 'dropna', 'dtypes', 'duplicated', 'empty', 'eq', 'equals', 'eval', 'ewm', 'expanding', 'ffill', 'fillna', 'filter', 'first', 'first_valid_index', 'floordiv', 'from_csv', 'from_dict', 'from_items', 'from_records', 'ftypes', 'ge', 'get', 'get_dtype_counts', 'get_ftype_counts', 'get_value', 'get_values', 'groupby', 'gt', 'head', 'hist', 'iat', 'idxmax', 'idxmin', 'iloc', 'index', 'info', 'insert', 'interpolate', 'is_copy', 'isin', 'isnull', 'items', 'iteritems', 'iterrows', 'itertuples', 'ix', 'join', 'keys', 'kurt', 'kurtosis', 'last', 'last_valid_index', 'le', 'loc', 'lookup', 'lt', 'mad', 'mask', 'max', 'mean', 'median', 'melt', 'memory_usage', 'merge', 'min', 'mod', 'mode', 'mul', 'multiply', 'ndim', 'ne', 'nlargest', 'notnull', 'nsmallest', 'nunique', 'pct_change', 'pipe', 'pivot', 'pivot_table', 'plot', 'pop', 'pow', 'prod', 'product', 'quantile', 'query', 'radd', 'rank', 'rdiv', 'reindex', 'reindex_axis', 'reindex_like', 'rename', 'rename_axis', 'reorder_levels', 'replace', 'resample', 'reset_index', 'rfloordiv', 'rmod', 'rmul', 'rolling', 'round', 'rpow', 'rsub', 'rtruediv', 'sample', 'select', 'select_dtypes', 'sem', 'set_axis', 'set_index', 'set_value', 'shape', 'shift', 'size', 'skew', 'slice_shift', 'sort_index', 'sort_values', 'sortlevel', 'squeeze', 'stack', 'std', 'style', 'sub', 'subtract', 'sum', 'swapaxes', 'swaplevel', 'tail', 'take', 'to_clipboard', 'to_csv', 'to_dense', 'to_dict', 'to_excel', 'to_feather', 'to_gbq', 'to_hdf', 'to_html', 'to_json', 'to_latex', 'to_msgpack', 'to_panel', 'to_period', 'to_pickle', 'to_records', 'to_sparse', 'to_sql', 'to_stata', 'to_string', 'to_timestamp', 'to_xarray', 'transform', 'transpose', 'truediv', 'truncate', 'tshift', 'tz_convert', 'tz_localize', 'unstack', 'update', 'values', 'var', 'where', 'xs']

ممکن است ده دقیقه طول بکشد تا متوجه شویم که آن چه نیاز داریم df.melt است.

>>> df.melt()
     variable  value
  0  humitidy     65
  1  humitidy     65
  2  humitidy     60
  3  humitidy     60
  4  humitidy     55
  5  humitidy     55
  6      temp     67
  7      temp     69
  8      temp     70
  9      temp     70
 10      temp     71
 11      temp     70

بدین ترتیب به پایان این مقاله می‌رسیم. امیدواریم از مطالعه این راهنما بهره کافی را برده باشید.

اگر این مطلب برای شما مفید بوده است، آموزش‌های زیر نیز به شما پیشنهاد می‌شوند:

==

بر اساس رای ۲ نفر
آیا این مطلب برای شما مفید بود؟
شما قبلا رای داده‌اید!
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.

«میثم لطفی» در رشته‌های ریاضیات کاربردی و مهندسی کامپیوتر به تحصیل پرداخته و شیفته فناوری است. وی در حال حاضر علاوه بر پیگیری علاقه‌مندی‌هایش در رشته‌های برنامه‌نویسی، کپی‌رایتینگ و محتوای چندرسانه‌ای، در زمینه نگارش مقالاتی با محوریت نرم‌افزار با مجله فرادرس همکاری دارد.